????

Your IP : 3.139.240.192


Current Path : C:/Program Files/Microsoft SQL Server/MSSQL15.MSSQLSERVER/MSSQL/Binn/
Upload File :
Current File : C:/Program Files/Microsoft SQL Server/MSSQL15.MSSQLSERVER/MSSQL/Binn/sqlscriptupgrade.dll

MZ����@��	�!�L�!This program cannot be run in DOS mode.

$�	W�h9��h9��h9��
8��h9�����h9��8��h9��h8��h9��<��h9��=��h9��:��h9�$0��h9�$9��h9�$ƿ�h9��h���h9�$;��h9�Rich�h9�PEd�W�g�" �Y�@�Y��Z`A+dt+PP�uY@��YP(�YL�$T% �.text� `.rdata@  @@.data�00@�.pdata�@@@@.rsrc�uYP�YP@@.relocL�Y�Y@B����������������H��tH��tH� H����3���������������������ff�H;
��uH��f�����u��H�������������������H��(��t9��t(��t��t
�H��(�������H��(�I��H��(�M����H��(�(��������H�\$H�t$H�|$ AVH�� H��L��3����u3�H�\$0H�t$8H�|$HH�� A^����؈D$@@��=U'���E'�(��tO��
�2�uH��H�
��@
��u)���t H��H�
w�
��&@2�����@���[����	H��H�8t$H�����tL�ƺI��H�L�
A��!�����������������H�\$H�t$WH�� @��� 3ۅ�3�H�\$0H�t$@H�� _�ȉ� �@���D$8�==&u5���E�
�&&�@���3�@������Ë�랹�X��������H��H�X L�@�PH�HVWAVH��@I��L���u9( 3����B���wEH��H��u
�D$0���؉D$0����L�Ƌ�I������؉D$0����L�Ƌ�I���9�؉D$0��u8��u4L��3�I���L��3�I���<���H�H��tL��3�I���
��t��u@L�Ƌ�I�������؉D$0��t)H��
H��u	�X�\$0�L�Ƌ�I��O
�؉D$0�3ۉ\$0��H�\$xH��@A^_^������������������H�\$H�t$WH�� I����H��u�L�Nj�H��H�\$0H�t$8H�� _�����������@SH�� H��3�3H��2�H�Ⱥ	�H�� [H�%��������H�L$H��8��
��t��)H�
�H�D$8H� H�D$8H��H��H��H�\H�D$@H�`�6	��0�:�Hk�H�
2H��Hk�H�
H�L �Hk�H�
�H�L H�
Q���H��8��������@SVWH��@H���
H���3�E3�H�T$`H��H��t9H�d$8H�L$hH�T$`L��H�L$0L��H�L$pH�L$(3�H�\$ ��
�ǃ�|�H��@_^[��������H�\$ UH��H�� H�TH�2��-�+H;�utH�eH�M�N
H�EH�E�H
��H1E�D
��H�M H1E�<
�E H�MH�� H3E H3EH3�H�������H#�H�3��-�+H;�HD�H��H�\$HH��H��H�� ]���������H��(��uH�=�
u��	�H��(����������H�
�!H�%�	������H�
�!����������H��!���������H��!���������H��(����H�����H�H��(����������H��(�g��t!eH�%0H�H�H;�t3��H�
p!u�2�H��(������H��(�'��t������r��t2���q�H��(���������H��(3��i����H��(��������H��(���u2���z��u�}��H��(���������H��(�c�^�H��(��������H�\$H�l$H�t$WH�� I��I���H���h��u��uL��3�H��H���H�T$X�L$PH�\$0H�l$8H�t$@H�� _���������H��(���tH�
L H��(�����u�H��(���������H��(3��H��(���������@SH�� ���ɻDÈ���U��u2���H��u	3��a���H�� [��������@SH��@�=����������]��t(��u$H�
�����uH�
����ts2��xH���@�ƒ�?+�H��H��H3�H�D$ H�D$(D$ H�D$0�L$0/H�D$ H�D$(D$ H�D$0�
#�L$0�
&���H��@[ù�)���������H��L���MZf91�uyHcd�H�!�H��9PEu_�f9AuTL+��AH�QH��AH��L��H�$I;�t�JL;�r
�B�L;�rH��(��3�H��u2���z$}2��
��2��2�H����������@SH�� ����3҅�t��uH�H�� [���������@SH�� �=���t��u�������H�� [����������H�
��������̃%����������H�\$UH��$@���H���ٹ���t���)����3�H�M�A���nH�M��H���H���H��E3��,H��t<H�d$8H���H���L��H�L$0L��H���H�L$(H�M�H�L$ 3��H���H�L$PH���3�H���A��H��H�����H���H�D$`�D$P@�D$T�/��H�D$PH�D$@H�E���H�D$H3�^H�L$@�[��u��u�H���H��$�H��]�������H�\$WH�� H��H�=��H�H��t��H��H;�r�H�\$0H�� _���������H�\$WH�� H��H�=��H�H��t�XH��H;�r�H�\$0H�� _�������������������������������H�\$H�t$WH��3��53��%�D��3�D��A��ntelA��GenuD�ҋ�3ɍGE��A��ineI�$Eʉ\$D�ىL$�T$uPH�
��%�?�=�t(=`t!=pt������ w$H�H��sD�QA��D�F�D�=�;�|&3���$���\$�L$�T$��	sA��D�A��sp�I�CA��sUA��sN3��H�� H�H�T$ H�D$ $<u2�����@�� t�� ����H�\$(3�H�t$0H��_���������̸�������3�9������������������%������%������%�������%0������%������%������%�������%������%�������%�������%�������%@�������̰���������̰���������̰���������̰���������̰����������3�����������������ff���������@UH�� H��M@H�� ]�����������@UH�� H�������M8H�� ]����������@UH��0H��H��H�L$(�T$ L�
��L�Ep�UhH�M`����H��0]��������@UH��H�3Ɂ8�����]���-//�.�.�.�.�.p.\.H.*..�-�-�,�,�,�-l-�-0----J-�@`@��P��2@�3@procsyst.sqlu_tables.sqlProvisionAgentIdentity.sqlno_op.sqlsqlagent90_sysdbupg.sqlsqlagent100_msdb_upgrade.sqlrepl_upgrade.sqlprovisionsystemaccounts.sqlprovisionexpress.sqlupgrade_ucp_cmdw_discovery.sqlupgrade_ucp_cmdw.sqlmsdb110_upgrade.sqlssis_discoveryISServer_upgrade.sqlSSIS_hotfix_install.sqlshutdown_xevents_add.sqlprovision_ceipsvc_account.sqlW�g�&&W�g�&�&W�g
P�&�&0@� @� @8!@uRSDS��^����N��3ĜWF:\dbs\sh\el1q\1018_151007\cmd\1t\obj\x64retail\sql\mkmastr\scriptdlls\upgradesrc\sqlscriptupgrade\sqlscriptupgrade.vcxproj\sqlscriptupgrade.pdbGCTLP.text$mnP.text$mn$00b�.text$x �.idata$5� .00cfg� .CRT$XCA!.CRT$XCZ!.CRT$XIA!.CRT$XIZ!.CRT$XPA !.CRT$XPZ(!.CRT$XTA0!.CRT$XTZ8!(.gfids`!�.rdata&.rdata$zzzdbg).rtc$IAA).rtc$IZZ ).rtc$TAA().rtc$TZZ0)�.xdata+d.edatat+<.idata$2�+.idata$3�+�.idata$4�,�.idata$60�.data�2�.bss@�.pdataP`.rsrc$01`T qY.rsrc$02t	d42�v$�h��h2Pd42pv<Z�z��2PB	4r�p`v����RPd42p		brp`020

4	
2PBB	"v�N�NPB202020r0dT42pBBBBBB4��P

4
2p

4
2pd4p����B+8+<+@+W+sqlscriptupgrade.dllGetDbScriptLevelUpgradeTableH,�,� h,�-� �+2/ �-//�.�.�.�.�.p.\.H.*..�-�-�,�,�,�-l-�-0----J-__C_specific_handler%__std_type_info_destroy_list>memsetVCRUNTIME140.dll6_initterm7_initterm_e?_seh_filter_dll_configure_narrow_argv3_initialize_narrow_environment4_initialize_onexit_table"_execute_onexit_table_cexitapi-ms-win-crt-runtime-l1-1-0.dll�RtlCaptureContext�RtlLookupFunctionEntry�RtlVirtualUnwind�UnhandledExceptionFiltersSetUnhandledExceptionFilterGetCurrentProcess�TerminateProcess�IsProcessorFeaturePresentIQueryPerformanceCounterGetCurrentProcessId GetCurrentThreadId�GetSystemTimeAsFileTime DisableThreadLibraryCallsgInitializeSListHead}IsDebuggerPresentKERNEL32.dll�] �f��2��-�+����/ �.x!@�.�!@9�.�!@R�!@R�!@�."@�.8"@%�.�!@R�.x"@R�.�"@��.�"@#@RH#@�.x#@�R�.#@R�.H#@���.�#@R�.�#@���.�#@%R�. $@��.X$@RPq0)���)��4)��x)���)�
�)H*P!*(�*�L*Tw(*��0*�%`*,`�*h}�*���*���*�0�*8h�*p��*��p*���*�U8*\�h*��x*�&�*,h�*p��*�G�*`b+hp)���)���)��X*H�(������.��.��.0��.H��.`��.x��.���.���.���.���.���.��. ��.8��.P��.h��.���.���.������	�			(	8	H	X	h	x	�	�	�	�	�	�	�	�			(	8PY�@rbF��}(��V��  �,��@1�! ^p& ��(�H��H�5
h5S�0BSY8�YE��Y�	X�Y��Y���Y2`T��Y}
TSQLSCRIPT�4VS_VERSION_INFO����RR?JStringFileInfo&040904B0PlatformNT�ILegalTrademarksMicrosoft SQL Server is a registered trademark of Microsoft Corporation. CommentsSQL&GoldenBitsTrueLCompanyNameMicrosoft CorporationXFileDescriptionSQL Upgrade Scripts DLL�2FileVersion2019.0150.2130.03 ((sql2019_rtm_gdr).241018-2146)BInternalNameSQLSCRIPTUPGRADEd LegalCopyrightMicrosoft. All rights reserved.ROriginalFilenameSQLSCRIPTUPGRADE.DLLJProductNameMicrosoft SQL Server<ProductVersion15.0.2130.3>AssociatedHotfixBuild4410DVarFileInfo$Translation	�/*
** ProcSyst.SQL
** Copyright Microsoft, Inc. 1994 - 2000
** All Rights Reserved.
*/

go
use master
go

----- DO NOT ADD SYSTEM OBJECTS HERE. Place them in a script for the resource-database.

set nocount on
set implicit_transactions off
set ansi_nulls on	-- default for osql (consistent for suites)
set quoted_identifier on	-- Force all ye devs to do this correctly!
go


----- DO NOT ADD SYSTEM OBJECTS HERE. Place them in a script for the resource-database.


-- Drop client-side tables in master. They have been moved to resouce database.
--
if object_id('spt_provider_types', 'local') is not null
	drop table spt_provider_types
if object_id('spt_datatype_info_ext', 'local') is not null
	drop table spt_datatype_info_ext
if object_id('spt_datatype_info', 'local') is not null
	drop table spt_datatype_info
if object_id('spt_server_info', 'local') is not null
	drop table spt_server_info
go


-- Drop stored procedures no longer in use
if object_id('sp_msupg_removesystemcomputedcolumns', 'local') is not null
	drop procedure sp_msupg_removesystemcomputedcolumns
if object_id('sp_msupg_recreatecatalogfaketables', 'local') is not null
	drop procedure sp_msupg_recreatecatalogfaketables
if object_id('sp_msupg_dosystabcatalogupgrades', 'local') is not null
	drop procedure sp_msupg_dosystabcatalogupgrades
if object_id('sp_msupg_dropcatalogcomputedcols', 'local') is not null
	drop procedure sp_msupg_dropcatalogcomputedcols
if object_id('sp_msupg_createcatalogcomputedcols', 'local') is not null
	drop procedure sp_msupg_createcatalogcomputedcols
if object_id('sp_msupg_recreatesystemviews', 'local') is not null
	drop procedure sp_msupg_recreatesystemviews
if object_id('sp_msupg_upgradecatalog', 'local') is not null
	drop procedure sp_msupg_upgradecatalog
if object_id('sp_db_upgrade', 'local') is not null
	drop procedure sp_db_upgrade
if object_id('sp_MS_upd_sysobj_category', 'local') is not null
	drop procedure sp_MS_upd_sysobj_category
if object_id('sp_remove_tempdb_file', 'local') is not null
	drop procedure sp_remove_tempdb_file
if object_id('sp_validatepropertyinputs', 'local') is not null
	drop procedure sp_validatepropertyinputs
if object_id('sp_helplog', 'local') is not null
	drop procedure sp_helplog
if object_id('sp_helpsql', 'local') is not null
	drop procedure sp_helpsql
if object_id('sp_fixindex', 'local') is not null
	drop procedure sp_fixindex
if object_id('sp_diskdefault', 'local') is not null
	drop procedure sp_diskdefault	
if object_id('sp_logdevice', 'local') is not null
	drop procedure sp_logdevice
if object_id('sp_sdidebug', 'local') is not null
	drop procedure sp_sdidebug
if object_id('xp_MSFullText', 'local') is not null
	drop procedure xp_MSFullText	
go

if object_id('xp_getprotocoldllinfo', 'local') is not null
	drop procedure xp_getprotocoldllinfo	
if object_id('xp_sqlagent_proxy_account', 'local') is not null
	drop procedure xp_sqlagent_proxy_account	
if object_id('xp_sqlagent_msx_account', 'local') is not null
	drop procedure xp_sqlagent_msx_account	
if object_id('xp_ntsec_enumdomains', 'local') is not null
	drop procedure xp_ntsec_enumdomains	
if object_id('xp_updateFTSSQLAccount', 'local') is not null
	drop procedure xp_updateFTSSQLAccount	
if object_id('xp_unc_to_drive', 'local') is not null
	drop procedure xp_unc_to_drive	
go

if object_id('sp_add_server_sortinfo', 'local') is not null
	drop procedure sp_add_server_sortinfo	
if object_id('sp_add_server_sortinfo75', 'local') is not null
	drop procedure sp_add_server_sortinfo75	
if object_id('sp_MScheck_uid_owns_anything', 'local') is not null
	drop procedure sp_MScheck_uid_owns_anything	
if object_id('sp_MSget_current_activity', 'local') is not null
	drop procedure sp_MSget_current_activity	
if object_id('sp_MSset_current_activity', 'local') is not null
	drop procedure sp_MSset_current_activity	
if object_id('sp_MSobjsearch', 'local') is not null
	drop procedure sp_MSobjsearch	
go

if object_id('sp_blockcnt', 'local') is not null
	drop procedure sp_blockcnt	
if object_id('sp_tempdbspace', 'local') is not null
	drop procedure sp_tempdbspace	
if object_id('sp_fallback_MS_sel_fb_svr', 'local') is not null
	drop procedure sp_fallback_MS_sel_fb_svr	
if object_id('sp_checknames', 'local') is not null
	drop procedure sp_checknames	
if object_id('sp_oledb_column_constraints', 'local') is not null
	drop procedure sp_oledb_column_constraints	
if object_id('sp_oledb_indexinfo', 'local') is not null
	drop procedure sp_oledb_indexinfo
go

if object_id('MS_sqlctrs_users', 'local') is not null
	drop procedure MS_sqlctrs_users
if object_id('sp_addalias','P') IS NOT NULL
	drop procedure sp_addalias
if object_id('sp_addgroup','P') IS NOT NULL
	drop procedure sp_addgroup
if object_id('sp_dropgroup','P') IS NOT NULL
	drop procedure sp_dropgroup
if object_id('sp_changegroup','P') IS NOT NULL
	drop procedure sp_changegroup
if object_id('sp_helpgroup','P') IS NOT NULL
	drop procedure sp_helpgroup
go

-- From 7.0, dropped in 8.0
if object_id('spt_committab','local') is not null
	drop table spt_committab
if object_id('sysalternates','local') is not null
	drop view sysalternates
if object_id('sp_abort_xact','local') is not null
	drop procedure sp_abort_xact
if object_id('sp_checkdbtempsize','local') is not null
	drop procedure sp_checkdbtempsize
if object_id('sp_checktabletempsize','local') is not null
	drop procedure sp_checktabletempsize
if object_id('sp_commit_xact','local') is not null
	drop procedure sp_commit_xact
if object_id('sp_helpstartup','local') is not null
	drop procedure sp_helpstartup
if object_id('sp_makestartup','local') is not null
	drop procedure sp_makestartup
if object_id('sp_probe_xact','local') is not null
	drop procedure sp_probe_xact
if object_id('sp_remove_xact','local') is not null
	drop procedure sp_remove_xact
if object_id('sp_scan_xact','local') is not null
	drop procedure sp_scan_xact
if object_id('sp_start_xact','local') is not null
	drop procedure sp_start_xact
if object_id('sp_stat_xact','local') is not null
	drop procedure sp_stat_xact
if object_id('sp_unmakestartup','local') is not null
	drop procedure sp_unmakestartup
go

checkpoint
go


----- DO NOT ADD SYSTEM OBJECTS HERE. Place them in a script for the resource-database.

/*
** U_Tables.CQL    --- 1996/09/16 12:22
** Copyright Microsoft, Inc. 1994 - 2000
** All Rights Reserved.
** ***********************
** If this file is updated for Denali, then do not forget to update the target level for u_table.sql
** in sql\ntdbms\mkmaster\scriptdlls\upgradesrc\sqlscriptupgrade.cpp
**
*/

go
use master
go
set nocount on
go


declare @vdt varchar(99)
select  @vdt = convert(varchar,getdate(),113)
raiserror('Starting u_Tables.SQL at  %s',0,1,@vdt) with nowait
raiserror('This file creates all the system tables in master.',0,1)
go

-- Create a synonym spt_values in master pointing to spt_master in Resource DB,
-- for backward compat

if object_id('spt_values','U') IS NOT NULL
	begin
	print 'drop table spt_values ....'
	drop table spt_values
	end
go

if object_id('spt_values','SN') IS NOT NULL
	begin
	print 'drop synonym spt_values ....'
	drop synonym spt_values
	end
go

if object_id('spt_values','V') IS NOT NULL
	begin
	print 'drop view spt_values ....'
	drop view spt_values
	end
go

raiserror('Creating view ''%s''.', -1,-1,'spt_values')
go

create view spt_values as
select name collate database_default as name,
	number,
	type collate database_default as type,
	low, high, status
from sys.spt_values
go

EXEC sp_MS_marksystemobject 'spt_values'
go

grant select on spt_values to public
go

------------------------------------------------------------------

if object_id('spt_monitor','U') IS NOT NULL
	begin
	print 'drop table spt_monitor ....'
	drop table spt_monitor
	end
go

------------------------------------------------------------------
------------------------------------------------------------------

raiserror('Creating ''%s''.', -1,-1,'spt_monitor')
go

create table spt_monitor
(
	lastrun		datetime	NOT NULL,
	cpu_busy	int		NOT NULL,
	io_busy		int		NOT NULL,
	idle		int		NOT NULL,
	pack_received	int		NOT NULL,
	pack_sent	int		NOT NULL,
	connections	int		NOT NULL,
	pack_errors	int		NOT NULL,
	total_read	int		NOT NULL,
	total_write 	int		NOT NULL,
	total_errors 	int		NOT NULL
)
go

EXEC sp_MS_marksystemobject 'spt_monitor'
go

------------------------------------------------------------------

raiserror('Grant Select on spt_monitor',0,1)
go

grant select on spt_monitor to public
go


------------------------------------------------------------------
------------------------------------------------------------------


raiserror('Insert into spt_monitor ....',0,1)
go

insert into spt_monitor
	select
	lastrun = getdate(),
	cpu_busy = @@cpu_busy,
	io_busy = @@io_busy,
	idle = @@idle,
	pack_received = @@pack_received,
	pack_sent = @@pack_sent,
	connections = @@connections,
	pack_errors = @@packet_errors,
	total_read = @@total_read,
	total_write = @@total_write,
	total_errors = @@total_errors
go


-------------------------------------------------------------------------------------------------------
-- Extended Events Default Session: system_health
--
-- All wait types in this session use <= and >= for ranges in order to reduce the dependency on 
-- wait types outside of a range.
-- If wait type values are changed in future in \Sql\Ntdbms\dk\sos\include\sc_waittypes.h then this
-- session definition could break as it has a dependency on the values within ranges of wait types.
-- The only complete mitigation would be to only use = predicates but that would be a huge perf hit
-- as events are fired for this session very frequently in perf-sensitive code paths.
-- As a partial mitigation, static_asserts were added to InitXE() in \Sql\Ntdbms\ksource\serverma.cpp to
-- generate compile-time errors if any of the ranges are changed in future.
---------------------------------------------------------------------------------------------------------

    if exists(select * from sys.server_event_sessions where name='system_health')
        drop event session system_health on server
    go
    -- The predicates in this session have been carefully crafted to minimize impact of event collection
    -- Changing the predicate definition may impact system performance
    --
    create event session system_health on server
    add event sqlserver.error_reported
    (
        action (package0.callstack, sqlserver.session_id, sqlserver.database_id, sqlserver.sql_text, sqlserver.tsql_stack)
        -- Get callstack, SPID, and query for all high severity errors ( above sev 20 )
        where severity >= 20
        -- Get callstack, SPID, and query for OOM errors ( 17803 , 701 , 802 , 8645 , 8651 , 8657 , 8902 ), Hekaton checkpoint/merge errors (41354, 41355, 41367, 41384), Hekaton compilation related errors (41336, 41309, 41312, 41313)
        or (error_number = 17803 or error_number = 701 or error_number = 802 or error_number = 8645 or error_number = 8651 or error_number = 8657 or error_number = 8902 or error_number = 41354 or error_number = 41355 or error_number = 41367 or error_number = 41384 or error_number = 41336 or error_number = 41309 or error_number = 41312 or error_number = 41313)
    ),
    add event sqlclr.clr_allocation_failure
        (action (package0.callstack, sqlserver.session_id)),
    add event sqlclr.clr_virtual_alloc_failure
        (action (package0.callstack, sqlserver.session_id)),
    add event sqlos.scheduler_monitor_non_yielding_ring_buffer_recorded,
    add event sqlserver.xml_deadlock_report,
    add event sqlos.wait_info
    (
        action (package0.callstack, sqlserver.session_id, sqlserver.sql_text)
        where 
        (duration > 15000 and 
            (   
                (wait_type >= N'LATCH_NL' -- Waits for latches and important wait resources (not locks ) that have exceeded 15 seconds. 
                    and
                    (
                        (wait_type >= N'PAGELATCH_NL' and wait_type <= N'PAGELATCH_DT') --PAGELATCH_NL;PAGELATCH_KP;PAGELATCH_SH;PAGELATCH_UP;PAGELATCH_EX;PAGELATCH_DT
                        or (wait_type <= N'LATCH_DT') --LATCH_NL;LATCH_KP;LATCH_SH;LATCH_UP;LATCH_EX;LATCH_DT
                        or (wait_type >= N'PAGEIOLATCH_NL' and wait_type <= N'PAGEIOLATCH_DT') --PAGEIOLATCH_NL;PAGEIOLATCH_KP;PAGEIOLATCH_SH;PAGEIOLATCH_UP;PAGEIOLATCH_EX;PAGEIOLATCH_DT
                        or (wait_type >= N'IO_COMPLETION' and wait_type <= N'NETWORK_IO') --IO_COMPLETION;ASYNC_IO_COMPLETION;NETWORK_IO
                        or (wait_type = N'RESOURCE_SEMAPHORE')
                        or (wait_type = N'SOS_WORKER')
                        or (wait_type >= N'FCB_REPLICA_WRITE' and wait_type <= N'WRITELOG') --FCB_REPLICA_WRITE;FCB_REPLICA_READ;WRITELOG
                        or (wait_type = N'CMEMTHREAD')
                        or (wait_type = N'TRACEWRITE')
                        or (wait_type = N'RESOURCE_SEMAPHORE_MUTEX')
                    )
                )
                or 
                (duration > 30000       -- Waits for locks that have exceeded 30 secs.
                    and wait_type <= N'LCK_M_RX_X' -- all lock waits
                ) 
            )
        )
    ),
    add event sqlos.wait_info_external
    (
        action (package0.callstack, sqlserver.session_id, sqlserver.sql_text)
        where 
        (duration > 5000 and
            (   
                (   -- Login related preemptive waits that have exceeded 5 seconds.
                    (wait_type >= N'PREEMPTIVE_OS_GENERICOPS' and wait_type <= N'PREEMPTIVE_OS_ENCRYPTMESSAGE') --PREEMPTIVE_OS_GENERICOPS;PREEMPTIVE_OS_AUTHENTICATIONOPS;PREEMPTIVE_OS_ACCEPTSECURITYCONTEXT;PREEMPTIVE_OS_ACQUIRECREDENTIALSHANDLE;PREEMPTIVE_OS_COMPLETEAUTHTOKEN;PREEMPTIVE_OS_DECRYPTMESSAGE;PREEMPTIVE_OS_DELETESECURITYCONTEXT;PREEMPTIVE_OS_ENCRYPTMESSAGE
                    or  (wait_type >= N'PREEMPTIVE_OS_INITIALIZESECURITYCONTEXT' and wait_type <= N'PREEMPTIVE_OS_QUERYSECURITYCONTEXTTOKEN') --PREEMPTIVE_OS_INITIALIZESECURITYCONTEXT;PREEMPTIVE_OS_LOGONUSER;PREEMPTIVE_OS_QUERYSECURITYCONTEXTTOKEN
                    or  (wait_type >= N'PREEMPTIVE_OS_AUTHZGETINFORMATIONFROMCONTEXT' and wait_type <= N'PREEMPTIVE_OS_REVERTTOSELF') --PREEMPTIVE_OS_AUTHZGETINFORMATIONFROMCONTEXT;PREEMPTIVE_OS_AUTHZINITIALIZECONTEXTFROMSID;PREEMPTIVE_OS_AUTHZINITIALIZERESOURCEMANAGER;PREEMPTIVE_OS_LOOKUPACCOUNTSID;PREEMPTIVE_OS_REVERTTOSELF
                    or  (wait_type >= N'PREEMPTIVE_OS_CRYPTACQUIRECONTEXT' and wait_type <= N'PREEMPTIVE_OS_DEVICEOPS') --PREEMPTIVE_OS_CRYPTACQUIRECONTEXT;PREEMPTIVE_OS_CRYPTIMPORTKEY;PREEMPTIVE_OS_DEVICEOPS
                    or  (wait_type >= N'PREEMPTIVE_OS_NETGROUPGETUSERS' and wait_type <= N'PREEMPTIVE_OS_NETUSERMODALSGET') --PREEMPTIVE_OS_NETGROUPGETUSERS;PREEMPTIVE_OS_NETLOCALGROUPGETMEMBERS;PREEMPTIVE_OS_NETUSERGETGROUPS;PREEMPTIVE_OS_NETUSERGETLOCALGROUPS;PREEMPTIVE_OS_NETUSERMODALSGET
                    or  (wait_type >= N'PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICYFREE' and wait_type <= N'PREEMPTIVE_OS_DOMAINSERVICESOPS') --PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICYFREE;PREEMPTIVE_OS_DOMAINSERVICESOPS
                    or (wait_type = N'PREEMPTIVE_OS_VERIFYSIGNATURE')
                )
                or 
                (duration > 45000   -- Preemptive OS waits that have exceeded 45 seconds. 
                    and 
                    (   
                        (wait_type >= N'PREEMPTIVE_OS_SETNAMEDSECURITYINFO' and wait_type <= N'PREEMPTIVE_CLUSAPI_CLUSTERRESOURCECONTROL') --PREEMPTIVE_OS_SETNAMEDSECURITYINFO;PREEMPTIVE_OS_CLUSTEROPS;PREEMPTIVE_CLUSAPI_CLUSTERRESOURCECONTROL
                        or  (wait_type >= N'PREEMPTIVE_OS_RSFXDEVICEOPS' and wait_type <= N'PREEMPTIVE_OS_DSGETDCNAME') --PREEMPTIVE_OS_RSFXDEVICEOPS;PREEMPTIVE_OS_DIRSVC_NETWORKOPS;PREEMPTIVE_OS_DSGETDCNAME
                        or  (wait_type >= N'PREEMPTIVE_OS_DTCOPS' and wait_type <= N'PREEMPTIVE_DTC_ABORT') --PREEMPTIVE_OS_DTCOPS;PREEMPTIVE_DTC_ABORT
                        or  (wait_type >= N'PREEMPTIVE_OS_CLOSEHANDLE' and wait_type <= N'PREEMPTIVE_OS_FINDFILE') --PREEMPTIVE_OS_CLOSEHANDLE;PREEMPTIVE_OS_COPYFILE;PREEMPTIVE_OS_CREATEDIRECTORY;PREEMPTIVE_OS_CREATEFILE;PREEMPTIVE_OS_DELETEFILE;PREEMPTIVE_OS_DEVICEIOCONTROL;PREEMPTIVE_OS_FINDFILE
                        or  (wait_type >= N'PREEMPTIVE_OS_GETCOMPRESSEDFILESIZE' and wait_type <= N'PREEMPTIVE_ODBCOPS') --PREEMPTIVE_OS_GETCOMPRESSEDFILESIZE;PREEMPTIVE_OS_GETDISKFREESPACE;PREEMPTIVE_OS_GETFILEATTRIBUTES;PREEMPTIVE_OS_GETFILESIZE;PREEMPTIVE_OS_GETLONGPATHNAME;PREEMPTIVE_OS_GETVOLUMEPATHNAME;PREEMPTIVE_OS_GETVOLUMENAMEFORVOLUMEMOUNTPOINT;PREEMPTIVE_OS_MOVEFILE;PREEMPTIVE_OS_OPENDIRECTORY;PREEMPTIVE_OS_REMOVEDIRECTORY;PREEMPTIVE_OS_SETENDOFFILE;PREEMPTIVE_OS_SETFILEPOINTER;PREEMPTIVE_OS_SETFILEVALIDDATA;PREEMPTIVE_OS_WRITEFILE;PREEMPTIVE_OS_WRITEFILEGATHER;PREEMPTIVE_OS_LIBRARYOPS;PREEMPTIVE_OS_FREELIBRARY;PREEMPTIVE_OS_GETPROCADDRESS;PREEMPTIVE_OS_LOADLIBRARY;PREEMPTIVE_OS_MESSAGEQUEUEOPS;PREEMPTIVE_ODBCOPS
                        or  (wait_type >= N'PREEMPTIVE_OS_DISCONNECTNAMEDPIPE' and wait_type <= N'PREEMPTIVE_CLOSEBACKUPMEDIA') --PREEMPTIVE_OS_DISCONNECTNAMEDPIPE;PREEMPTIVE_OS_PROCESSOPS;PREEMPTIVE_OS_SECURITYOPS;PREEMPTIVE_OS_SERVICEOPS;PREEMPTIVE_OS_SQLCLROPS;PREEMPTIVE_OS_WINSOCKOPS;PREEMPTIVE_OS_GETADDRINFO;PREEMPTIVE_OS_WSASETLASTERROR;PREEMPTIVE_OS_FORMATMESSAGE;PREEMPTIVE_OS_REPORTEVENT;PREEMPTIVE_OS_BACKUPREAD;PREEMPTIVE_OS_WAITFORSINGLEOBJECT;PREEMPTIVE_OS_QUERYREGISTRY;PREEMPTIVE_CLOSEBACKUPMEDIA
                        or wait_type = N'PREEMPTIVE_OS_AUTHENTICATIONOPS'
                        or wait_type = N'PREEMPTIVE_OS_FREECREDENTIALSHANDLE'
                        or wait_type = N'PREEMPTIVE_OS_AUTHORIZATIONOPS'
                        or wait_type = N'PREEMPTIVE_COM_COCREATEINSTANCE'
                        or wait_type = N'PREEMPTIVE_OS_NETVALIDATEPASSWORDPOLICY'
                        or wait_type = N'PREEMPTIVE_VSS_CREATESNAPSHOT'
                    )
                )
            )
        )
    ),
    add event sqlos.memory_broker_ring_buffer_recorded,
    add event sqlos.scheduler_monitor_deadlock_ring_buffer_recorded,
    add event sqlos.scheduler_monitor_system_health_ring_buffer_recorded,
    add event sqlos.scheduler_monitor_non_yielding_iocp_ring_buffer_recorded,
    add event sqlos.scheduler_monitor_non_yielding_rm_ring_buffer_recorded,
    add event sqlos.scheduler_monitor_stalled_dispatcher_ring_buffer_recorded,
    add event sqlos.memory_node_oom_ring_buffer_recorded
        (action (package0.callstack, sqlserver.session_id, sqlserver.sql_text, sqlserver.tsql_stack)),
    add event sqlserver.security_error_ring_buffer_recorded
        (set collect_call_stack = 1),
    add event sqlserver.connectivity_ring_buffer_recorded
        (set collect_call_stack = 1),
    add event sqlserver.sp_server_diagnostics_component_result
    (
        set collect_data = 1
        where
            sqlserver.is_system = 1 and
            component != 4 /* DiagnoseComponent::DCCI_EVENTS */
    ),
    add event sqlserver.job_object_ring_buffer_stats,
    add event sqlserver.nonyield_copiedstack_ring_buffer_recorded
    add target package0.event_file      -- Store events on disk (in the LOG folder of the instance)
    (
        set filename           = N'system_health.xel',
            max_file_size      = 5, /* MB */
            max_rollover_files = 4
    ),
    add target package0.ring_buffer     -- Store events in the ring buffer target
        (set max_memory = 4096, max_events_limit = 5000)
    with
    (
        MAX_DISPATCH_LATENCY = 120 SECONDS,
        startup_state = on
    )
    go
    if not exists (select * from sys.dm_xe_sessions where name = 'system_health')
        alter event session system_health on server state=start
    go


-- AlwaysOn default session: AlwaysOn_health
-- For now the session is off by default.
if exists(select * from sys.server_event_sessions where name='AlwaysOn_health')
	drop event session AlwaysOn_health on server;
go

create event session AlwaysOn_health on server 
    add event sqlserver.alwayson_ddl_executed,
    add event sqlserver.availability_group_lease_expired,
    add event sqlserver.availability_replica_automatic_failover_validation,
    add event sqlserver.availability_replica_manager_state_change,
    add event sqlserver.availability_replica_state,
    add event sqlserver.availability_replica_state_change,
    add event sqlserver.hadr_db_partner_set_sync_state,
    add event lock_redo_blocked,
    add event sqlserver.error_reported
    (
        where 
        (
            --endpoint issue:stopped
            [error_number]=(9691)  
            or [error_number]=(35204) 
        
            --endpoint issue:invalid ip address
            or [error_number]=(9693) 
            or [error_number]=(26024)
         
            --endpoint issue:encryption and handshake issue
            or [error_number]=(28047)

            --endpoint issue:port conflict
            or [error_number]=(26023)
            or [error_number]=(9692) 

            --endpoint issue:authentication failure
            or [error_number]=(28034) 
            or [error_number]=(28036) 
            or [error_number]=(28048) 
            or [error_number]=(28080) 
            or [error_number]=(28091) 

            --endpoint:listening
            or [error_number]=(26022) 

            --endpoint issue:generic message
            or [error_number]=(9642) 

            --alwayson connection timeout information
            or [error_number]=(35201) --new connection timeout
            or [error_number]=(35202) --connected
            or [error_number]=(35206) --existing connection timeout
            or [error_number]=(35207) --general connection issue message

            --alwayson listener state
            or [error_number]=(26069) --started 
            or [error_number]=(26070) --stopped

            --wsfc cluster issues
            or [error_number]>(41047) and [error_number]<(41056)

            --failover validation message
            or [error_number]=(41142) 

            --availability group resource failure
            or [error_number]=(41144) 
            
            --database replica role change	
            or [error_number]=(1480) 

            --automatic page repair event
            or [error_number]=(823) 
            or [error_number]=(824) 
            or [error_number]=(829) 
            
            --database replica suspended resumed
            or [error_number]=(35264) 
            or [error_number]=(35265)

            --alwayson alter ag rollback
            or [error_number]=(41188) 
            or [error_number]=(41189) 
        )
    ) 

    add target package0.event_file
    (
        set filename = N'AlwaysOn_health.xel',
            max_file_size = 5,
            max_rollover_files = 4
    )
    with 
    (
        max_memory = 4 mb,
        event_retention_mode = allow_single_event_loss,
        max_dispatch_latency = 30 seconds,
        max_event_size = 0 mb,
        memory_partition_mode = none,
        track_causality = off,
        startup_state=off
    );
go

-------------------------------------------------------------------------------------------------------
-- Extended Events Telemetry Session: telemetry_xevents
--
---------------------------------------------------------------------------------------------------------

if exists(select * from sys.server_event_sessions where name='telemetry_xevents')
    drop event session telemetry_xevents on server
go
-- The above code here is only for historical reasons. telemetry_xevents is not 
-- defined in $\Sql\Ntdbms\sqlceip\EventSessionDefinition.sql

declare @vdt varchar(99)
select  @vdt = convert(varchar,getdate(),113)
raiserror('Finishing at  %s',0,1,@vdt)
go

checkpoint
go



/**********************************************************************/
/* ProvisionAgentIdentity.sql                                            */
/*                                                                    */
/* This script is called during setup to provision SQL Server Agent   */
/* user group on SQL Server                                           */
/*                                                                    */
/* Copyright (c) Microsoft Corporation                                */
/* All Rights Reserved.                                               */
/*                                                                    */
/**********************************************************************/

-- push and enable option state for xp_instance_regread in case categories are off
DECLARE @advopt_old_value INT;
DECLARE @comp_old_value   INT;
SELECT @advopt_old_value=cast(value_in_use as int) from sys.configurations where name = 'show advanced options';
SELECT @comp_old_value=cast(value_in_use as int) from sys.configurations where name = 'Agent XPs';
EXEC sp_configure 'show advanced options', 1; 
RECONFIGURE WITH OVERRIDE;
EXEC sp_configure 'Agent XPs', 1;
RECONFIGURE WITH OVERRIDE; 

DECLARE @AgentLoginSID nvarchar(256) -- SID of service account or service principal representing agent service
DECLARE @AGTGroupSID nvarchar (256) -- Name of the agent group SID
EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\Setup', N'AGTService', @AgentLoginSID OUTPUT, no_output
EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\Setup', N'AGTGroup', @AGTGroupSID OUTPUT

-- restore option state
EXEC sp_configure 'Agent XPs', @comp_old_value;
RECONFIGURE WITH OVERRIDE; 
EXEC sp_configure 'show advanced options', @advopt_old_value; 
RECONFIGURE WITH OVERRIDE;

BEGIN
	IF (@AgentLoginSID IS NOT NULL)
	BEGIN
	    --we get the login name from SID. Ensures that we always get the login name with correct casing
	    --with respect to sql server collation.
	    DECLARE @AgentLoginSIDBinary varbinary(256)
	    DECLARE @AgentLoginName nvarchar(256)
	    
	    SELECT @AgentLoginSIDBinary = sid_binary(@AgentLoginSID);
	    SELECT @AgentLoginName = suser_sname(@AgentLoginSIDBinary);
	    
	    IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = @AgentLoginName)
		BEGIN
			-- Create a new login. Standalone install or S/K upgrade scenario
            DECLARE @cmd1 nvarchar(max)
            SELECT @cmd1 = 'CREATE LOGIN ' + QUOTENAME(@AgentLoginName) + ' FROM WINDOWS'
            print 'Running command to create login: ' + @cmd1
            EXEC (@cmd1)
            EXEC sys.sp_addsrvrolemember @AgentLoginName, N'sysadmin'
		END
	END
	
	IF (@AGTGroupSID IS NOT NULL)
	BEGIN
		--this is the case on upgrade. Basically, if a login already exists representing the AGTGroup,
		--if its current name on windows is different from what we have on SQL, we alter the name. Needed in Y->K upgrade,
		--in which we rename the agent local group
		DECLARE @AGTGroupSIDBinary varbinary(256) -- binary representation of AGTGroupSID
		DECLARE @AGTGroupName nvarchar (256) -- name of the group
		DECLARE @existing_name as sysname
		
		SELECT @AGTGroupSIDBinary = sid_binary(@AGTGroupSID);
		SELECT @AGTGroupName = suser_sname(@AGTGroupSIDBinary);
		SELECT @existing_name = name FROM sys.server_principals WHERE sid = suser_sid(@AGTGroupName);
		
		--note that if the login does not exist, we don't add it.
		IF (@existing_name IS NOT NULL)
		BEGIN
			IF (QUOTENAME(@existing_name) <> QUOTENAME(@AGTGroupName))
			BEGIN
				-- Drop this login. Not used anymore (only using SID).
				DECLARE @newcmd nvarchar(max)
				SELECT @newcmd = 'DROP LOGIN ' + QUOTENAME(@existing_name)
				print 'Running command to drop login: ' + @newcmd
				EXEC (@newcmd)
			END
		END	
	END
END
/**********************************************************************/
/* Sqlagent100_msdb_upgrade.SQL                                       */
/*                                                                    */
/* Upgrades 7.x, 8.x and 9.0 to 10.0 and drops all obsolete 8.x       */
/*                                                                    */
/*
** Copyright (c) Microsoft Corporation
** All Rights Reserved.
*/
/**********************************************************************/

PRINT '------------------------------------------------------------'
PRINT 'Starting execution of Sqlagent100_msdb_upgrade.SQL          '
PRINT 'Upgrades 7.x, 8.x and 9.0 to 10.0 and drops all obsolete 8.x'
PRINT '------------------------------------------------------------'

PRINT ''
PRINT '-------------------------------------------'
PRINT 'Starting execution of PREINSTMSDB100.SQL   '
PRINT '-------------------------------------------'

use msdb
go
-- Check that we're in msdb
IF (DB_NAME() <> N'msdb')
  RAISERROR('A problem was encountered accessing msdb. upgrade script terminating.', 20, 127) WITH LOG
go

CHECKPOINT
go

PRINT 'Data Collector pre-upgrade steps...';

-- Capture current state of DataCollector in temp table
-- and re-enable collector after script upgrade
-- #304027 - Data Collection is disabled when upgrading SQL Server 2008 
--           service pack upgrade or hotfix upgrade

PRINT 'Check if Data collector config table exists...'
IF (OBJECT_ID(N'[dbo].[syscollector_config_store_internal]', 'U') IS NOT NULL)
BEGIN
    IF (OBJECT_ID('tempdb..#data_collector_status', 'U') IS NOT NULL)
    BEGIN
        PRINT 'Dropping existing temp table tempdb..#data_collector_status...'
	DROP TABLE #data_collector_status
    END

    DECLARE @collector_enabled int;

    SELECT @collector_enabled = ISNULL(CONVERT(int, parameter_value),0)
    FROM [dbo].[syscollector_config_store_internal]
    WHERE parameter_name = 'CollectorEnabled'

    PRINT 'Data Collector state before upgrade: ' + CONVERT(varchar, @collector_enabled)

    SELECT @collector_enabled AS data_collector_old_status
    INTO #data_collector_status
	
END

-- Capture Collection set status before upgrade
PRINT 'sqlagent100_msdb_upgrade::Check if syscollector_collection_sets table exists...'
IF (OBJECT_ID(N'[dbo].[syscollector_collection_sets_internal]', 'U') IS NOT NULL)
BEGIN
	-- Capture Collection set status in temp table
	IF (OBJECT_ID('tempdb..#data_collector_collectionset_status', 'U') IS NOT NULL)
    BEGIN
        PRINT 'sqlagent100_msdb_upgrade::Dropping existing temp table tempdb..#data_collector_collectionset_status...'
		DROP TABLE #data_collector_collectionset_status
    END

	PRINT 'sqlagent100_msdb_upgrade::Capturing Collection set status in temp table...'
	SELECT collection_set_uid, name, is_running 
	INTO #data_collector_collectionset_status
	FROM [dbo].[syscollector_collection_sets_internal]
END
GO


-- If the user sets implicit_transactions on, some specprocs and DBCC commands will fail
-- Need to save the state and disable implicit_transactions
DECLARE @implicit_transactions_flag INT
DECLARE @is_implicit_transactions_set BIT

SELECT @is_implicit_transactions_set = CONVERT(BIT, value)
FROM fn_listextendedproperty(N'IMPLICIT_TRANSACTIONS', default, default, default, default, default, default);

IF (@is_implicit_transactions_set IS NOT NULL)
BEGIN
	EXEC sp_dropextendedproperty N'IMPLICIT_TRANSACTIONS'
END

SET @implicit_transactions_flag = 2
SET @is_implicit_transactions_set = @@options & @implicit_transactions_flag

EXEC sp_addextendedproperty 
@name = N'IMPLICIT_TRANSACTIONS', @value = @is_implicit_transactions_set

SET IMPLICIT_TRANSACTIONS OFF
GO

--set compatibily level to 100
sp_dbcmptlevel @dbname = 'msdb',  @new_cmptlevel = '100'

GO

-- Allow updates to system catalogs so that we can fully manipulate our system objects
EXECUTE master.dbo.sp_configure N'allow updates', 1
go
RECONFIGURE WITH OVERRIDE
go

/**************************************************************/
/* Record time of start of creates                            */
/**************************************************************/
SELECT start = getdate() INTO #InstMsdb
go

--preserve existing object permnission during upgrade
--create perms table
IF (NOT OBJECT_ID(N'dbo.upgrade_perms', 'U') IS NULL)
BEGIN
  DROP TABLE dbo.upgrade_perms
END
-- upgrade_perms is filled with current permission of objects in MSDB
-- the structure of table is:
-- state_desc = GRANT|DENY
-- permission_name = SELECT|EXECUTE|UPDATE ...
-- object_name = grantor name
-- grantee_name = grantee name

CREATE TABLE dbo.upgrade_perms(state_desc nvarchar(60), permission_name sysname, object_name sysname, grantee_name sysname)
CREATE INDEX indnc ON dbo.upgrade_perms(object_name)
DECLARE @state_desc			  nvarchar(60)
DECLARE @permission_name	sysname
DECLARE @object_name		  sysname
DECLARE @grantee_name		  sysname 

DECLARE perms_cursor CURSOR LOCAL FOR 
	SELECT state_desc, permission_name, OBJECT_NAME(major_id), USER_NAME(grantee_principal_id) from msdb.sys.database_permissions 
  WHERE state_desc IS NOT NULL AND 
  permission_name IS NOT NULL AND 
  OBJECT_NAME(major_id) IS NOT NULL AND 
  USER_NAME(grantee_principal_id) IS NOT NULL 	

OPEN perms_cursor
   FETCH NEXT FROM perms_cursor INTO @state_desc, @permission_name, @object_name, @grantee_name
   WHILE (@@fetch_status = 0)
   BEGIN
      INSERT dbo.upgrade_perms(state_desc, permission_name, object_name, grantee_name)
      VALUES(@state_desc, @permission_name, @object_name, @grantee_name) 
      FETCH NEXT FROM perms_cursor INTO @state_desc, @permission_name, @object_name, @grantee_name
   END
DEALLOCATE perms_cursor
go


------------------------VIEWS UPGRADE---------------------------------------


------------------------TABLE UPGRADE---------------------------------------
--create an populate sysoriginatingservers
use msdb
go

IF (NOT EXISTS (SELECT *   --just a safe belt, this table shouldn't be in 8.x
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysoriginatingservers')
              AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysoriginatingservers...'
  CREATE TABLE dbo.sysoriginatingservers
  (
    -- There is only a single MSX server record in this table (originating_server_id = 1)
    -- 0 is generated by sysoriginatingservers_view and indicates the local server
    originating_server_id	INT 		CONSTRAINT CK_originating_server_id_MustBe_1 CHECK (originating_server_id = 1) 
									    DEFAULT (1) UNIQUE CLUSTERED,				
    originating_server    sysname		NOT NULL UNIQUE NONCLUSTERED,
    --Mark this record as a MSX server entry
    master_server         bit			CONSTRAINT CK_master_server_MustBe_1 CHECK (master_server = 1) 
									    DEFAULT (1)	
  )
END
go

IF (NOT EXISTS (SELECT t.name FROM msdb.sys.all_columns c JOIN msdb.sys.all_objects t 
                ON c.object_id = t.object_id 
                WHERE c.name = 'originating_server_id' and t.name = 'sysjobs' and t.type = 'U'))
BEGIN                
  PRINT ''
  PRINT 'Adding column originating_server_id to table sysjobs...'
  --add new column 9.0 originating_server_id
  ALTER TABLE sysjobs WITH NOCHECK 
  ADD originating_server_id INT NULL
END
go

DECLARE @MSXServerName        sysname
DECLARE @LocalServerName      sysname
DECLARE @UpdateOrgServerTSQL  nvarchar(MAX)

SELECT @LocalServerName = UPPER(CONVERT(sysname, SERVERPROPERTY('servername')))

EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                        N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                        N'MSXServerName',
                                        @MSXServerName OUTPUT,
                                        N'no_output'

SELECT @MSXServerName = LTRIM(RTRIM(UPPER(@MSXServerName)))
IF (@MSXServerName = '') SELECT @MSXServerName = NULL

IF (@MSXServerName IS NOT NULL)
BEGIN
  IF (NOT EXISTS( SELECT * FROM dbo.sysoriginatingservers 
                  WHERE originating_server_id = 1 AND originating_server = @MSXServerName
                  AND master_server = 1))
    BEGIN
      PRINT ''
      PRINT 'Populate table sysoriginatingservers...'
      INSERT INTO sysoriginatingservers( originating_server_id, originating_server,  master_server) 
      VALUES(1, @MSXServerName, 1)
    END  
END

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysjobs')
              AND (type = 'U')))
BEGIN
  IF (EXISTS (SELECT t.name FROM msdb.sys.all_columns c JOIN msdb.sys.all_objects t 
                  ON c.object_id = t.object_id 
                  WHERE c.name = 'originating_server' and t.name = 'sysjobs' and t.type = 'U'))
  BEGIN                

    PRINT ''
    PRINT 'Populate new column originating_server_id of table sysjobs...'
    --set this column based on the value of 8.0 only column originating_server 
	  --if MSX server is NULL we come up with server name that cannot exit, a generated GUID
    SELECT @UpdateOrgServerTSQL = 
    '
    UPDATE sysjobs SET originating_server_id = 
    CASE UPPER(originating_server) 
      WHEN ''' + @LocalServerName + ''' THEN 0 --local_server_id 
      WHEN ''' + ISNULL(@MSXServerName, CONVERT(sysname, NEWID()))   + ''' THEN 1 --msx_server_id 
      ELSE 0 --7.0 (local) or bad data 
    END
    '
    EXECUTE( @UpdateOrgServerTSQL)
    
    PRINT ''
    PRINT 'Drop column originating_server of table sysjobs...'
    --drop 8.0 column originating_server
    DROP INDEX sysjobs.nc2
    ALTER TABLE sysjobs DROP COLUMN originating_server
  END
END  
go

--normalize 8.0 sysjobschedules into 9.0 sysschedules and sysjobschedules
IF NOT EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysschedules')
              AND (type = 'U'))
BEGIN
--create first sysschedules table  
	PRINT ''
	PRINT 'Creating table sysschedules...'

	CREATE TABLE dbo.sysschedules
	(
	schedule_id            INT IDENTITY     PRIMARY KEY CLUSTERED,
	schedule_uid           UNIQUEIDENTIFIER NOT NULL,
	originating_server_id  INT              NOT NULL, 
	name                   sysname          NOT NULL,
	owner_sid				       varbinary(85)	  NOT NULL, 
	enabled                INT              NOT NULL,
	freq_type              INT              NOT NULL,
	freq_interval          INT              NOT NULL,
	freq_subday_type       INT              NOT NULL,
	freq_subday_interval   INT              NOT NULL,
	freq_relative_interval INT              NOT NULL,
	freq_recurrence_factor INT              NOT NULL,
	active_start_date      INT              NOT NULL,
	active_end_date        INT              NOT NULL,
	active_start_time      INT              NOT NULL,
	active_end_time        INT              NOT NULL,
	date_created           DATETIME         NOT NULL  DEFAULT (GETDATE()),
	date_modified          DATETIME         NOT NULL  DEFAULT (GETDATE()),
	version_number         INT              NOT NULL  DEFAULT (1)
	)
-- CREATE UNIQUE CLUSTERED INDEX clust ON sysschedules(job_id, name)
-- CREATE UNIQUE NONCLUSTERED INDEX nc1 ON sysschedules(schedule_id)
END
go
--a system object cannot be renamed, turn off marking system object trace to alloe renaming of temp_sysjobschedules
dbcc traceoff(1717, -1) 

go
-- create temp cross 9.0 table temp_sysjobschedules
IF EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'temp_sysjobschedules')
              AND (type = 'U'))
BEGIN
  DROP TABLE dbo.temp_sysjobschedules
END
go

PRINT ''
PRINT 'Creating table temp_sysjobschedules'	
CREATE TABLE dbo.temp_sysjobschedules
	(
	schedule_id		         INT					    REFERENCES dbo.sysschedules(schedule_id),
	job_id                 UNIQUEIDENTIFIER REFERENCES dbo.sysjobs(job_id),
	next_run_date          INT              NOT NULL   DEFAULT 0,
	next_run_time          INT              NOT NULL   DEFAULT 0
	)
go

DECLARE @dynamicSQL nvarchar(4000)
IF (EXISTS (SELECT t.name FROM msdb.sys.all_columns c JOIN msdb.sys.all_objects t 
                ON c.object_id = t.object_id 
                WHERE c.name = 'name' and t.name = 'sysjobschedules' and t.type = 'U'))
BEGIN	
  PRINT ''
  PRINT 'Moving schedule data ...'	
	SET IDENTITY_INSERT dbo.sysschedules ON
  SELECT @dynamicSQL =
  '
	INSERT INTO dbo.sysschedules
	(
		schedule_id            ,
		schedule_uid           ,
		originating_server_id  ,
		name                   ,
		owner_sid				       ,
		enabled                ,
		freq_type              ,
		freq_interval          ,
		freq_subday_type       ,
		freq_subday_interval   ,
		freq_relative_interval ,
		freq_recurrence_factor ,
		active_start_date      ,
		active_end_date        ,
		active_start_time      ,
		active_end_time        ,
		date_created           
	)
	SELECT
		js.schedule_id            ,
		NEWID()                   ,
		0                         , --local server. TO DO make sure local server = 0
		js.name                   ,      
		j.owner_sid               ,      
		js.enabled                   ,
		js.freq_type              ,
		js.freq_interval          ,
		js.freq_subday_type       ,
		js.freq_subday_interval   ,
		js.freq_relative_interval ,
		js.freq_recurrence_factor ,
		js.active_start_date      ,
		js.active_end_date        ,
		js.active_start_time      ,
		js.active_end_time        ,
		js.date_created           
	FROM
  dbo.sysjobs j JOIN dbo.sysjobschedules js ON j.job_id = js.job_id
	  
	INSERT INTO dbo.temp_sysjobschedules
	(
		schedule_id		        ,
		job_id                ,
		next_run_date         ,
		next_run_time         
	)
	SELECT
		js.schedule_id          ,
		js.job_id               ,
		js.next_run_date        ,
		js.next_run_time          
	FROM dbo.sysjobs j JOIN dbo.sysjobschedules js ON j.job_id = js.job_id
'
  EXECUTE (@dynamicSQL)

	SET IDENTITY_INSERT dbo.sysschedules OFF

	IF (EXISTS (SELECT *
				FROM msdb.dbo.syscolumns
				WHERE (id = OBJECT_ID(N'sysjobschedules'))
				AND (name = N'date_created')
				AND (cdefault <> 0)))
	  EXECUTE sp_unbindefault N'sysjobschedules.date_created'

	DROP TABLE dbo.sysjobschedules
	EXECUTE sp_rename 'temp_sysjobschedules', 'sysjobschedules'
	EXECUTE (N'CREATE UNIQUE CLUSTERED INDEX clust ON dbo.sysjobschedules(job_id, schedule_id)')
	PRINT ''
	PRINT 'Updating schedules done'
END	
go

--just a safe belt
IF EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'temp_sysjobschedules')
              AND (type = 'U'))
BEGIN
  DROP TABLE dbo.temp_sysjobschedules
END
go

--alter only if command column is not already nvarchar(max)
IF (NOT EXISTS (SELECT c.* FROM msdb.sys.all_columns c JOIN msdb.sys.all_objects t 
                ON c.object_id = t.object_id 
                WHERE (c.name = 'proxy_id' OR c.name = 'step_uid') AND t.name = 'sysjobsteps' AND t.type = 'U'))
BEGIN
  PRINT ''
  PRINT 'Adding proxy_id, step_uid  columns to sysjobsteps table'  
  ALTER TABLE sysjobsteps ADD
        proxy_id INT NULL,
        step_uid UNIQUEIDENTIFIER NULL
END
go

--rename DTS subsystem to SSIS
IF (OBJECT_ID('dbo.sysjobsteps', 'U') IS NOT NULL)
BEGIN
    UPDATE dbo.sysjobsteps SET subsystem = N'SSIS' WHERE UPPER(subsystem collate SQL_Latin1_General_CP1_CS_AS) = N'DTS'
END
go

--to be safer populate sysjobsteps with guids, otherwise step table logs are not possible
EXECUTE (N'UPDATE sysjobsteps SET step_uid = NEWID() WHERE step_uid IS NULL')
go

--if there is no index for step_uid, create it, so step table logs can reference it
IF NOT EXISTS (SELECT name FROM sys.indexes WHERE name = N'nc2' and object_id = object_id(N'[dbo].[sysjobsteps]') )
BEGIN
  EXECUTE (N'CREATE UNIQUE NONCLUSTERED INDEX nc2 ON sysjobsteps(step_uid)')
END
go


--alter sysdownloadlist table
PRINT ''
PRINT 'Alter table sysdownloadlist...'
ALTER TABLE sysdownloadlist ALTER COLUMN source_server sysname
ALTER TABLE sysdownloadlist ALTER COLUMN target_server sysname
go

--alter sysjobhistory  table
PRINT ''
PRINT 'Alter table sysjobhistory...'
ALTER TABLE sysjobhistory ALTER COLUMN server sysname
go

IF EXISTS (SELECT * FROM msdb.dbo.sysobjects WHERE name = N'sysjobhistory' and type = 'U')
BEGIN
  UPDATE  msdb.dbo.sysjobhistory
  SET     server = CONVERT(sysname, SERVERPROPERTY('servername'))
  WHERE   UPPER(server) = '(LOCAL)'
END
go

--alter systargetservers table
PRINT ''
PRINT 'Alter table systargetservers...'
ALTER TABLE systargetservers  ALTER COLUMN server_name sysname
go

--drop syssubsystems table if it exists( to support update from IDW(n) to RTM)
--it will recreated later with the updated schema
IF (OBJECT_ID('dbo.syssubsystems', 'U') IS NOT NULL)
BEGIN
  DROP TABLE dbo.syssubsystems
END

--drop column logshipping from sysdbmaintplans table
--alter only if command column is not already nvarchar(max)
IF (EXISTS (SELECT c.* FROM msdb.sys.all_columns c JOIN msdb.sys.all_objects t 
                ON c.object_id = t.object_id 
                WHERE (c.name = 'logshipping') AND t.name = 'sysdbmaintplans' AND t.type = 'U'))
BEGIN
  ALTER TABLE sysdbmaintplans DROP COLUMN logshipping
END
go

--make sure 
--it will recreated later with the updated schema
IF (OBJECT_ID('dbo.sysjobstepslogs', 'U') IS NOT NULL)
BEGIN
  ALTER TABLE dbo.sysjobstepslogs  ALTER COLUMN log_size bigint
END

-- sysproxylogin upgrade
-- sysproxylogin upgrade
BEGIN TRY
	IF EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='principal_id' and id =
			(SELECT OBJECT_ID(N'dbo.sysproxylogin', 'U')))
	BEGIN
		-- convert data from principal_id to sid
		exec sp_executesql N'
		DECLARE @sid varbinary(85)
		DECLARE @principal_id int 

		DECLARE principal_sid_cursor CURSOR LOCAL 
		FOR
		SELECT distinct principal_id
		FROM dbo.sysproxylogin WHERE (sid IS NULL) AND (flags = 2)

		OPEN principal_sid_cursor 
		FETCH NEXT FROM principal_sid_cursor INTO @principal_id
		WHILE (@@fetch_status = 0)
		BEGIN
		    SELECT @sid=sid FROM msdb.sys.database_principals WHERE principal_id=@principal_id

			IF @sid IS NOT NULL -- principal_id is valid
			BEGIN
				UPDATE dbo.sysproxylogin
				SET sid = @sid
				WHERE principal_id = @principal_id
			END
			ELSE
			BEGIN
				DELETE FROM dbo.sysproxylogin
				WHERE principal_id = @principal_id
			END
			FETCH NEXT FROM principal_sid_cursor INTO @principal_id
		END
		CLOSE principal_sid_cursor 
		DEALLOCATE principal_sid_cursor
		'

		-- remove obsolete column
		DROP INDEX sysproxylogin.clust
		ALTER TABLE dbo.sysproxylogin DROP COLUMN principal_id
        CREATE UNIQUE CLUSTERED    INDEX clust ON sysproxylogin(proxy_id, sid, flags)
	END
END TRY
BEGIN CATCH
	print 'There was a problem upgrading sysproxylogin table.'
	print 'Unable to map existing principal_id values to sid column'
	print 'Error State: ' + convert(varchar(10),ERROR_STATE() )
END CATCH

GO

/**************************************************************/
/* Mark system objects                                        */
/**************************************************************/
declare  @start datetime
		,@name  sysname
select @start = start from #InstMsdb
declare newsysobjs cursor for select name from sys.objects where schema_id = 1 and create_date >= @start
open newsysobjs
fetch next from newsysobjs into @name
while @@fetch_status = 0
begin
	Exec sp_MS_marksystemobject @name
	fetch next from newsysobjs into @name
end
deallocate newsysobjs
drop table #InstMsdb
go

EXECUTE master.dbo.sp_configure N'allow updates', 0
go
RECONFIGURE WITH OVERRIDE
go


/**************************************************************/
/* DMF Pre-upgrade steps                                      */
/**************************************************************/

PRINT 'DMF pre-upgrade steps...'
--
-- >>> CTP5 -> CTP6 Upgrade
--
-- The check is based on presense of ObjectSet tables
--  We also check if principal DMF objects is there
--  if it's not we either upgrade from Yukon
--  or there is nothing to upgrade anyway
IF (OBJECT_ID('[dbo].[syspolicy_policies_internal]', 'U') IS NOT NULL)
	AND (OBJECT_ID('[dbo].[syspolicy_object_sets_internal]', 'U') IS NULL)
BEGIN
	BEGIN TRY
		-- Open transaction - we don't want to delete tables if we don't have a copy of data
		BEGIN TRANSACTION
		-- Create upgrade marker
		CREATE TABLE dbo.dmf_upgrade (id int)
	
		-- STORE DATA
		SELECT * INTO msdb.dbo.tmp_syspolicy_target_sets_internal FROM syspolicy_target_sets_internal 
		SELECT * INTO msdb.dbo.tmp_syspolicy_target_set_levels_internal FROM syspolicy_target_set_levels_internal 
		SELECT * INTO msdb.dbo.tmp_syspolicy_policies_internal FROM syspolicy_policies_internal 
		SELECT * INTO msdb.dbo.tmp_syspolicy_system_health_state_internal FROM syspolicy_system_health_state_internal 
		SELECT * INTO msdb.dbo.tmp_syspolicy_policy_execution_history_internal FROM syspolicy_policy_execution_history_internal 
		SELECT * INTO msdb.dbo.tmp_syspolicy_policy_execution_history_details_internal FROM syspolicy_policy_execution_history_details_internal 
	
		-- Delete policies to invoke the trigger that removes dependent objects (jobs, if any).
		DELETE [dbo].[syspolicy_policies_internal]
		-- T-SQL Policy jobs are now gone, we must nullify job_id to not violate the foreign key constraint
		UPDATE msdb.dbo.tmp_syspolicy_policies_internal SET job_id = NULL
		
		-- DROP TABLES (WITH DEPENDENCIES)	
		IF (OBJECT_ID('[dbo].[syspolicy_target_set_levels_internal]', 'U') IS NOT NULL) DROP TABLE [dbo].[syspolicy_target_set_levels_internal]
		IF (OBJECT_ID('[dbo].[syspolicy_target_sets_internal]', 'U') IS NOT NULL) DROP TABLE [dbo].[syspolicy_target_sets_internal]
		IF (OBJECT_ID('[dbo].[syspolicy_policy_execution_history_details_internal]', 'U') IS NOT NULL) DROP TABLE [dbo].[syspolicy_policy_execution_history_details_internal]
		IF (OBJECT_ID('[dbo].[syspolicy_policy_execution_history_internal]', 'U') IS NOT NULL) DROP TABLE [dbo].[syspolicy_policy_execution_history_internal]
		IF (OBJECT_ID('[dbo].[syspolicy_system_health_state_internal]', 'U') IS NOT NULL) DROP TABLE [dbo].[syspolicy_system_health_state_internal]
		IF (OBJECT_ID('[dbo].[syspolicy_policies]', 'V') IS NOT NULL) DROP VIEW [dbo].[syspolicy_policies]
		IF (OBJECT_ID('[dbo].[syspolicy_policies_internal]', 'U') IS NOT NULL) DROP TABLE [dbo].[syspolicy_policies_internal]
	
		COMMIT TRANSACTION
	END TRY
	BEGIN CATCH
            IF (XACT_STATE() <> 0)
            BEGIN
                ROLLBACK TRANSACTION;
            END

            DECLARE @ErrorMessage   NVARCHAR(4000);
            DECLARE @ErrorSeverity  INT;
            DECLARE @ErrorState     INT;
            DECLARE @ErrorNumber    INT;
            DECLARE @ErrorLine      INT;
            DECLARE @ErrorProcedure NVARCHAR(200);
            SELECT @ErrorLine = ERROR_LINE(),
                   @ErrorSeverity = ERROR_SEVERITY(),
                   @ErrorState = ERROR_STATE(),
                   @ErrorNumber = ERROR_NUMBER(),
                   @ErrorMessage = ERROR_MESSAGE(),
                   @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
            PRINT 'ERROR: DMF CTP5 to CTP6 upgrade tasks failed' 
            RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
	END CATCH;
END
GO
--
-- <<< CTP5 - CTP6 Upgrade
--

/**************************************************************/
/* End of DMF Pre-upgrade steps                               */
/**************************************************************/


/**********************************************************************/
/* DC Pre-upgrade steps                                               */
/**********************************************************************/
GO
PRINT 'DC pre-upgrade steps...';

--
-- CTP6->CTP6 Refresh
--

-- Drop the parent_log_id->log_id self reference
IF ((OBJECT_ID ('dbo.syscollector_execution_log_internal') IS NOT NULL) 
	AND (OBJECT_ID('dbo.FK_syscollector_execution_log_parent_log_id', 'F') IS NOT NULL))
BEGIN
    PRINT 'Dropping [FK_syscollector_execution_log_parent_log_id]';
    ALTER TABLE [dbo].[syscollector_execution_log_internal] DROP CONSTRAINT [FK_syscollector_execution_log_parent_log_id];
END

--
-- >>> CTP5 -> CTP6 Upgrade
--
-- Change int log_id columns to bigint 
IF (OBJECT_ID ('dbo.syscollector_execution_log_internal') IS NOT NULL) AND EXISTS (
    SELECT * 
    FROM sys.columns AS c
    INNER JOIN sys.types AS t ON c.system_type_id = t.system_type_id
    WHERE [object_id] = OBJECT_ID ('dbo.syscollector_execution_log_internal')
        AND c.name = 'log_id' AND t.name = 'int'
    )
BEGIN
    BEGIN TRY
        PRINT 'Starting log_id int -> bigint conversion'
        BEGIN TRANSACTION PreInstMsdb100_DCUpgrade
        -- Drop PK/FK constaints referencing log_id columns so we can change the data types of these columns
        PRINT 'Dropping [FK_syscollector_execution_stats_log_id]';
        ALTER TABLE [dbo].[syscollector_execution_stats_internal] DROP CONSTRAINT [FK_syscollector_execution_stats_log_id];
        PRINT 'Dropping [PK_syscollector_execution_stats]';
        ALTER TABLE [dbo].[syscollector_execution_stats_internal] DROP CONSTRAINT [PK_syscollector_execution_stats]; 
        PRINT 'Dropping [PK_syscollector_execution_log]';
        ALTER TABLE [dbo].[syscollector_execution_log_internal] DROP CONSTRAINT [PK_syscollector_execution_log];
        
        -- Truncate the DC log table to avoid causing unnecessary delays during CTP upgrade
        PRINT 'Truncating [syscollector_execution_log_internal]...';
        TRUNCATE TABLE [dbo].[syscollector_execution_log_internal];
        -- log_id values stored in syscollector_execution_stats will no longer be valid after we have truncated the log table
        PRINT 'Truncating [syscollector_execution_stats_internal]...';
        TRUNCATE TABLE [dbo].[syscollector_execution_stats_internal];
        
        -- Change the data type of all log_id columns
        PRINT 'Changing log_id column datatypes...';
        PRINT '   syscollector_execution_stats_internal.log_id';
        ALTER TABLE [dbo].[syscollector_execution_stats_internal] ALTER COLUMN [log_id] bigint NOT NULL;
        PRINT '   syscollector_execution_log_internal.log_id';
        ALTER TABLE [dbo].[syscollector_execution_log_internal] ALTER COLUMN [log_id] bigint NOT NULL; 
        PRINT '   syscollector_execution_log_internal.parent_log_id';
        ALTER TABLE [dbo].[syscollector_execution_log_internal] ALTER COLUMN [parent_log_id] bigint NULL;
    END TRY
    BEGIN CATCH
        IF (XACT_STATE() <> 0)
        BEGIN
            ROLLBACK TRANSACTION;
        END

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
        PRINT 'ERROR: DC CTP5 to CTP6 upgrade tasks failed' 
        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
    END CATCH;
END
GO

-- Re-create the PK/FK constraints that we dropped in order to modify column datatypes
IF (OBJECT_ID ('dbo.syscollector_execution_log_internal') IS NOT NULL) AND NOT EXISTS (SELECT * FROM sys.objects WHERE name = 'PK_syscollector_execution_log' AND [schema_id] = SCHEMA_ID ('dbo'))
BEGIN
    BEGIN TRY
        PRINT 'Creating PK_syscollector_execution_log...';
        ALTER TABLE [dbo].[syscollector_execution_log_internal] ADD CONSTRAINT [PK_syscollector_execution_log] 
        PRIMARY KEY CLUSTERED (log_id ASC);
    END TRY
    BEGIN CATCH
        -- If we're still in the transaction started by the prior batch, roll it back so that it's 
        -- as if we never dropped any constraints
        PRINT 'ERROR: DC CTP5 to CTP6 upgrade tasks failed to create [PK_syscollector_execution_log] ' 
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
    END CATCH;
END
GO
IF (OBJECT_ID ('dbo.syscollector_execution_stats_internal') IS NOT NULL) AND NOT EXISTS (SELECT * FROM sys.objects WHERE name = 'PK_syscollector_execution_stats' AND [schema_id] = SCHEMA_ID ('dbo'))
BEGIN
    BEGIN TRY
        PRINT 'Creating PK_syscollector_execution_stats...';
        ALTER TABLE [dbo].[syscollector_execution_stats_internal] ADD CONSTRAINT [PK_syscollector_execution_stats] 
        PRIMARY KEY CLUSTERED (log_id ASC, task_name ASC, log_time DESC);
    END TRY
    BEGIN CATCH
        PRINT 'ERROR: DC CTP5 to CTP6 upgrade tasks failed to create [PK_syscollector_execution_stats] ' 
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
    END CATCH;
END
GO
IF (OBJECT_ID ('dbo.syscollector_execution_stats_internal') IS NOT NULL) AND NOT EXISTS (SELECT * FROM sys.objects WHERE name = 'FK_syscollector_execution_stats_log_id' AND [schema_id] = SCHEMA_ID ('dbo'))
BEGIN
    BEGIN TRY
        PRINT 'Creating FK_syscollector_execution_stats_log_id...';
        ALTER TABLE [dbo].[syscollector_execution_stats_internal] ADD CONSTRAINT [FK_syscollector_execution_stats_log_id] 
        FOREIGN KEY (log_id) REFERENCES [dbo].[syscollector_execution_log_internal] (log_id)
            ON DELETE CASCADE;
    END TRY
    BEGIN CATCH
        PRINT 'ERROR: DC CTP5 to CTP6 upgrade tasks failed to create [FK_syscollector_execution_stats_log_id] ' 
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
    END CATCH;
END
GO

IF (@@TRANCOUNT > 0) COMMIT TRANSACTION;
GO
IF (OBJECT_ID ('dbo.syscollector_execution_log_internal') IS NOT NULL)
BEGIN
    EXEC sp_refreshview 'dbo.syscollector_execution_log'
    EXEC sp_refreshview 'dbo.syscollector_execution_stats'
END
GO
--
-- <<< CTP5 - CTP6 Upgrade
--

--
-- >>> Delete auto-generated T-SQL packages stored in msdb. 
--

IF OBJECT_ID ('dbo.syscollector_tsql_query_collector') IS NOT NULL
BEGIN
    RAISERROR ('Deleting cached auto-generated T-SQL Data Collection packages from msdb...', 0, 1) WITH NOWAIT

    /*
    ** We have a row in [syscollector_tsql_query_collector] for each T-SQL collection item, and 
    ** each item has a collection and an upload package. There's a DELETE trigger on this table 
    ** that will take care of deleting the packages from sysssispackages for us.  The packages 
    ** will be re-generated automatically the next time their parent collection set's collection 
    ** package runs. 
    */ 
    DELETE FROM syscollector_tsql_query_collector
END
--
-- <<< Delete auto-generated T-SQL packages stored in msdb. 
--
GO

PRINT 'End of DC pre-upgrade steps.';

/**********************************************************************/
/* End of DC Pre-upgrade steps                                        */
/**********************************************************************/
GO


PRINT ''
PRINT '----------------------------------------'
PRINT 'Execution of PREINSTMSDB100.SQL complete'
PRINT '----------------------------------------'
go

/**********************************************************************/
/* INSTMSDB.SQL                                                       */
/*                                                                    */
/* Installs the tables, triggers and stored procedures necessary for  */
/* supporting local (and multi-server) jobs, alerts, operators, and   */
/* backup history.  These objects are used by SQL SMO, SQL Management */
/* Studio, SQLServerAgent, DMF and Data Collector                     */
/*                                                                    */
/* Also contains SSIS (Data Transformation Services) tables and       */
/* stored procedures for local SQL Server storage of SSIS Packages.   */
/*                                                                    */
/*                                                                    */
/* Copyright Microsoft, Inc. 1994 - 2008                              */
/* All Rights Reserved.                                               */
/*                                                                    */
/**********************************************************************/

PRINT '----------------------------------'
PRINT 'Starting execution of INSTMSDB.SQL'
PRINT '----------------------------------'
go


--this version of instmsdb should be executed only against 10.0 and later servers
IF (@@microsoftversion / 0x01000000) < 10
BEGIN
      RAISERROR('This version of instmsdb.sql should only be executed against 10.0 or later servers.', 20, 127) WITH LOG 
END
go

-- disable the event collection for policies while running this script
IF EXISTS (SELECT * FROM sys.server_triggers WHERE name = N'syspolicy_server_trigger')
    DISABLE TRIGGER [syspolicy_server_trigger] ON ALL SERVER 
GO

IF EXISTS (SELECT * FROM sys.service_queues where name = N'syspolicy_event_queue')
    ALTER QUEUE [syspolicy_event_queue] WITH ACTIVATION (STATUS = OFF)
GO

/*********************************************************************/
/* Create auxilary procedure to enable OBD (Off By Default component */
/*********************************************************************/
CREATE PROCEDURE #sp_enable_component     
   @comp_name     sysname, 
   @advopt_old_value    INT OUT, 
   @comp_old_value   INT OUT 
AS
   BEGIN
   SELECT @advopt_old_value=cast(value_in_use as int) from sys.configurations where name = 'show advanced options';
   SELECT @comp_old_value=cast(value_in_use as int) from sys.configurations where name = @comp_name; 
   EXEC sp_configure 'show advanced options',1;
   RECONFIGURE WITH OVERRIDE;
   EXEC sp_configure @comp_name, 1; 
   RECONFIGURE WITH OVERRIDE;
   END
go

CREATE PROCEDURE #sp_restore_component_state 
   @comp_name     sysname, 
   @advopt_old_value    INT, 
   @comp_old_value   INT 
AS
   BEGIN
   EXEC sp_configure @comp_name, @comp_old_value; 
   RECONFIGURE WITH OVERRIDE;
   EXEC sp_configure 'show advanced options',@advopt_old_value;
   RECONFIGURE WITH OVERRIDE;
   END
go

-- Explicitly set the options that the server stores with the object in sysobjects.status
-- so that it doesn't matter if the script is run using a DBLib or ODBC based client.
SET QUOTED_IDENTIFIER OFF -- We don't use quoted identifiers
SET ANSI_NULLS ON         -- We don't want (NULL = NULL) == TRUE
go
SET ANSI_PADDING ON       -- Set so that trailing zeros aren't trimmed off sysjobs.owner_login_sid
go

-- Allow updates to system catalogs so that all our SP's inherit full DML capability on
-- system objects and so that we can exercise full DDL control on our system objects
EXECUTE master.dbo.sp_configure N'allow updates', 1
go
RECONFIGURE WITH OVERRIDE
go

/**************************************************************/
/*                                                            */
/*      D  A  T  A  B  A  S  E    C  R  E  A  T  I  O  N      */
/*                                                            */
/**************************************************************/

IF (NOT EXISTS (SELECT name
                FROM master.dbo.sysdatabases
                WHERE (name = N'msdb')))
BEGIN
  PRINT 'Creating the msdb database...'
END
go

USE master
go

SET NOCOUNT ON

-- NOTE: It is important that this script can be re-run WITHOUT causing loss of data, hence
--       we only create the database if it missing (if the database already exists we test
--       that it has enough free space and if not we expand both the device and the database).
DECLARE @model_db_size    INT
DECLARE @msdb_db_size     INT
DECLARE @sz_msdb_db_size  VARCHAR(10)
DECLARE @device_directory NVARCHAR(260)
DECLARE @page_size        INT
DECLARE @size             INT
DECLARE @free_db_space    FLOAT

SELECT @page_size = 8

IF (NOT EXISTS (SELECT name
                FROM master.dbo.sysdatabases
                WHERE (name = N'msdb')))
BEGIN
  -- Make sure that we create [the data portion of] MSDB to be at least as large as
  -- the MODEL database
  SELECT @model_db_size = (SUM(size) * @page_size)
  FROM model.dbo.sysfiles

  IF (@model_db_size > 3072) -- 3 is the minimum required size for MSDB (in megabytes)
    SELECT @msdb_db_size = @model_db_size
  ELSE
    SELECT @msdb_db_size = 3072

  SELECT @device_directory = REVERSE(SUBSTRING(REVERSE(physical_name), CHARINDEX(N'\', REVERSE(physical_name)), LEN(physical_name))) 
  FROM sys.master_files 
  WHERE [database_id] = 1
  AND [file_id] = 1
  AND [data_space_id] = 1

  -- Drop any existing MSDBData / MSDBLog file(s)
  DECLARE   @advopt_old_value    INT 
  DECLARE   @comp_old_value   INT
  EXECUTE #sp_enable_component 'xp_cmdshell', @advopt_old_value out, @comp_old_value out
  EXECUTE(N'EXECUTE master.dbo.xp_cmdshell N''DEL ' + @device_directory + N'MSDBData.mdf'', no_output')
  EXECUTE(N'EXECUTE master.dbo.xp_cmdshell N''DEL ' + @device_directory + N'MSDBLog.ldf'', no_output')
  EXECUTE #sp_restore_component_state 'xp_cmdshell', @advopt_old_value, @comp_old_value

  -- Create the database
  PRINT ''
  PRINT 'Creating MSDB database...'
  SELECT @sz_msdb_db_size = RTRIM(LTRIM(CONVERT(VARCHAR, @msdb_db_size)))
  EXECUTE (N'CREATE DATABASE msdb ON (NAME = N''MSDBData'', FILENAME = N''' + @device_directory + N'MSDBData.mdf'', SIZE = ' + @sz_msdb_db_size + N'KB, MAXSIZE = UNLIMITED, FILEGROWTH = 10%)
                         LOG ON (NAME = N''MSDBLog'',  FILENAME = N''' + @device_directory + N'MSDBLog.ldf'',  SIZE = 512KB, MAXSIZE = UNLIMITED, FILEGROWTH = 10%)')
  EXECUTE (N'ALTER DATABASE msdb SET DB_CHAINING ON')
  PRINT ''
END
ELSE
BEGIN
  PRINT 'Checking the size of MSDB...'

  DBCC UPDATEUSAGE(N'msdb') WITH NO_INFOMSGS

  -- Make sure that MSDBLog has unlimited growth
  ALTER DATABASE msdb MODIFY FILE (NAME = N'MSDBLog', MAXSIZE = 2TB)

  -- Determine amount of free space in msdb. We need at least 2MB free.
  SELECT @free_db_space = ((((SELECT SUM(size)
                              FROM msdb.dbo.sysfiles
                              WHERE status & 0x8040 = 0) -
                             (SELECT SUM(reserved)
                              FROM msdb.dbo.sysindexes
                              WHERE indid IN (0, 1, 255))) * @page_size) / 1024.0)

  IF (@free_db_space < 2)
  BEGIN
    DECLARE @logical_file_name sysname
    DECLARE @os_file_name      NVARCHAR(255)
    DECLARE @size_as_char      VARCHAR(10)
   
    SELECT @logical_file_name = name,
           @os_file_name = filename,
           @size_as_char = CONVERT(VARCHAR(10), size*8 + 2048) -- size column in sysaltfiles is in number of 8KB pages
    FROM master.dbo.sysaltfiles
    WHERE (name = N'MSDBData')
    set @os_file_name = QUOTENAME(@os_file_name,'''')
    PRINT 'Attempting to expand the msdb database...'
    EXECUTE(N'ALTER DATABASE msdb MODIFY FILE (NAME = N''' + @logical_file_name + N''',
                                               FILENAME = N' + @os_file_name + N',
                                               SIZE =' + @size_as_char + N'KB)')    
    IF (@@error <> 0)
      RAISERROR('Unable to expand the msdb database. INSTMSDB.SQL terminating.', 20, 127) WITH LOG
  END
  PRINT ''
END

EXECUTE (N'ALTER DATABASE msdb SET TRUSTWORTHY ON')

go

-- truncate log on checkpoint
ALTER DATABASE msdb 
SET RECOVERY SIMPLE
go

USE msdb
go

-- Check that we're in msdb
IF (DB_NAME() <> N'msdb')
  RAISERROR('A problem was encountered accessing msdb. INSTMSDB.SQL terminating.', 20, 127) WITH LOG
go

-- Add the guest user
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysusers
                WHERE (name = N'guest')
                  AND (hasdbaccess = 1)))
BEGIN
  PRINT ''
  EXECUTE sys.sp_adduser N'guest'
END
go

CHECKPOINT
go

/**************************************************************/
/* Record time of start of creates                            */
/**************************************************************/
SELECT start = getdate() INTO #InstMsdb
go

/**************************************************************/
/*                                                            */
/*              T  A  B  L  E     D  R  O  P  S               */
/*                                                            */
/**************************************************************/

SET NOCOUNT ON

DECLARE @build_number   INT
SELECT @build_number = @@microsoftversion & 0xffff

-- Do any necessary changes based on build number here



/**************************************************************/
/* drop certificate signature from Agent signed sps           */
/**************************************************************/

BEGIN TRANSACTION
declare @sp sysname
declare @exec_str nvarchar(1024)
declare ms_crs_sps cursor global for select object_name(crypts.major_id) 
   from sys.crypt_properties crypts, sys.certificates certs
   where crypts.thumbprint = certs.thumbprint
   and crypts.class = 1
   and certs.name = '##MS_AgentSigningCertificate##'
open ms_crs_sps
fetch next from ms_crs_sps into @sp
while @@fetch_status = 0
begin
   if exists(select * from sys.objects where name = @sp)
   begin
      print 'Dropping signature from: ' + @sp
      set @exec_str = N'drop signature from ' + quotename(@sp) + N' by certificate [##MS_AgentSigningCertificate##]'
      Execute(@exec_str)
      if (@@error <> 0)
      begin
         declare @err_str nvarchar(1024)
         set @err_str = 'Cannot drop signature from ' + quotename(@sp) + '. Terminating.'
         close ms_crs_sps
         deallocate ms_crs_sps
         ROLLBACK TRANSACTION
         RAISERROR(@err_str, 20, 127) WITH LOG
         return
      end
   end
   fetch next from ms_crs_sps into @sp
end
close ms_crs_sps
deallocate ms_crs_sps
COMMIT TRANSACTION
go


---------------------------------------------------------------
-- Replication Datatype mapping tables
---------------------------------------------------------------

-- REVIEW - Is this needed? What does this do?
exec sys.sp_MSrepl_dropdatatypemappings
go

CHECKPOINT
go

/**************************************************************/
/*                                                            */
/*                     D  E  F  A  U  L  T  S                 */
/*                                                            */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'default_sdl_error_message')
                  AND (type = 'D')))
  EXECUTE('CREATE DEFAULT default_sdl_error_message AS NULL')
go
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'default_current_date')
                  AND (type = 'D')))
  EXECUTE('CREATE DEFAULT default_current_date AS GETDATE()')
go
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'default_zero')
                  AND (type = 'D')))
  EXECUTE('CREATE DEFAULT default_zero AS 0')
go
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'default_one')
                  AND (type = 'D')))
  EXECUTE('CREATE DEFAULT default_one AS 1')
go

/**************************************************************/
/*                                                            */
/*                       T  A  B  L  E  S                     */
/*                                                            */
/**************************************************************/

/**************************************************************/
/* SYSPROXIES                                              */
/**************************************************************/
IF (OBJECT_ID(N'dbo.sysproxies', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysproxies...'
   CREATE TABLE dbo.sysproxies
   (
      proxy_id             INT               IDENTITY,   --used to identify a proxy
      name                  sysname       NOT NULL,   --friendly name of a proxy
    credential_id         INT           NOT NULL,
      enabled                TINYINT       NOT NULL,  
      description           NVARCHAR(512) NULL,     --nvarchar(512)  
    user_sid              VARBINARY(85) NOT NULL,  --sid of proxy NT user
    credential_date_created  DATETIME   NOT NULL   --the date the associated credential has been created   
   )
   CREATE UNIQUE CLUSTERED    INDEX clust ON sysproxies(proxy_id)
   CREATE UNIQUE NONCLUSTERED INDEX nc1   ON sysproxies(name) 
END
go

IF (OBJECT_ID(N'dbo.syssubsystems', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table syssubsystems...'
   CREATE TABLE dbo.syssubsystems
   (
      subsystem_id       INT         NOT NULL,                       -- used to identify the subsystem 
      subsystem          NVARCHAR(40)  COLLATE database_default NOT NULL,    -- Name of subsystem
      description_id     INT         NULL,                           -- Message number of description string. These messages are stored in sysmessages table for localization purposes. Same story as for Shiloh
      subsystem_dll      NVARCHAR(255) COLLATE database_default NULL,        -- Store full path of the name of subsystem dll  subsystem are always installed in the binn folder of an instance
      agent_exe          NVARCHAR(255) COLLATE database_default NULL,        -- Full path to the executable that use the subsystem
      start_entry_point  NVARCHAR(30)  COLLATE database_default NULL,        -- Function called when initializing subsystem
      event_entry_point  NVARCHAR(30)  COLLATE database_default NULL,        -- Function called when executing a subsystem step 
      stop_entry_point   NVARCHAR(30)  COLLATE database_default NULL,        -- Function called when unloading a subsystem 
      max_worker_threads INT           NULL                                  -- Number of maximum concurrent steps for a subsystem 
   )
   CREATE UNIQUE CLUSTERED    INDEX clust ON syssubsystems(subsystem_id)
   CREATE UNIQUE NONCLUSTERED INDEX nc1   ON syssubsystems(subsystem) 
END
go

IF (OBJECT_ID(N'dbo.sysproxysubsystem', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysproxysubsystem...'

   CREATE TABLE dbo.sysproxysubsystem
   (
      subsystem_id INT           NOT NULL,  --  used to identify the subsystem 
      proxy_id     INT           NOT NULL,  --  used to identify the proxy 
   )
   CREATE UNIQUE CLUSTERED    INDEX clust ON sysproxysubsystem(subsystem_id, proxy_id)
END
go

IF (OBJECT_ID(N'dbo.sysproxylogin', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysproxylogin...'

   CREATE TABLE dbo.sysproxylogin
   (
      proxy_id     INT           NOT NULL,  --used to identify the proxy 
    sid          VARBINARY(85) NULL,       --keep logins, fixed server roles or msdb database roles
      flags        INT           DEFAULT 0 NOT NULL   -- tells is member_id is login = 0, server fixed role, msdb role. 
   )
   CREATE UNIQUE CLUSTERED    INDEX clust ON sysproxylogin(proxy_id, sid, flags)
END
go

PRINT ''
PRINT 'Creating view sysproxyloginsubsystem_view...'
go
IF (NOT OBJECT_ID(N'dbo.sysproxyloginsubsystem_view', 'V') IS NULL)
  DROP VIEW sysproxyloginsubsystem_view
go
CREATE VIEW sysproxyloginsubsystem_view
AS
SELECT ps.subsystem_id AS subsystem_id, pl.proxy_id AS proxy_id, pl.sid AS sid, pl.flags AS flags
FROM sysproxylogin pl JOIN sysproxysubsystem ps ON pl.proxy_id = ps.proxy_id
go

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sqlagent_info')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sqlagent_info...'

  CREATE TABLE sqlagent_info
  (
  attribute sysname       NOT NULL,
  value     NVARCHAR(512) NOT NULL
  )
END
go

/**************************************************************/
/* SYSDOWNLOADLIST                                            */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysdownloadlist')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysdownloadlist...'

  CREATE TABLE sysdownloadlist
  (
  instance_id         INT IDENTITY     NOT NULL,
  source_server       sysname          NOT NULL,
  operation_code      TINYINT          NOT NULL,
  object_type         TINYINT          NOT NULL,
  object_id           UNIQUEIDENTIFIER NOT NULL,
  target_server       sysname        NOT NULL,
  error_message       NVARCHAR(1024)   NULL,
  date_posted         DATETIME         NOT NULL,
  date_downloaded     DATETIME         NULL,
  status              TINYINT          NOT NULL,
  deleted_object_name sysname          NULL
  )

  EXECUTE sys.sp_bindefault default_sdl_error_message, N'sysdownloadlist.error_message'
  EXECUTE sys.sp_bindefault default_current_date,      N'sysdownloadlist.date_posted'
  EXECUTE sys.sp_bindefault default_zero,              N'sysdownloadlist.status'

  CREATE UNIQUE CLUSTERED INDEX clust ON sysdownloadlist(instance_id)
  CREATE NONCLUSTERED     INDEX nc1   ON sysdownloadlist(target_server)
  CREATE NONCLUSTERED     INDEX nc2   ON sysdownloadlist(object_id)
END
go

/**************************************************************/
/* SYSJOBHISTORY                                              */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysjobhistory')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysjobhistory...'

  CREATE TABLE sysjobhistory
  (
  instance_id          INT IDENTITY     NOT NULL,
  job_id               UNIQUEIDENTIFIER NOT NULL,
  step_id              INT              NOT NULL,
  step_name            sysname          NOT NULL,
  sql_message_id       INT              NOT NULL,
  sql_severity         INT              NOT NULL,
  message              NVARCHAR(4000)   NULL,
  run_status           INT              NOT NULL,
  run_date             INT              NOT NULL,
  run_time             INT              NOT NULL,
  run_duration         INT              NOT NULL,
  operator_id_emailed  INT              NOT NULL,
  operator_id_netsent  INT              NOT NULL,
  operator_id_paged    INT              NOT NULL,
  retries_attempted    INT              NOT NULL,
  server               sysname          NOT NULL
  )

  CREATE UNIQUE CLUSTERED INDEX clust ON sysjobhistory(instance_id)
  CREATE NONCLUSTERED     INDEX nc1   ON sysjobhistory(job_id)
END
ELSE
BEGIN
  ALTER TABLE sysjobhistory ALTER COLUMN
    message NVARCHAR(4000)   NULL
END
go


/**************************************************************/
/* sysoriginatingservers                                      */
/**************************************************************/
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysoriginatingservers')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysoriginatingservers...'

CREATE TABLE dbo.sysoriginatingservers
(
  -- There is only a single MSX server record in this table (originating_server_id = 1)
  -- 0 is generated by sysoriginatingservers_view and indicates the local server
  originating_server_id INT      CONSTRAINT CK_originating_server_id_MustBe_1 CHECK (originating_server_id = 1) 
                             DEFAULT (1) UNIQUE CLUSTERED,           
  originating_server    sysname     NOT NULL UNIQUE NONCLUSTERED,
  --Mark this record as a MSX server entry
  master_server         bit         CONSTRAINT CK_master_server_MustBe_1 CHECK (master_server = 1) 
                             DEFAULT (1)  
)

END
go


/**************************************************************/
/* trig_sysoriginatingservers_delete                          */
/**************************************************************/
PRINT ''
PRINT 'Creating trigger trig_sysoriginatingservers_delete...'

IF NOT OBJECT_ID('dbo.trig_sysoriginatingservers_delete', 'TR') IS NULL
    DROP TRIGGER dbo.trig_sysoriginatingservers_delete
GO
CREATE TRIGGER dbo.trig_sysoriginatingservers_delete
ON dbo.sysoriginatingservers
FOR DELETE
AS
BEGIN
  SET NOCOUNT ON
  -- Only a single MSX server entry can exist in this table. ie. originating_server_id = 1 and master_server = 1. 
  IF((EXISTS (SELECT *
           FROM deleted AS d
                JOIN dbo.sysjobs AS j ON d.originating_server_id = j.originating_server_id)) OR
    (EXISTS (SELECT *
           FROM deleted AS d
                JOIN dbo.sysschedules AS s ON d.originating_server_id = s.originating_server_id)))
  BEGIN
    RAISERROR(14380, -1, -1)
   ROLLBACK TRANSACTION
    RETURN
  END
END
go


/**************************************************************/
/* sysoriginatingservers_view                                 */
/**************************************************************/
PRINT ''
PRINT 'Creating view sysoriginatingservers_view...'
GO
IF (NOT OBJECT_ID(N'dbo.sysoriginatingservers_view', 'V') IS NULL)
  DROP VIEW sysoriginatingservers_view
GO
CREATE VIEW dbo.sysoriginatingservers_view(originating_server_id, originating_server, master_server)
AS 
   SELECT
      0 AS originating_server_id, 
      UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))) AS originating_server,
      0 AS master_server
   UNION
   SELECT 
      originating_server_id,
      originating_server,
      master_server
   FROM
      dbo.sysoriginatingservers
GO

/**************************************************************/
/* SYSJOBS                                                    */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysjobs')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysjobs...'

  CREATE TABLE sysjobs
  (
  job_id                     UNIQUEIDENTIFIER NOT NULL,
  originating_server_id      INT              NOT NULL, -- REFERENCE enforced by trig_sysjobs_insert_update
  name                       sysname          NOT NULL,
  enabled                    TINYINT          NOT NULL,
  description                NVARCHAR(512)    NULL,
  start_step_id              INT              NOT NULL,
  category_id                INT              NOT NULL,
  owner_sid                  VARBINARY(85)    NOT NULL,
  notify_level_eventlog      INT              NOT NULL,
  notify_level_email         INT              NOT NULL,
  notify_level_netsend       INT              NOT NULL,
  notify_level_page          INT              NOT NULL,
  notify_email_operator_id   INT              NOT NULL,
  notify_netsend_operator_id INT              NOT NULL,
  notify_page_operator_id    INT              NOT NULL,
  delete_level               INT              NOT NULL,
  date_created               DATETIME         NOT NULL,
  date_modified              DATETIME         NOT NULL,
  version_number             INT              NOT NULL
  )

  CREATE UNIQUE CLUSTERED INDEX clust ON sysjobs(job_id)
  CREATE NONCLUSTERED     INDEX nc1   ON sysjobs(name) -- NOTE: This is deliberately non-unique
  CREATE NONCLUSTERED     INDEX nc3   ON sysjobs(category_id)
  CREATE NONCLUSTERED     INDEX nc4   ON sysjobs(owner_sid)
END
go

/**************************************************************/
/* trig_sysjobs_insert_update                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating trigger trig_sysjobs_insert_update...'

IF NOT OBJECT_ID('dbo.trig_sysjobs_insert_update', 'TR') IS NULL
    DROP TRIGGER dbo.trig_sysjobs_insert_update
GO
CREATE TRIGGER dbo.trig_sysjobs_insert_update
ON dbo.sysjobs
FOR INSERT, UPDATE
AS
BEGIN
  SET NOCOUNT ON
  -- Disallow the insert or update if the originating_server_id isn't in sysoriginatingservers_view.  
  IF (EXISTS (SELECT *
            FROM inserted
           WHERE inserted.originating_server_id NOT IN 
                    (SELECT v.originating_server_id 
                     FROM sysoriginatingservers_view AS v)))
  BEGIN
   RAISERROR(14379, -1, -1, 'dbo.sysjobs')
   ROLLBACK TRANSACTION
    RETURN
  END
END
go

/**************************************************************/
/* SYSJOBSERVERS                                              */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysjobservers')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysjobservers...'

  CREATE TABLE sysjobservers
  (
  job_id               UNIQUEIDENTIFIER NOT NULL,
  server_id            INT              NOT NULL,
  last_run_outcome     TINYINT          NOT NULL,
  last_outcome_message NVARCHAR(4000)   NULL,
  last_run_date        INT              NOT NULL,
  last_run_time        INT              NOT NULL,
  last_run_duration    INT              NOT NULL
  )

  CREATE CLUSTERED    INDEX clust ON sysjobservers(job_id)
  CREATE NONCLUSTERED INDEX nc1   ON sysjobservers(server_id)
END
ELSE
BEGIN
  ALTER TABLE sysjobservers ALTER COLUMN
    last_outcome_message NVARCHAR(4000)   NULL
END
go

/**************************************************************/
/* SYSJOBS_VIEW                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating view sysjobs_view...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysjobs_view')
              AND (type = 'V')))
  DROP VIEW sysjobs_view
go
CREATE VIEW sysjobs_view
AS
SELECT jobs.job_id,
       svr.originating_server,
       jobs.name,
       jobs.enabled,
       jobs.description,
       jobs.start_step_id,
       jobs.category_id,
       jobs.owner_sid,
       jobs.notify_level_eventlog,
       jobs.notify_level_email,
       jobs.notify_level_netsend,
       jobs.notify_level_page,
       jobs.notify_email_operator_id,
       jobs.notify_netsend_operator_id,
       jobs.notify_page_operator_id,
       jobs.delete_level,
       jobs.date_created,
       jobs.date_modified,
       jobs.version_number,
       jobs.originating_server_id,
       svr.master_server
FROM msdb.dbo.sysjobs as jobs
  JOIN msdb.dbo.sysoriginatingservers_view as svr
    ON jobs.originating_server_id = svr.originating_server_id
  --LEFT JOIN msdb.dbo.sysjobservers js ON jobs.job_id = js.job_id
WHERE (owner_sid = SUSER_SID())
   OR (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
   OR (ISNULL(IS_MEMBER(N'SQLAgentReaderRole'), 0) = 1)
   OR ( (ISNULL(IS_MEMBER(N'TargetServersRole'), 0) = 1) AND
        (EXISTS(SELECT * FROM msdb.dbo.sysjobservers js 
         WHERE js.server_id <> 0 AND js.job_id = jobs.job_id))) -- filter out local jobs  
go



IF (OBJECT_ID(N'dbo.syssessions', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table syssessions...'

   CREATE TABLE dbo.syssessions
   (
      session_id                INT  IDENTITY PRIMARY KEY,       
    agent_start_date          DATETIME NOT NULL    
   )
   CREATE UNIQUE NONCLUSTERED INDEX nonclust ON syssessions(agent_start_date)
END
go

IF (OBJECT_ID(N'dbo.sysjobactivity', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysjobactivity...'

   CREATE TABLE dbo.sysjobactivity
   (
      session_id                INT              NOT NULL REFERENCES syssessions(session_id),           
      job_id                    UNIQUEIDENTIFIER NOT NULL REFERENCES  sysjobs(job_id) ON DELETE CASCADE,           
    run_requested_date        DATETIME         NULL,
    run_requested_source      sysname          NULL,
    queued_date               DATETIME         NULL,
    start_execution_date      DATETIME         NULL,
    last_executed_step_id     INT              NULL,
    last_executed_step_date   DATETIME         NULL,
    stop_execution_date       DATETIME         NULL,
    job_history_id            INT              NULL,      --keeps a reference to the record in sysjobhistory for detailed job information
    next_scheduled_run_date   DATETIME         NULL
   )
   CREATE UNIQUE CLUSTERED    INDEX clust ON sysjobactivity(session_id, job_id)
END
go


/**************************************************************/
/* SYSJOBSTEPS                                                */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysjobsteps')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysjobsteps...'

  CREATE TABLE sysjobsteps
  (
  job_id                UNIQUEIDENTIFIER NOT NULL,
  step_id               INT              NOT NULL,
  step_name             sysname          NOT NULL,
  subsystem             NVARCHAR(40)    NOT NULL,
  command               NVARCHAR(max)    NULL,
  flags                 INT              NOT NULL,
  additional_parameters NVARCHAR(max)    NULL,
  cmdexec_success_code  INT              NOT NULL,
  on_success_action     TINYINT          NOT NULL,
  on_success_step_id    INT              NOT NULL,
  on_fail_action        TINYINT          NOT NULL,
  on_fail_step_id       INT              NOT NULL,
  server                sysname          NULL,      -- Used only by replication and OLAP
  database_name         sysname          NULL,
  database_user_name    sysname          NULL,
  retry_attempts        INT              NOT NULL,
  retry_interval        INT              NOT NULL,
  os_run_priority       INT              NOT NULL,  -- NOTE: Cannot use TINYINT because we need a signed number
  output_file_name      NVARCHAR(200)    NULL,
  last_run_outcome      INT              NOT NULL,
  last_run_duration     INT              NOT NULL,
  last_run_retries      INT              NOT NULL,
  last_run_date         INT              NOT NULL,
  last_run_time         INT              NOT NULL,
  proxy_id              INT              NULL,
  step_uid              UNIQUEIDENTIFIER NULL
  )

  CREATE UNIQUE CLUSTERED INDEX clust ON sysjobsteps(job_id, step_id)
  CREATE UNIQUE NONCLUSTERED INDEX nc1 ON sysjobsteps(job_id, step_name)
  CREATE UNIQUE NONCLUSTERED INDEX nc2 ON sysjobsteps(step_uid)

END
go


/**************************************************************/
/* SYSJOBSTEPSLOGS                                                */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysjobstepslogs')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysjobstepslogs...'

  CREATE TABLE sysjobstepslogs
  (
  log_id                INT IDENTITY (1,1) PRIMARY KEY NOT NULL,
  log                   NVARCHAR(max)    NOT NULL,
  date_created          DATETIME         NOT NULL DEFAULT getdate(),
  date_modified         DATETIME         NOT NULL DEFAULT getdate(),
  log_size              bigint           NOT NULL ,
  step_uid              UNIQUEIDENTIFIER NOT NULL REFERENCES  sysjobsteps(step_uid) ON DELETE CASCADE 
  )
END
go


/**************************************************************/
/* SYSSCHEDULES                                               */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysschedules')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysschedules...'

  CREATE TABLE sysschedules
  (
  schedule_id            INT IDENTITY     PRIMARY KEY CLUSTERED,
  schedule_uid           UNIQUEIDENTIFIER NOT NULL,
  originating_server_id  INT              NOT NULL, -- REFERENCE enforced by trig_sysschedules_insert_update
  name                   sysname          NOT NULL,
  owner_sid              varbinary(85)    NOT NULL, 
  enabled                INT              NOT NULL,
  freq_type              INT              NOT NULL,
  freq_interval          INT              NOT NULL,
  freq_subday_type       INT              NOT NULL,
  freq_subday_interval   INT              NOT NULL,
  freq_relative_interval INT              NOT NULL,
  freq_recurrence_factor INT              NOT NULL,
  active_start_date      INT              NOT NULL,
  active_end_date        INT              NOT NULL,
  active_start_time      INT              NOT NULL,
  active_end_time        INT              NOT NULL,
  date_created           DATETIME         NOT NULL  DEFAULT (GETDATE()),
  date_modified          DATETIME         NOT NULL  DEFAULT (GETDATE()),
  version_number         INT              NOT NULL  DEFAULT (1)
  )

  -- CREATE UNIQUE CLUSTERED INDEX clust ON sysschedules(job_id, name)
END
go

/**************************************************************/
/* trig_sysschedules_insert_update                            */
/**************************************************************/

PRINT ''
PRINT 'Creating trigger trig_sysschedules_insert_update...'

IF NOT OBJECT_ID('dbo.trig_sysschedules_insert_update', 'TR') IS NULL
    DROP TRIGGER dbo.trig_sysschedules_insert_update
GO
CREATE TRIGGER dbo.trig_sysschedules_insert_update
ON dbo.sysschedules
FOR INSERT, UPDATE
AS
BEGIN
  SET NOCOUNT ON
  -- Disallow the insert or update if the originating_server_id isn't in sysoriginatingservers_view. 
  IF (EXISTS (SELECT *
            FROM inserted
           WHERE inserted.originating_server_id NOT IN 
                    (SELECT v.originating_server_id 
                     FROM sysoriginatingservers_view AS v)))
  BEGIN
   RAISERROR(14379, -1, -1, 'dbo.sysschedules')
   ROLLBACK TRANSACTION
    RETURN
  END
END
go

/**************************************************************/
/* SYSSCHEDULES_LOCALSERVER_VIEW                              */
/**************************************************************/

PRINT ''
PRINT 'Creating view sysschedules_localserver_view...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysschedules_localserver_view')
              AND (type = 'V')))
  DROP VIEW sysschedules_localserver_view
go
CREATE VIEW sysschedules_localserver_view
AS
SELECT sched.schedule_id,
       sched.schedule_uid,
       sched.originating_server_id,
       sched.name,
       sched.owner_sid,
       sched.enabled,
       sched.freq_type,
       sched.freq_interval,
       sched.freq_subday_type,
       sched.freq_subday_interval,
       sched.freq_relative_interval,
       sched.freq_recurrence_factor,
       sched.active_start_date,
       sched.active_end_date,
       sched.active_start_time,
       sched.active_end_time,
       sched.date_created,
       sched.date_modified,
       sched.version_number,
       svr.originating_server,
       svr.master_server
FROM msdb.dbo.sysschedules as sched
    JOIN msdb.dbo.sysoriginatingservers_view as svr
    ON sched.originating_server_id = svr.originating_server_id
WHERE (svr.master_server = 0)
  AND ( (sched.owner_sid = SUSER_SID())
        OR (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
      OR (ISNULL(IS_MEMBER(N'SQLAgentReaderRole'), 0) = 1)
      )
go


/**************************************************************/
/* SYSJOBSCHEDULES                                            */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysjobschedules')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysjobschedules...'

  CREATE TABLE sysjobschedules
  (
  schedule_id            INT              REFERENCES sysschedules(schedule_id),
  job_id                 UNIQUEIDENTIFIER REFERENCES sysjobs(job_id),
  next_run_date          INT              NOT NULL DEFAULT 0,
  next_run_time          INT              NOT NULL DEFAULT 0
  )

  CREATE UNIQUE CLUSTERED INDEX clust ON sysjobschedules(job_id, schedule_id)
  CREATE NONCLUSTERED INDEX [NC_sysjobschedules_schedule_id] ON sysjobschedules(schedule_id)
END
go


/**************************************************************/
/* SYSCATEGORIES                                              */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'syscategories')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table syscategories...'

  CREATE TABLE syscategories
  (
  category_id    INT IDENTITY NOT NULL,
  category_class INT          NOT NULL, -- 1 = Job, 2 = Alert, 3 = Operator
  category_type  TINYINT      NOT NULL, -- 1 = Local, 2 = Multi-Server [Only relevant if class is 1; otherwise, 3 (None)]
  name           sysname      NOT NULL
  )

  CREATE UNIQUE CLUSTERED INDEX clust ON syscategories(name, category_class)
END
go

-- Install standard [permanent] categories (reserved ID range is 0 - 99)
SET IDENTITY_INSERT msdb.dbo.syscategories ON

DELETE FROM msdb.dbo.syscategories
WHERE (category_id < 100)

-- Core categories
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 0, 1, 1, N'[Uncategorized (Local)]')        -- Local default
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 1, 1, 1, N'Jobs from MSX')                  -- All jobs downloaded from the MSX are placed in this category
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 2, 1, 2, N'[Uncategorized (Multi-Server)]') -- Multi-server default
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 3, 1, 1, N'Database Maintenance')           -- Default for all jobs created by the Maintenance Plan Wizard
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 5, 1, 1, N'Full-Text')                      -- Default for all jobs created by the Index Server
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 6, 1, 1, N'Log Shipping')                   -- Default for Log Shipping jobs
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 7, 1, 1, N'Database Engine Tuning Advisor') -- Default for DTA jobs
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 8, 1, 1, N'Data Collector')                   -- Default for all jobs created by the Data Collector
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (98, 2, 3, N'[Uncategorized]')                -- Alert default
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (99, 3, 3, N'[Uncategorized]')                -- Operator default

-- Replication categories
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (10, 1, 1, N'REPL-Distribution')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (11, 1, 1, N'REPL-Distribution Cleanup')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (12, 1, 1, N'REPL-History Cleanup')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (13, 1, 1, N'REPL-LogReader')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (14, 1, 1, N'REPL-Merge')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (15, 1, 1, N'REPL-Snapshot')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (16, 1, 1, N'REPL-Checkup')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (17, 1, 1, N'REPL-Subscription Cleanup')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (18, 1, 1, N'REPL-Alert Response')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (19, 1, 1, N'REPL-QueueReader')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (20, 2, 3, N'Replication')

SET IDENTITY_INSERT msdb.dbo.syscategories OFF
go

/**************************************************************/
/* SYSTARGETSERVERS                                           */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'systargetservers')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table systargetservers...'

  CREATE TABLE systargetservers
  (
  server_id               INT IDENTITY  NOT NULL,
  server_name             sysname       NOT NULL,
  location                NVARCHAR(200) NULL,
  time_zone_adjustment    INT           NOT NULL,  -- The offset from GMT in minutes (set by sp_msx_enlist)
  enlist_date             DATETIME      NOT NULL,
  last_poll_date          DATETIME      NOT NULL,
  status                  INT           NOT NULL,  -- 1 = Normal, 2 = Offline, 4 = Blocked
  local_time_at_last_poll DATETIME      NOT NULL,  -- The local time at the target server as-of the last time it polled the MSX
  enlisted_by_nt_user     NVARCHAR(100) NOT NULL,
  poll_interval           INT           NOT NULL   -- The MSX polling interval (in seconds)
  )

  EXECUTE sys.sp_bindefault default_current_date, N'systargetservers.enlist_date'
  EXECUTE sys.sp_bindefault default_current_date, N'systargetservers.last_poll_date'
  EXECUTE sys.sp_bindefault default_one,          N'systargetservers.status'

  CREATE UNIQUE CLUSTERED    INDEX clust ON systargetservers(server_id)
  CREATE UNIQUE NONCLUSTERED INDEX nc1   ON systargetservers(server_name)
END
go

/**************************************************************/
/* SYSTARGETSERVERS_VIEW                                      */
/**************************************************************/

PRINT ''
PRINT 'Creating view systargetservers_view...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'systargetservers_view')
              AND (type = 'V')))
  DROP VIEW systargetservers_view
go
CREATE VIEW systargetservers_view
AS
SELECT server_id,
       server_name,
       enlist_date,
       last_poll_date
FROM msdb.dbo.systargetservers
UNION
SELECT 0,
       CONVERT(sysname, SERVERPROPERTY('ServerName')),
       CONVERT(DATETIME, N'19981113', 112),
       CONVERT(DATETIME, N'19981113', 112)
go

/**************************************************************/
/* SYSTARGETSERVERGROUPS                                      */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'systargetservergroups')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table systargetservergroups...'

  CREATE TABLE systargetservergroups
  (
  servergroup_id INT IDENTITY NOT NULL,
  name           sysname      NOT NULL
  )

  CREATE UNIQUE CLUSTERED INDEX clust ON systargetservergroups(name)
END
go

/**************************************************************/
/* SYSTARGETSERVERGROUPMEMBERS                                */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'systargetservergroupmembers')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table systargetservergroupmembers...'

  CREATE TABLE systargetservergroupmembers
  (
  servergroup_id INT NOT NULL,
  server_id      INT NOT NULL
  )

  CREATE UNIQUE CLUSTERED INDEX clust ON systargetservergroupmembers(servergroup_id, server_id)
  CREATE NONCLUSTERED     INDEX nc1   ON systargetservergroupmembers(server_id)
END
go

/**************************************************************/
/* SYSALERTS                                                  */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysalerts')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysalerts...'

  CREATE TABLE sysalerts
  (
  id                        INT IDENTITY     NOT NULL,
  name                      sysname          NOT NULL, -- Was length 60 in 6.x
  event_source              NVARCHAR(100)    NOT NULL,
  event_category_id         INT              NULL,
  event_id                  INT              NULL,
  message_id                INT              NOT NULL, -- Was NULL in 6.x
  severity                  INT              NOT NULL, -- Was NULL in 6.x
  enabled                   TINYINT          NOT NULL,
  delay_between_responses   INT              NOT NULL,
  last_occurrence_date      INT              NOT NULL, -- Was NULL in 6.x
  last_occurrence_time      INT              NOT NULL, -- Was NULL in 6.x
  last_response_date        INT              NOT NULL, -- Was NULL in 6.x
  last_response_time        INT              NOT NULL, -- Was NULL in 6.x
  notification_message      NVARCHAR(512)    NULL,
  include_event_description TINYINT          NOT NULL,
  database_name             NVARCHAR(512)    NULL,
  event_description_keyword NVARCHAR(100)    NULL,
  occurrence_count          INT              NOT NULL,
  count_reset_date          INT              NOT NULL, -- Was NULL in 6.x
  count_reset_time          INT              NOT NULL, -- Was NULL in 6.x
  job_id                    UNIQUEIDENTIFIER NOT NULL, -- Was NULL in 6.x
  has_notification          INT              NOT NULL, -- New for 7.0
  flags                     INT              NOT NULL, -- Was NULL in 6.x
  performance_condition     NVARCHAR(512)    NULL,
  category_id               INT              NOT NULL  -- New for 7.0
  )

  CREATE UNIQUE CLUSTERED INDEX ByName ON sysalerts(name)
  CREATE UNIQUE INDEX ByID ON sysalerts(id)
END
go

/**************************************************************/
/* SYSOPERATORS                                               */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysoperators')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysoperators...'

  CREATE TABLE sysoperators
  (
  id                        INT IDENTITY  NOT NULL,
  name                      sysname       NOT NULL, -- Was length 50 in 6.x
  enabled                   TINYINT       NOT NULL,
  email_address             NVARCHAR(100) NULL,
  last_email_date           INT           NOT NULL, -- Was NULL in 6.x
  last_email_time           INT           NOT NULL, -- Was NULL in 6.x
  pager_address             NVARCHAR(100) NULL,
  last_pager_date           INT           NOT NULL, -- Was NULL in 6.x
  last_pager_time           INT           NOT NULL, -- Was NULL in 6.x
  weekday_pager_start_time  INT           NOT NULL,
  weekday_pager_end_time    INT           NOT NULL,
  saturday_pager_start_time INT           NOT NULL,
  saturday_pager_end_time   INT           NOT NULL,
  sunday_pager_start_time   INT           NOT NULL,
  sunday_pager_end_time     INT           NOT NULL,
  pager_days                TINYINT       NOT NULL,
  netsend_address           NVARCHAR(100) NULL,     -- New for 7.0
  last_netsend_date         INT           NOT NULL, -- New for 7.0
  last_netsend_time         INT           NOT NULL, -- New for 7.0
  category_id               INT           NOT NULL  -- New for 7.0
  )

  CREATE UNIQUE CLUSTERED INDEX ByName ON sysoperators(name)
  CREATE UNIQUE INDEX ByID ON sysoperators(id)
END
go

/**************************************************************/
/* SYSNOTIFICATIONS                                           */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysnotifications')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysnotifications...'

  CREATE TABLE sysnotifications
  (
  alert_id             INT      NOT NULL,
  operator_id          INT      NOT NULL,
  notification_method  TINYINT  NOT NULL
  )

  CREATE UNIQUE CLUSTERED INDEX ByAlertIDAndOperatorID ON sysnotifications(alert_id, operator_id)
END
go



/**************************************************************/
/*                                                            */
/*  M  A  I  N  T  E  N  A  N  C  E    P  L  A  N  S          */
/*                                                            */
/**************************************************************/

/**************************************************************/
/* sysmaintplan_subplans                                      */
/**************************************************************/
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysmaintplan_subplans')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysmaintplan_subplans...'

    -- This table stores the DTS package associated with the maintenance plan
    -- It also stored metadata about the maintenance plan such as its name, description etc
    CREATE TABLE sysmaintplan_subplans
    (
        subplan_id          UNIQUEIDENTIFIER    NOT NULL 
            CONSTRAINT [PK_sysmaintplan_subplan] PRIMARY KEY CLUSTERED,
        subplan_name        sysname             NOT NULL,
        subplan_description NVARCHAR(512)       NULL,
        plan_id             UNIQUEIDENTIFIER    NOT NULL,
        job_id              UNIQUEIDENTIFIER    NOT NULL 
            CONSTRAINT FK_subplan_job_id
            FOREIGN KEY (job_id) REFERENCES sysjobs(job_id),
        msx_job_id          UNIQUEIDENTIFIER DEFAULT NULL NULL
            CONSTRAINT FK_subplan_msx_job_id
            FOREIGN KEY (msx_job_id) REFERENCES sysjobs(job_id),
        schedule_id         INT                 NULL 
            CONSTRAINT FK_subplan_schedule_id 
            FOREIGN KEY (schedule_id) REFERENCES sysschedules(schedule_id),
        msx_plan bit DEFAULT 0 NOT NULL
    )
END
go

/**************************************************************/
/* sysmaintplan_log                                           */
/**************************************************************/
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysmaintplan_log')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysmaintplan_log...'

    -- This table stores the maintenance plan log info 
    CREATE TABLE sysmaintplan_log
    (
        task_detail_id  UNIQUEIDENTIFIER    NOT NULL 
            CONSTRAINT [PK_sysmaintplan_taskdetail_id] PRIMARY KEY CLUSTERED,
        plan_id         UNIQUEIDENTIFIER    NULL,
        subplan_id      UNIQUEIDENTIFIER    NULL 
            CONSTRAINT [FK_sysmaintplan_log_subplan_id] 
            FOREIGN KEY (subplan_id) REFERENCES sysmaintplan_subplans(subplan_id),
        start_time      DATETIME            NULL,
        end_time        DATETIME            NULL,
        succeeded       BIT                 NULL,
        logged_remotely bit not null default (0),
        source_server_name nvarchar (128) NULL,
        plan_name nvarchar (128) NULL,
        subplan_name nvarchar (128) NULL
    )
END
go


/**************************************************************/
/* sysmaintplan_logdetail                                     */
/**************************************************************/
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysmaintplan_logdetail')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysmaintplan_logdetail...'

    -- This table stores the maintenance plan log details 
    CREATE TABLE sysmaintplan_logdetail
    (
        task_detail_id  UNIQUEIDENTIFIER NOT NULL 
            CONSTRAINT [FK_sysmaintplan_log_detail_task_id] 
            FOREIGN KEY (task_detail_id) REFERENCES sysmaintplan_log(task_detail_id)
            ON DELETE CASCADE,
        line1           NVARCHAR(256)   NOT NULL,
        line2           NVARCHAR(256)   NULL,
        line3           NVARCHAR(256)   NULL,
        line4           NVARCHAR(256)   NULL,
        line5           NVARCHAR(256)   NULL,
        server_name     sysname         NOT NULL,
        start_time      DATETIME        NULL,
        end_time        DATETIME        NULL,
        error_number    INT             NULL,
        error_message   NVARCHAR(max)   NULL,
        command         NVARCHAR(max)   NULL,
        succeeded       BIT             NULL
    )
END
go


/**************************************************************/
/* SYSTASKIDS                                                 */
/*                                                            */
/* This table provides a mapping between new GUID job ID's    */
/* and 6.x INT task ID's.                                     */
/* Entries are made in this table for all existing 6.x tasks  */
/* and for all new tasks added using the 7.0 version of       */
/* sp_addtask.                                                */
/* Callers of the 7.0 version of sp_helptask will ONLY see    */
/* tasks [jobs] that have a corresponding entry in this table */
/* [IE. Jobs created with sp_add_job will not be returned].   */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'systaskids')
                  AND (type = 'U')))
BEGIN
  CREATE TABLE systaskids
  (
  task_id INT IDENTITY     NOT NULL,
  job_id  UNIQUEIDENTIFIER NOT NULL
  )

  CREATE CLUSTERED INDEX clust ON systaskids(job_id)
END
go

/**************************************************************/
/* SYSCACHEDCREDENTIALS                                       */
/*                                                            */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'syscachedcredentials')
                  AND (type = 'U')))
BEGIN
  CREATE TABLE syscachedcredentials
  (
  login_name          sysname      COLLATE database_default NOT NULL PRIMARY KEY,
  has_server_access   BIT          NOT NULL DEFAULT 0,
  is_sysadmin_member  BIT          NOT NULL DEFAULT 0,
  cachedate           DATETIME     NOT NULL DEFAULT getdate()
  )
END
go

---------------------------------------------------------------
-- Replication Datatype mapping tables
---------------------------------------------------------------

exec sys.sp_MSrepl_createdatatypemappings
go


CHECKPOINT
go

/**************************************************************/
/*                                                            */
/*        C  O  R  E     P  R  O  C  E  D  U  R  E  S         */
/*                                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating function SQLAGENT_SUSER_SNAME ...'
IF (NOT OBJECT_ID(N'dbo.SQLAGENT_SUSER_SNAME', 'FN') IS NULL)
  DROP FUNCTION dbo.SQLAGENT_SUSER_SNAME
go

CREATE FUNCTION dbo.SQLAGENT_SUSER_SNAME(@user_sid VARBINARY(85)) RETURNS sysname
AS
BEGIN
  DECLARE @ret sysname
  IF @user_sid = 0xFFFFFFFF
    SELECT @ret = N'$(SQLAgentAccount)'
  ELSE
    SELECT @ret = SUSER_SNAME(@user_sid)
  RETURN @ret
END
go

PRINT ''
PRINT 'Creating function SQLAGENT_SUSER_SID ...'
IF (NOT OBJECT_ID(N'dbo.SQLAGENT_SUSER_SID', 'FN') IS NULL)
  DROP FUNCTION dbo.SQLAGENT_SUSER_SID
go

CREATE FUNCTION dbo.SQLAGENT_SUSER_SID(@user_name sysname) RETURNS VARBINARY(85)
AS
BEGIN
  DECLARE @ret VARBINARY(85)
  IF @user_name = N'$(SQLAgentAccount)'
    SELECT @ret = 0xFFFFFFFF
  ELSE
    SELECT @ret = SUSER_SID(@user_name, 0)
  RETURN @ret
END
go

-----------------------------------------------------------
-- get_principal_id : retrieves principal_id for a given sid
--
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.get_principal_id', 'FN') IS NULL
    DROP FUNCTION dbo.get_principal_id
GO

CREATE FUNCTION dbo.get_principal_id(@principal_sid varbinary(85))
RETURNS int
AS
BEGIN
    DECLARE @principal_id int
    SELECT @principal_id=principal_id FROM msdb.sys.database_principals WHERE sid=@principal_sid
    RETURN @principal_id
END
GO

-----------------------------------------------------------
-- get_principal_sid : retrieves principal sid from principal_id 
--
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.get_principal_sid', 'FN') IS NULL
    DROP FUNCTION dbo.get_principal_sid
GO

CREATE FUNCTION dbo.get_principal_sid(@principal_id int)
RETURNS varbinary(85)
AS
BEGIN
    DECLARE @principal_sid varbinary(85)
    SELECT @principal_sid=sid FROM msdb.sys.database_principals WHERE principal_id=@principal_id
    RETURN @principal_sid
END
GO
/**************************************************************/
/* SP_SQLAGENT_IS_SRVROLEMEMBER                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure SP_SQLAGENT_IS_SRVROLEMEMBER...'
IF (NOT OBJECT_ID(N'dbo.sp_sqlagent_is_srvrolemember', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_sqlagent_is_srvrolemember
go

CREATE PROCEDURE sp_sqlagent_is_srvrolemember
   @role_name sysname, @login_name sysname
AS
BEGIN
  DECLARE @is_member        INT
  SET NOCOUNT ON
  
  IF @role_name IS NULL OR @login_name IS NULL
    RETURN(0)
  
  SELECT @is_member = 0
  --IS_SRVROLEMEMBER works only if the login to be tested is provisioned with sqlserver
  if( @login_name = SUSER_SNAME())
    SELECT @is_member = IS_SRVROLEMEMBER(@role_name)
  else
    SELECT @is_member = IS_SRVROLEMEMBER(@role_name, @login_name)
    
  
  --try to impersonate. A try catch is used because we can have @name as NT groups also
  IF @is_member IS NULL
  BEGIN
    BEGIN TRY
      if( is_srvrolemember('sysadmin') = 1)
      begin
      EXECUTE AS LOGIN = @login_name -- impersonate 
        SELECT @is_member = IS_SRVROLEMEMBER(@role_name)  -- check role membership 
      REVERT -- revert back
      end
    END TRY
    BEGIN CATCH
      SELECT @is_member = 0
    END CATCH
  END
 
  RETURN ISNULL(@is_member,0)
END
go

/**************************************************************/
/* SP_VERIFY_CATEGORY_IDENTIFIERS                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_category_identifiers...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_category_identifiers', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_category_identifiers
go

CREATE PROCEDURE sp_verify_category_identifiers
   @name_of_name_parameter [varchar](60),
   @name_of_id_parameter [varchar](60),
   @category_name [sysname] OUTPUT,
   @category_id [INT] OUTPUT
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @category_id_as_char NVARCHAR(36)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name_of_name_parameter = LTRIM(RTRIM(@name_of_name_parameter))
  SELECT @name_of_id_parameter   = LTRIM(RTRIM(@name_of_id_parameter))
  SELECT @category_name          = LTRIM(RTRIM(@category_name))

  IF (@category_name = N'') SELECT @category_name = NULL

  IF ((@category_name IS NOT NULL) AND (@category_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, @name_of_id_parameter, @name_of_name_parameter)
    RETURN(1) -- Failure
  END

  -- Check category id
  IF (@category_id IS NOT NULL)
  BEGIN
    SELECT @category_name = name
    FROM msdb.dbo.syscategories
    WHERE (category_id = @category_id)
    IF (@category_name IS NULL)
    BEGIN
     SELECT @category_id_as_char = CONVERT(nvarchar(36), @category_id)
      RAISERROR(14262, -1, -1, '@category_id', @category_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check category name
  IF (@category_name IS NOT NULL)
  BEGIN
    -- The name is not ambiguous, so get the corresponding category_id (if the job exists)
    SELECT @category_id = category_id
    FROM msdb.dbo.syscategories
    WHERE (name = @category_name)
    IF (@category_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@category_name', @category_name)
      RETURN(1) -- Failure
    END
  END

  RETURN(0) -- Success
END
go

PRINT ''
PRINT 'Creating function agent_datetime...'
IF (NOT OBJECT_ID(N'dbo.agent_datetime', 'FN') IS NULL)
  DROP FUNCTION dbo.agent_datetime
go

CREATE FUNCTION agent_datetime(@date int, @time int)
RETURNS DATETIME
AS
BEGIN
 RETURN
  (
    CONVERT(DATETIME,
          CONVERT(NVARCHAR(4),@date / 10000) + N'-' + 
          CONVERT(NVARCHAR(2),(@date % 10000)/100)  + N'-' +
          CONVERT(NVARCHAR(2),@date % 100) + N' ' +        
          CONVERT(NVARCHAR(2),@time / 10000) + N':' +        
          CONVERT(NVARCHAR(2),(@time % 10000)/100) + N':' +        
          CONVERT(NVARCHAR(2),@time % 100),
    120)
  )
END
go

/**************************************************************/
/* SP_VERIFY_PROXY_IDENTIFIERS                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_proxy_identifiers...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_proxy_identifiers', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_proxy_identifiers
go

CREATE PROCEDURE sp_verify_proxy_identifiers
   @name_of_name_parameter [varchar](60),
   @name_of_id_parameter [varchar](60),
   @proxy_name [sysname] OUTPUT,
   @proxy_id [INT] OUTPUT
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @proxy_id_as_char NVARCHAR(36)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name_of_name_parameter = LTRIM(RTRIM(@name_of_name_parameter))
  SELECT @name_of_id_parameter   = LTRIM(RTRIM(@name_of_id_parameter))
  SELECT @proxy_name             = LTRIM(RTRIM(@proxy_name))

  IF (@proxy_name = N'') SELECT @proxy_name = NULL

  IF ((@proxy_name IS NULL)     AND (@proxy_id IS NULL)) OR
     ((@proxy_name IS NOT NULL) AND (@proxy_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, @name_of_id_parameter, @name_of_name_parameter)
    RETURN(1) -- Failure
  END

  -- Check proxy id
  IF (@proxy_id IS NOT NULL)
  BEGIN
    SELECT @proxy_name = name
    FROM msdb.dbo.sysproxies
    WHERE (proxy_id = @proxy_id)
    IF (@proxy_name IS NULL)
    BEGIN
     SELECT @proxy_id_as_char = CONVERT(nvarchar(36), @proxy_id)
      RAISERROR(14262, -1, -1, '@proxy_id', @proxy_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check proxy name
  IF (@proxy_name IS NOT NULL)
  BEGIN
    -- The name is not ambiguous, so get the corresponding proxy_id (if the job exists)
    SELECT @proxy_id = proxy_id
    FROM msdb.dbo.sysproxies
    WHERE (name = @proxy_name)
    IF (@proxy_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@proxy_name', @proxy_name)
      RETURN(1) -- Failure
    END
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_VERIFY_CREDENTIAL_IDENTIFIERS                           */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_verify_credential_identifiers...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_credential_identifiers', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_credential_identifiers
go

CREATE PROCEDURE sp_verify_credential_identifiers
   @name_of_name_parameter [varchar](60),
   @name_of_id_parameter [varchar](60),
   @credential_name [sysname] OUTPUT,
   @credential_id [INT] OUTPUT
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @credential_id_as_char NVARCHAR(36)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name_of_name_parameter = LTRIM(RTRIM(@name_of_name_parameter))
  SELECT @name_of_id_parameter   = LTRIM(RTRIM(@name_of_id_parameter))
  SELECT @credential_name        = LTRIM(RTRIM(@credential_name))

  IF (@credential_name = N'') SELECT @credential_name = NULL

  IF ((@credential_name IS NULL)     AND (@credential_id IS NULL)) OR
     ((@credential_name IS NOT NULL) AND (@credential_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, @name_of_id_parameter, @name_of_name_parameter)
    RETURN(1) -- Failure
  END

  -- Check credential_id
  IF (@credential_id IS NOT NULL)
  BEGIN
    SELECT @credential_name = name
    FROM master.sys.credentials
    WHERE (credential_id = @credential_id)
    IF (@credential_name IS NULL)
    BEGIN
     SELECT @credential_id_as_char = CONVERT(nvarchar(36), @credential_id)
      RAISERROR(14262, -1, -1, '@credential_id', @credential_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check proxy name
  IF (@credential_name IS NOT NULL)
  BEGIN
    -- The name is not ambiguous, so get the corresponding credential_id (if the job exists)
    SELECT @credential_id = credential_id
    FROM master.sys.credentials
    WHERE (name = @credential_name)
    IF (@credential_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@credential_name', @credential_name)
      RETURN(1) -- Failure
    END
  END

  RETURN(0) -- Success
END
go


/**************************************************************/
/* sp_verify_subsystems                                       */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_subsystems...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_subsystems', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_subsystems
go

CREATE PROCEDURE dbo.sp_verify_subsystems
   @syssubsytems_refresh_needed BIT = 0
AS
BEGIN
  SET NOCOUNT ON
   
  DECLARE @retval         INT
  DECLARE @InstRootPath nvarchar(512)
  DECLARE @VersionRootPath nvarchar(512)
  DECLARE @ComRootPath nvarchar(512)
  DECLARE @DtsRootPath nvarchar(512)
  DECLARE @SQLPSPath nvarchar(512)
  DECLARE @DTExec nvarchar(512)
  DECLARE @DTExecExists INT
  DECLARE @ToolsPath nvarchar(512)

  IF ( (@syssubsytems_refresh_needed=1) OR (NOT EXISTS(select * from syssubsystems)) )
  BEGIN
     EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\Setup', N'SQLPath', @InstRootPath OUTPUT
     IF @InstRootPath IS NULL
     BEGIN
       RAISERROR(14658, -1, -1) WITH LOG
       RETURN (1)
     END
     SELECT @InstRootPath = @InstRootPath + N'\binn\'

     EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\Microsoft Sql Server\100', N'VerSpecificRootDir', @VersionRootPath OUTPUT
     IF @VersionRootPath IS NULL
     BEGIN
       RAISERROR(14659, -1, -1) WITH LOG
       RETURN(1)
     END

     EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\Microsoft SQL Server\100\SSIS\Setup\DTSPath', N'', @DtsRootPath OUTPUT, N'no_output'
     IF (@DtsRootPath IS NOT NULL)
     BEGIN
       SELECT @DtsRootPath  = @DtsRootPath  + N'Binn\'
       SELECT @DTExec = @DtsRootPath + N'DTExec.exe'
       CREATE TABLE #t (file_exists int, is_directory int, parent_directory_exists int)
       INSERT #t EXEC xp_fileexist @DTExec
       SELECT TOP 1 @DTExecExists=file_exists from #t
       DROP TABLE #t
       IF ((@DTExecExists IS NULL) OR (@DTExecExists = 0))
         SET @DtsRootPath = NULL
     END

     SELECT @ComRootPath  = @VersionRootPath  + N'COM\'

     create table #Platform(ID int,  Name  sysname, Internal_Value int NULL, Value nvarchar(512))
     insert #Platform exec master.dbo.xp_msver 'Platform'
     if EXISTS(select * from #Platform where Value like '%64%')
     EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Wow6432Node\Microsoft\Microsoft Sql Server\100\Tools\ClientSetup', N'SQLPath', @ToolsPath OUTPUT
  else
     EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\Microsoft Sql Server\100\Tools\ClientSetup', N'SQLPath', @ToolsPath OUTPUT
     drop table #Platform
     SELECT @SQLPSPath  = @ToolsPath  + N'\Binn\SQLPS.exe'
     
     -- Procedure must start its own transaction if we don't have one already.
     DECLARE @TranCounter INT;
     SET @TranCounter = @@TRANCOUNT;
     IF @TranCounter = 0
     BEGIN
        BEGIN TRANSACTION;
     END

     -- Obtain processor count to determine maximum number of threads per subsystem
     DECLARE @xp_results TABLE
     (
     id              INT           NOT NULL,
     name            NVARCHAR(30)  COLLATE database_default NOT NULL,
     internal_value  INT           NULL,
     character_value NVARCHAR(212) COLLATE database_default NULL
     )
     INSERT INTO @xp_results
     EXECUTE master.dbo.xp_msver

     DECLARE @processor_count INT
     SELECT @processor_count = internal_value from @xp_results where id=16 -- ProcessorCount

     -- Modify database.
     BEGIN TRY

       --create subsystems
       --TSQL subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'TSQL')
       INSERT syssubsystems
       VALUES
       (
          1, N'TSQL',14556, FORMATMESSAGE(14557), FORMATMESSAGE(14557), FORMATMESSAGE(14557), FORMATMESSAGE(14557), FORMATMESSAGE(14557), 20 * @processor_count
       )

       --CmdExec subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'CmdExec')
       INSERT syssubsystems
       VALUES
       (
          3, N'CmdExec', 14550, @InstRootPath + N'SQLCMDSS.DLL',NULL,N'CmdExecStart',N'CmdEvent',N'CmdExecStop', 10 * @processor_count
       )

       --Snapshot subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'Snapshot')
       INSERT syssubsystems
       VALUES
       (
          4, N'Snapshot',   14551, @InstRootPath + N'SQLREPSS.DLL', @ComRootPath + N'SNAPSHOT.EXE', N'ReplStart',N'ReplEvent',N'ReplStop',100 * @processor_count
       )

       --LogReader subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'LogReader')
       INSERT syssubsystems
       VALUES
       (
          5, N'LogReader',  14552, @InstRootPath + N'SQLREPSS.DLL', @ComRootPath + N'logread.exe',N'ReplStart',N'ReplEvent',N'ReplStop',25 * @processor_count
       )

       --Distribution subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'Distribution')
       INSERT syssubsystems
       VALUES
       (
          6, N'Distribution',  14553, @InstRootPath + N'SQLREPSS.DLL', @ComRootPath + N'DISTRIB.EXE',N'ReplStart',N'ReplEvent',N'ReplStop',100 * @processor_count
       )

       --Merge subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'Merge')
       INSERT syssubsystems
       VALUES
       (
          7, N'Merge',   14554, @InstRootPath + N'SQLREPSS.DLL',@ComRootPath + N'REPLMERG.EXE',N'ReplStart',N'ReplEvent',N'ReplStop',100 * @processor_count
       )

       --QueueReader subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'QueueReader')
       INSERT syssubsystems
       VALUES
       (
          8, N'QueueReader',   14581, @InstRootPath + N'SQLREPSS.dll',@ComRootPath + N'qrdrsvc.exe',N'ReplStart',N'ReplEvent',N'ReplStop',100 * @processor_count
       )

       --ANALYSISQUERY subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'ANALYSISQUERY')
       INSERT syssubsystems
       VALUES
       (
          9, N'ANALYSISQUERY', 14513, @InstRootPath + N'SQLOLAPSS.DLL',NULL,N'OlapStart',N'OlapQueryEvent',N'OlapStop',100 * @processor_count
       )

       --ANALYSISCOMMAND subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'ANALYSISCOMMAND')
       INSERT syssubsystems
       VALUES
       (
          10, N'ANALYSISCOMMAND', 14514, @InstRootPath + N'SQLOLAPSS.DLL',NULL,N'OlapStart',N'OlapCommandEvent',N'OlapStop',100 * @processor_count
       )

       IF(@DtsRootPath IS NOT NULL)
       BEGIN
          --DTS subsystem
          IF (NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'SSIS') )
             INSERT syssubsystems
             VALUES
             (
                11, N'SSIS', 14538, @InstRootPath + N'SQLDTSSS.DLL',@DtsRootPath + N'DTExec.exe',N'DtsStart',N'DtsEvent',N'DtsStop',100 * @processor_count
             )
          ELSE
             UPDATE syssubsystems SET agent_exe = @DtsRootPath + N'DTExec.exe' WHERE subsystem = N'SSIS'
       END
       ELSE
       BEGIN
          IF EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'SSIS')
            DELETE FROM syssubsystems WHERE subsystem = N'SSIS' 
       END
       
       --PowerShell subsystem     
	   IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'PowerShell')
	   INSERT syssubsystems
	   VALUES
	   (
		  12, N'PowerShell', 14698, @InstRootPath + N'SQLPOWERSHELLSS.DLL', @SQLPSPath, N'PowerShellStart',N'PowerShellEvent',N'PowerShellStop',2
	   )
	   

   END TRY
   BEGIN CATCH

       DECLARE @ErrorMessage NVARCHAR(400)
       DECLARE @ErrorSeverity INT
       DECLARE @ErrorState INT

       SELECT @ErrorMessage = ERROR_MESSAGE()
       SELECT @ErrorSeverity = ERROR_SEVERITY()
       SELECT @ErrorState = ERROR_STATE()

       -- Roll back the transaction that we started if we are not nested
       IF @TranCounter = 0
       BEGIN
         ROLLBACK TRANSACTION;
       END
       -- if we are nested inside another transaction just raise the 
       -- error and let the outer transaction do the rollback
       RAISERROR (@ErrorMessage, -- Message text.
                   @ErrorSeverity, -- Severity.
                   @ErrorState -- State.
                   )
       RETURN (1)                  
     END CATCH
  END --(NOT EXISTS(select * from syssubsystems))
  
  -- commit the transaction we started
  IF @TranCounter = 0
  BEGIN
    COMMIT TRANSACTION;
  END
  
  RETURN(0) -- Success
END
go


/**************************************************************/
/* sp_verify_subsystem_identifiers                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_subsystem_identifiers...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_subsystem_identifiers', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_subsystem_identifiers
go

CREATE PROCEDURE dbo.sp_verify_subsystem_identifiers
   @name_of_name_parameter [varchar](60),
   @name_of_id_parameter [varchar](60),
   @subsystem_name [sysname] OUTPUT,
   @subsystem_id [INT] OUTPUT
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @subsystem_id_as_char NVARCHAR(36)

  SET NOCOUNT ON

  -- this call will populate subsystems table if necessary
  EXEC @retval = msdb.dbo.sp_verify_subsystems
  IF @retval <> 0
     RETURN(@retval)

  -- Remove any leading/trailing spaces from parameters
  SELECT @name_of_name_parameter = LTRIM(RTRIM(@name_of_name_parameter))
  SELECT @name_of_id_parameter   = LTRIM(RTRIM(@name_of_id_parameter))
  SELECT @subsystem_name         = LTRIM(RTRIM(@subsystem_name))

  IF (@subsystem_name = N'') SELECT @subsystem_name = NULL

  IF ((@subsystem_name IS NULL)     AND (@subsystem_id IS NULL)) OR
     ((@subsystem_name IS NOT NULL) AND (@subsystem_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, @name_of_id_parameter, @name_of_name_parameter)
    RETURN(1) -- Failure
  END

  -- Check subsystem_id
  IF (@subsystem_id IS NOT NULL)
  BEGIN
    SELECT @subsystem_name = subsystem
    FROM msdb.dbo.syssubsystems
    WHERE (subsystem_id = @subsystem_id)
    IF (@subsystem_name IS NULL)
    BEGIN
     SELECT @subsystem_id_as_char = CONVERT(nvarchar(36), @subsystem_id)
      RAISERROR(14262, -1, -1, '@subsystem_id', @subsystem_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check subsystem name
  IF (@subsystem_name IS NOT NULL)
  BEGIN
    -- Make sure Dts is translated into new subsystem's name SSIS
    IF UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS) = N'DTS'
    BEGIN
      SET @subsystem_name = N'SSIS'
    END

    -- The name is not ambiguous, so get the corresponding subsystem_id (if the subsystem exists)
    SELECT @subsystem_id = subsystem_id
    FROM msdb.dbo.syssubsystems
    WHERE (UPPER(subsystem collate SQL_Latin1_General_CP1_CS_AS) = UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS))
    IF (@subsystem_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@subsystem_name', @subsystem_name)
      RETURN(1) -- Failure
    END
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* sp_verify_login_identifiers                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_login_identifiers...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_login_identifiers', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_login_identifiers
go

CREATE PROCEDURE dbo.sp_verify_login_identifiers
   @login_name [nvarchar](256),
   @fixed_server_role [nvarchar](256),
   @msdb_role [nvarchar](256),
   @name [nvarchar](256) OUTPUT,
  @sid  varbinary(85)   OUTPUT,
   @flags   INT OUTPUT
AS
BEGIN
   DECLARE @retval         INT
    DECLARE @raise_error    bit
   SET NOCOUNT ON

   SELECT @flags = -1, @raise_error = 0
  SELECT @sid = NULL

  IF @login_name IS NOT NULL 
   BEGIN
      --check validity
      --use the new optional parameter of SUSER_SID to have a case insensitive comparation for NT users
    SELECT @sid = SUSER_SID(@login_name, 0)
      IF @sid IS NULL
      BEGIN
         RAISERROR(14520, -1, -1, @login_name)
         RETURN(1) -- Failure    
      END
      SELECT @name = @login_name, @flags = 0
   END
  
   IF COALESCE(@login_name, @fixed_server_role, @msdb_role) IS NULL
   BEGIN
      RAISERROR(14519, -1, -1)
      RETURN(1) -- Failure    
   END

  IF @fixed_server_role IS NOT NULL  AND @flags <> -1
      SELECT @raise_error = 1
   ELSE IF @fixed_server_role IS NOT NULL
   --check validity
   BEGIN
      -- IS_SRVROLEMEMBER return NULL for an invalid server role
      IF ISNULL(IS_SRVROLEMEMBER(@fixed_server_role), -1) = -1
      BEGIN
         RAISERROR(14521, -1, -1, @fixed_server_role)
         RETURN(1) -- Failure    
      END   
      SELECT @name = @fixed_server_role, @flags = 1
    SELECT @sid = SUSER_SID(@fixed_server_role)
   END
   
  IF @msdb_role IS NOT NULL  AND @flags <> -1
      SELECT @raise_error = 1
   ELSE IF @msdb_role IS NOT NULL
   BEGIN
      --check the correctness of msdb role
      IF ISNULL(IS_MEMBER(@msdb_role), -1) = -1 
      BEGIN
         RAISERROR(14522, -1, -1, @msdb_role)
         RETURN(1) -- Failure    
      END      
      SELECT @sid = sid from sys.database_principals
      WHERE  UPPER(@msdb_role collate SQL_Latin1_General_CP1_CS_AS) = UPPER(name collate SQL_Latin1_General_CP1_CS_AS)
    AND type = 'R'
    IF @sid IS NULL
      BEGIN
         RAISERROR(14522, -1, -1, @msdb_role)
         RETURN(1) -- Failure    
      END      
      SELECT @name = @msdb_role, @flags = 2
   END

   IF    @raise_error = 1
   BEGIN
      RAISERROR(14519, -1, -1)
      RETURN(1) -- Failure    
   END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_VERIFY_PROXY                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_proxy
go

CREATE PROCEDURE dbo.sp_verify_proxy
   @proxy_id [INT] = NULL,
   @proxy_name [sysname],
   @enabled [tinyint],
   @description [nvarchar](512) = NULL
AS
BEGIN
  DECLARE @return_code INT
  SET NOCOUNT ON

  -- Check if the NewName is unique
  IF (EXISTS ( SELECT *
               FROM msdb.dbo.sysproxies
               WHERE (name = @proxy_name) AND
            proxy_id <> ISNULL(@proxy_id,0) ))
  BEGIN
    RAISERROR(14261, 16, 1, '@name', @proxy_name)
    RETURN(1) -- Failure
  END

  -- Enabled must be 0 or 1
  IF (@enabled NOT IN (0, 1))
  BEGIN
    RAISERROR(14266, 16, 1, '@enabled', '0, 1')
    RETURN(1) -- Failure
  END
  
  RETURN(0)
END
go

/**************************************************************/
/* SP_ADD_PROXY                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_add_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_add_proxy
go

CREATE PROCEDURE dbo.sp_add_proxy
   @proxy_name [sysname],
   @enabled [tinyint] = 1,
   @description [nvarchar](512) = NULL,
   @credential_name [sysname] = NULL,
  @credential_id [INT] = NULL,
   @proxy_id [int] = NULL OUTPUT
AS
BEGIN
  DECLARE @retval INT
  DECLARE @full_name NVARCHAR(257) --two sysnames + \
  DECLARE @user_sid VARBINARY(85)
  DECLARE @cred_date_time DATETIME
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @proxy_name                = LTRIM(RTRIM(@proxy_name))
  SELECT @description               = LTRIM(RTRIM(@description))

  IF @proxy_name  = ''  SELECT @proxy_name  = NULL
  IF @description = ''  SELECT @description = NULL

  EXECUTE @retval = sp_verify_proxy NULL,
                                    @proxy_name,
                           @enabled,
                           @description

  IF (@retval <> 0)
     RETURN(1) -- Failure

   EXECUTE @retval = sp_verify_credential_identifiers '@credential_name',
                                                  '@credential_id',
                                                   @credential_name OUTPUT,
                                                   @credential_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure
  -- warn if the user_domain\user_name does not exist
  SELECT @full_name = credential_identity, @cred_date_time = create_date from master.sys.credentials 
  WHERE  credential_id = @credential_id
  --force case insensitive comparation for NT users
  SELECT @user_sid = SUSER_SID(@full_name,0)
  IF @user_sid IS NULL
  BEGIN
    RAISERROR(14529, -1, -1, @full_name)
    RETURN(1)
  END

  -- Finally, do the actual INSERT
  INSERT INTO msdb.dbo.sysproxies
   (
      name,
    credential_id,
      enabled,
      description,
    user_sid,
    credential_date_created
   )
   VALUES
   (
      @proxy_name,
    @credential_id,
      @enabled,
      @description,
    @user_sid,
    @cred_date_time
   )
   
   --get newly created proxy_id;
   SELECT @proxy_id = SCOPE_IDENTITY()
END
go

/**************************************************************/
/* SP_DELETE_PROXY                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_delete_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_delete_proxy
go

CREATE PROCEDURE dbo.sp_delete_proxy
   @proxy_id      int = NULL,
   @proxy_name    sysname = NULL
   -- must specify only one of above parameters to identify the proxy
AS
BEGIN
   DECLARE @retval   INT
   SET NOCOUNT ON
    
   EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                  '@proxy_id',
                                                   @proxy_name OUTPUT,
                                                   @proxy_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

   --no jobsteps should use this proxy
   IF EXISTS (SELECT * FROM sysjobsteps 
            WHERE @proxy_id = proxy_id)
   BEGIN
      RAISERROR(14518, -1, -1, @proxy_id)
      RETURN(1) -- Failure
   END

    BEGIN TRANSACTION
      --delete any association between subsystems and this proxy 
      DELETE sysproxysubsystem
      WHERE  proxy_id = @proxy_id
       
      --delete any association between logins and this proxy 
      DELETE sysproxylogin
      WHERE  proxy_id = @proxy_id

      -- delete the entry in sysproxies table
      DELETE sysproxies
      WHERE proxy_id = @proxy_id

    COMMIT
   RETURN(0)
END
go


/**************************************************************/
/* SP_UPDATE_PROXY                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_update_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_update_proxy
go

CREATE PROCEDURE dbo.sp_update_proxy
   @proxy_id [int] = NULL,
   @proxy_name [sysname] = NULL, 
   -- must specify only one of above parameter identify the proxy
   @credential_name [sysname] = NULL,
   @credential_id [INT] = NULL,
   @new_name [sysname] = NULL,
   @enabled [tinyint] = NULL,
   @description [nvarchar](512) = NULL
AS
BEGIN
   DECLARE  @x_new_name [sysname] 
   DECLARE  @x_credential_id [int] 
   DECLARE  @x_enabled [tinyint] 
   DECLARE @x_description [nvarchar](512)
   DECLARE @x_credential_date_created [datetime]
   DECLARE @user_sid VARBINARY(85)
      DECLARE @full_name [sysname] --two sysnames + \
   DECLARE @retval   INT
   SET NOCOUNT ON
    
   EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                  '@proxy_id',
                                                   @proxy_name OUTPUT,
                                                   @proxy_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  IF @credential_id IS NOT NULL OR @credential_name IS NOT NULL
  BEGIN
     EXECUTE @retval = sp_verify_credential_identifiers '@credential_name',
                                                    '@credential_id',
                                                    @credential_name OUTPUT,
                                                    @credential_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

   -- Remove any leading/trailing spaces from parameters
   SELECT @new_name                = LTRIM(RTRIM(@new_name))
   SELECT @description             = LTRIM(RTRIM(@description))
  -- Turn [nullable] empty string parameters into NULLs
  IF @new_name      = '' SELECT @new_name = NULL
  IF @description    = '' SELECT @description = NULL

  -- Set the x_ (existing) variables
  SELECT    @x_new_name      = name,
    @x_credential_id = credential_id,
      @x_enabled       = enabled,
      @x_description   = description,
    @x_credential_date_created = credential_date_created
   FROM sysproxies
   WHERE proxy_id = @proxy_id

  --get the new date from credential table
  IF  (@credential_id IS NOT NULL)
    SELECT @x_credential_date_created = create_date FROM master.sys.credentials
    WHERE  credential_id = @credential_id
        
    -- Fill out the values for all non-supplied parameters from the existing values
   IF    (@new_name      IS NULL) SELECT   @new_name          =           @x_new_name                          
   IF (@credential_id IS NULL) SELECT   @credential_id     =           @x_credential_id                                    
   IF (@enabled       IS NULL) SELECT   @enabled           =           @x_enabled                                    
   IF (@description   IS NULL) SELECT   @description       =           @x_description            

  -- warn if the user_domain\user_name does not exist
  SELECT @full_name = credential_identity from master.sys.credentials 
  WHERE  credential_id = @credential_id
  
  --force case insensitive comparation for NT users
  SELECT @user_sid = SUSER_SID(@full_name, 0)
  IF @user_sid IS NULL
  BEGIN
    RAISERROR(14529, -1, -1, @full_name)
    RETURN(1)
  END
 
  -- Finally, do the actual UPDATE
  UPDATE msdb.dbo.sysproxies
   SET
   name     =  @new_name,
   credential_id  =  @credential_id,
   user_sid =  @user_sid,
   enabled     =  @enabled,
   description =  @description,
   credential_date_created = @x_credential_date_created  --@x_ is OK in this case
   WHERE proxy_id = @proxy_id
END
go

/**************************************************************/
/* SP_SQLAGENT_IS_MEMBER                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_is_member...'
IF (NOT OBJECT_ID(N'dbo.sp_sqlagent_is_member', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_sqlagent_is_member
go

-- check if a login is member of NT group\database role
--
-- if we specify a NT group SID @login_sid should be NOT NULL
--
-- if a @role_principal_id is specified, a NULL login is allowed
-- in this case we check if the msdb database user associated
-- with the current security context is member of the specified
-- msdb database role (this allows us to verify if a particular
-- msdb database loginless msdb user is member of that msdb role)
CREATE PROCEDURE dbo.sp_sqlagent_is_member
(
  @group_sid VARBINARY(85) = NULL,
   @role_principal_id  INT = NULL, 
   @login_sid VARBINARY(85)
) 
AS
BEGIN
   DECLARE @ret_success  INT
  DECLARE @login        NVARCHAR(256)
   DECLARE @impersonated INT
  DECLARE @group_name   NVARCHAR(256)
   SELECT   @ret_success = 0 --failure  
  SELECT  @impersonated = 0

  IF (@group_sid IS NOT NULL AND @login_sid IS NULL)
    RETURN(0)

  --a sysadmin can check for every user group membership
  IF (@login_sid IS NOT NULL) AND (ISNULL(IS_SRVROLEMEMBER('sysadmin'),0) = 1)
  BEGIN
    --get login name from principal_id
    SELECT @login = SUSER_SNAME(@login_sid)
    
    IF SUSER_SNAME() <> @login
    BEGIN
       --impersonate
        EXECUTE sp_setuserbylogin @login
      SELECT @impersonated = 1
    END
  END
  
  IF @group_sid IS NOT NULL
    SELECT @group_name = SUSER_SNAME(@group_sid)
  ELSE
    SELECT @group_name = USER_NAME(@role_principal_id)
        
   -- return success, if login is member of the group, and failure if group doesnt exist or login is not member of the group
   SELECT  @ret_success = ISNULL(IS_MEMBER(@group_name),0)

   --revert to self
  IF @impersonated = 1
         EXECUTE sp_setuserbylogin

   RETURN @ret_success
END 
go

/**************************************************************/
/* sp_verify_proxy_permissions                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_proxy_permissions...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_proxy_permissions', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_proxy_permissions
go
CREATE PROCEDURE dbo.sp_verify_proxy_permissions
   @subsystem_name sysname,
   @proxy_id      INT = NULL,
   @name       NVARCHAR(256) = NULL,
   @raise_error    INT = 1,
   @allow_disable_proxy INT = 0,
   @verify_special_account INT = 0,
   @check_only_read_perm INT = 0
AS 
BEGIN
  DECLARE @retval   INT
  DECLARE @granted_sid VARBINARY(85)
  DECLARE @is_member INT
  DECLARE @is_sysadmin BIT
  DECLARE @flags TINYINT
  DECLARE @enabled TINYINT
  DECLARE @name_sid VARBINARY(85)
  DECLARE @role_from_sid sysname
  DECLARE @name_from_sid sysname
  DECLARE @is_SQLAgentOperatorRole BIT
  DECLARE @check_only_subsystem BIT
  DECLARE proxy_subsystem CURSOR LOCAL
  FOR
    SELECT p.sid, p.flags
    FROM sysproxyloginsubsystem_view p, syssubsystems s
    WHERE p.proxy_id = @proxy_id AND p.subsystem_id = s.subsystem_id
        AND UPPER(s.subsystem collate SQL_Latin1_General_CP1_CS_AS) = 
            UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS)
   
  SET NOCOUNT ON
  SELECT @retval = 1

  IF @proxy_id IS NULL
    RETURN(0)

   -- TSQL subsystem prohibited
  IF (UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS) = N'TSQL')
  BEGIN
    RAISERROR(14517, -1, -1)
    RETURN(1) -- Failure
  END
   
  --check if the date stored inside proxy still exists and match the cred create_date inside proxy
  --otherwise the credential has been tempered from outside
  --if so, disable proxy and continue execution
  --only a sysadmin caller have cross database permissions but
  --when executing by sqlagent this check will be always performed
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) 
  BEGIN
    IF NOT EXISTS(SELECT * FROM sysproxies p JOIN master.sys.credentials c ON p.credential_id = c.credential_id
            WHERE p.proxy_id = @proxy_id AND p.credential_date_created = c.create_date AND enabled=1)
    BEGIN
        UPDATE sysproxies SET enabled=0 WHERE proxy_id = @proxy_id
    END
  END
  
  --if no login has been passed check permission against the caller 
  IF @name IS NULL
    SELECT @name = SUSER_SNAME()  
    
  --check if the proxy is disable and continue or not based on
  --allow_disable_proxy
  --allow creation of a job step with a disabled proxy but
  --sqlagent always call with @allow_disable_proxy = 0
  SELECT @enabled = enabled FROM sysproxies WHERE proxy_id = @proxy_id
  IF (@enabled = 0) AND (@allow_disable_proxy = 0)
  BEGIN
    RAISERROR(14537, -1, -1, @proxy_id)
    RETURN(2) -- Failure
  END

  --we need to check permission only against subsystem in following cases
  --1. @name is sysadmin
  --2. @name is member of SQLAgentOperatorRole and @check_only_read_perm=1
  --3. @verify_special_account =1
  --sysadmin and SQLAgentOperatorRole have permission to view all proxies
  IF (@verify_special_account = 1)
    SET @check_only_subsystem = 1  
  ELSE
  BEGIN
    EXEC @is_sysadmin = sp_sqlagent_is_srvrolemember N'sysadmin', @name 
    IF (@is_sysadmin = 1)
      SET @check_only_subsystem = 1  
    ELSE
    BEGIN
      EXEC @is_SQLAgentOperatorRole = sp_sqlagent_is_srvrolemember N'SQLAgentOperatorRole', @name -- check role membership 
      IF ((@is_SQLAgentOperatorRole = 1) AND (@check_only_read_perm = 1))
        SET @check_only_subsystem = 1 
    END
  END 
  
  IF (@check_only_subsystem = 1)
  BEGIN
    IF NOT EXISTS(SELECT * FROM sysproxysubsystem sp JOIN syssubsystems s ON sp.subsystem_id = s.subsystem_id
                  WHERE proxy_id = @proxy_id  AND UPPER(s.subsystem collate SQL_Latin1_General_CP1_CS_AS) = 
                     UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS))
    BEGIN
      IF (@raise_error <> 0)
      BEGIN
        RAISERROR(14516, -1, -1, @proxy_id, @subsystem_name, @name)
      END         
      RETURN(1) -- Failure     
    END
    RETURN(0)
  END
  
  --get SID from name; we verify if a login has permission to use a certain proxy
  --force case insensitive comparation for NT users
  SELECT @name_sid = SUSER_SID(@name, 0)

  --check first if name has been granted explicit permissions
  IF (@name_sid IS NOT NULL)
  BEGIN
      IF EXISTS(SELECT * FROM sysproxyloginsubsystem_view p, syssubsystems s
        WHERE p.proxy_id = @proxy_id AND p.subsystem_id = s.subsystem_id
            AND UPPER(s.subsystem collate SQL_Latin1_General_CP1_CS_AS) = 
                UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS)
            AND
        p.sid = @name_sid) -- name has been granted explicit permissions
      BEGIN
        RETURN(0)
      END
  END

  OPEN proxy_subsystem
  FETCH NEXT FROM proxy_subsystem INTO @granted_sid, @flags
  WHILE (@@fetch_status = 0 AND @retval = 1)
  BEGIN
    IF @flags = 0 AND @granted_sid IS NOT NULL AND @name_sid IS NOT NULL -- NT GROUP 
    BEGIN
        EXEC @is_member = sp_sqlagent_is_member @group_sid = @granted_sid, @login_sid = @name_sid 
        IF @is_member = 1
          SELECT @retval = 0
    END
    ELSE IF @flags = 2 AND @granted_sid IS NOT NULL -- MSDB role (@name_sid can be null in case of a loginless user member of msdb)
    BEGIN
        DECLARE @principal_id INT
        SET @principal_id = msdb.dbo.get_principal_id(@granted_sid)
        EXEC @is_member = sp_sqlagent_is_member @role_principal_id = @principal_id, @login_sid = @name_sid 
        IF @is_member = 1
          SELECT @retval = 0
    END
    ELSE IF (@flags = 1) AND @granted_sid IS NOT NULL AND @name_sid IS NOT NULL -- FIXED SERVER Roles
    BEGIN   
      -- we have to use impersonation to check for role membership
      SELECT @role_from_sid = SUSER_SNAME(@granted_sid)
      SELECT @name_from_sid = SUSER_SNAME(@name_sid)
      EXEC   @is_member = sp_sqlagent_is_srvrolemember @role_from_sid, @name_from_sid -- check role membership 

      IF @is_member = 1
        SELECT @retval = 0
    END

    IF @retval = 1
    BEGIN
        SELECT @granted_sid = NULL
        FETCH NEXT FROM proxy_subsystem INTO @granted_sid, @flags
    END
  END
  DEALLOCATE proxy_subsystem
  
  IF (@retval = 1 AND @raise_error <> 0)
  BEGIN
    RAISERROR(14516, -1, -1, @proxy_id, @subsystem_name, @name)
    RETURN(1) -- Failure
  END

   --0 is for success
   RETURN @retval
END
go

/**************************************************************/
/* SP_HELP_PROXY                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_help_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_help_proxy
go

CREATE PROCEDURE dbo.sp_help_proxy
   @proxy_id          int           = NULL,
   @proxy_name      sysname       = NULL,
   @subsystem_name sysname       = NULL,
   @name           nvarchar(256) = NULL
AS
BEGIN
  DECLARE @retval INT
  DECLARE @subsystem_id INT
  DECLARE @cur_subsystem_name NVARCHAR(40)
  DECLARE @current_proxy_id INT
  DECLARE @not_have_permission INT
  DECLARE cur_proxy CURSOR LOCAL
  FOR
    SELECT p.proxy_id, s.subsystem
    FROM sysproxies p, syssubsystems s
    WHERE ISNULL(UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS), 
        UPPER(s.subsystem collate SQL_Latin1_General_CP1_CS_AS) ) = 
        UPPER(s.subsystem collate SQL_Latin1_General_CP1_CS_AS) AND 
        s.subsystem_id <> 1 --last is TSQL subsystem
          
  -- this call will populate subsystems table if necessary
  EXEC @retval = msdb.dbo.sp_verify_subsystems
  IF @retval <> 0
     RETURN(1) --failure
     
  --create temp table with returned rows
  DECLARE @temp_proxy TABLE
   (
      proxy_id             INT  --used to identify a proxy
   )

  SET NOCOUNT ON

  SELECT @subsystem_id = NULL

  -- Remove any leading/trailing spaces from parameters
  SELECT @proxy_name       = LTRIM(RTRIM(@proxy_name))
  IF @proxy_name           = '' SELECT @proxy_name = NULL
  SELECT @subsystem_name   = LTRIM(RTRIM(@subsystem_name))
  IF @proxy_name           = '' SELECT @proxy_name = NULL
  SELECT @name             = LTRIM(RTRIM(@name))
  IF @name                 = '' SELECT @name = NULL

  IF (@proxy_id IS NOT NULL OR @proxy_name IS NOT NULL)
  BEGIN
    EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                        '@proxy_id',
                                        @proxy_name OUTPUT,
                                        @proxy_id   OUTPUT
    IF (@retval <> 0)
        RETURN(1) -- Failure
  END

  IF @subsystem_name IS NOT NULL 
  BEGIN
    EXECUTE @retval = sp_verify_subsystem_identifiers '@subsystem_name',
                                      '@subsystem_id',
                                      @subsystem_name OUTPUT,
                                      @subsystem_id   OUTPUT
    IF (@retval <> 0)
        RETURN(1) -- Failure
  END
    
  IF (@subsystem_name IS NOT NULL AND @name IS NULL) OR
    (@subsystem_name IS NULL AND @name IS NOT NULL)
  BEGIN
    RAISERROR(14532, -1, -1, '@subsystem_name', '@name')
    RETURN(1) -- Failure
  END   
  
  --only member of sysadmin and SQLAgentOperatorRole roles can see proxies granted to somebody else
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0) AND
      (ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) = 0))
  BEGIN
    SELECT @name = SUSER_SNAME()
  END

  IF @name IS NOT NULL
  BEGIN
    OPEN cur_proxy
    FETCH NEXT FROM cur_proxy INTO @current_proxy_id, @cur_subsystem_name
    WHILE (@@fetch_status = 0)
    BEGIN
      --verify if supplied user have permission to use the current proxy for specified subsystem
      --disabled proxy should be shown as well
      IF NOT EXISTS(SELECT * FROM @temp_proxy WHERE proxy_id = @current_proxy_id)
      BEGIN
        EXECUTE @not_have_permission = sp_verify_proxy_permissions 
          @subsystem_name = @cur_subsystem_name, 
          @proxy_id = @current_proxy_id, 
          @name = @name, 
          @raise_error = 0, 
          @allow_disable_proxy = 1, 
          @verify_special_account = 0, 
          @check_only_read_perm = 1
        IF (@not_have_permission = 0) -- have permissions
            INSERT @temp_proxy VALUES(@current_proxy_id)
      END
      FETCH NEXT FROM cur_proxy INTO @current_proxy_id, @cur_subsystem_name
    END 
    CLOSE cur_proxy  
    DEALLOCATE cur_proxy
  END
  ELSE
    INSERT @temp_proxy SELECT proxy_id from sysproxies

  -- returns different result sets if caller is admin or not
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) OR
      (ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) = 1))
  BEGIN
    SELECT p.proxy_id, 
          p.name, 
          c.credential_identity, 
          p.enabled, 
          p.description, 
          p.user_sid, 
          p.credential_id,
          CASE WHEN c.credential_id IS NULL THEN 0 ELSE 1 END as credential_identity_exists
    FROM sysproxies p LEFT JOIN master.sys.credentials c ON p.credential_id = c.credential_id  
                  JOIN @temp_proxy t ON p.proxy_id = t.proxy_id
              WHERE ISNULL(@proxy_id, p.proxy_id) = p.proxy_id 
  END
  ELSE
  BEGIN
    SELECT p.proxy_id, p.name, null as credential_identity, p.enabled, p.description, null as user_sid, p.credential_id, null as credential_identity_exists 
    FROM sysproxies p, @temp_proxy t
    WHERE  ISNULL(@proxy_id, p.proxy_id) = p.proxy_id AND
              p.proxy_id = t.proxy_id
  END

END
go

/**************************************************************/
/* sp_grant_proxy_to_subsystem                             */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_grant_proxy_to_subsystem...'
IF (NOT OBJECT_ID(N'dbo.sp_grant_proxy_to_subsystem', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_grant_proxy_to_subsystem
go
CREATE PROCEDURE dbo.sp_grant_proxy_to_subsystem
   @proxy_id      int = NULL,
   @proxy_name    sysname = NULL,
   -- must specify only one of above parameter to identify the proxy
   @subsystem_id  int = NULL,
   @subsystem_name sysname = NULL
   -- must specify only one of above parameter to identify the subsystem
AS
BEGIN
   DECLARE @retval   INT
   DECLARE @proxy_account sysname
   SET NOCOUNT ON

   -- Remove any leading/trailing spaces from parameters
   SELECT @subsystem_name          = LTRIM(RTRIM(@subsystem_name))
   SELECT @proxy_name              = LTRIM(RTRIM(@proxy_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF @subsystem_name    = '' SELECT @subsystem_name = NULL
  IF @proxy_name         = '' SELECT @proxy_name = NULL
    
   EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                  '@proxy_id',
                                                   @proxy_name OUTPUT,
                                                   @proxy_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

   EXECUTE @retval = sp_verify_subsystem_identifiers '@subsystem_name',
                                                  '@subsystem_id',
                                                   @subsystem_name OUTPUT,
                                                   @subsystem_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
   

  --TSQL subsystem is prohibited
  IF @subsystem_id = 1
   BEGIN
     RAISERROR(14530, -1, -1)
     RETURN(1) -- Failure
   END

  --check if we already added an user for the pair subsystem-proxy
  IF (EXISTS(SELECT * FROM sysproxysubsystem WHERE subsystem_id = @subsystem_id
               AND proxy_id = @proxy_id))
  BEGIN
     RAISERROR(14531, -1, -1)
     RETURN(1) -- Failure
   END
  
   INSERT INTO sysproxysubsystem
   (  subsystem_id,  proxy_id )
   VALUES
   (  @subsystem_id,    @proxy_id )

END
go

/**************************************************************/
/* sp_grant_login_to_proxy                           */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_grant_login_to_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_grant_login_to_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_grant_login_to_proxy
go
CREATE PROCEDURE dbo.sp_grant_login_to_proxy
   @login_name        NVARCHAR(256) = NULL,
   @fixed_server_role NVARCHAR(256) = NULL, 
   @msdb_role         NVARCHAR(256) = NULL, 
   -- must specify only one of above parameter to identify the type of login
   @proxy_id             int           = NULL,
   @proxy_name         sysname       = NULL
   -- must specify only one of above parameter to identify the proxy
AS
BEGIN
  DECLARE @retval   INT
  DECLARE @name nvarchar(256)
  DECLARE @flags INT
  DECLARE @sid VARBINARY(85)
  DECLARE @is_sysadmin BIT
  
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @proxy_name              = LTRIM(RTRIM(@proxy_name))
  SELECT @fixed_server_role       = LTRIM(RTRIM(@fixed_server_role))
  SELECT @msdb_role               = LTRIM(RTRIM(@msdb_role))

  -- Turn [nullable] empty string parameters into NULLs
  IF @proxy_name         = '' SELECT @proxy_name = NULL
  IF @login_name         = '' SELECT @login_name = NULL
  IF @fixed_server_role  = '' SELECT @fixed_server_role = NULL
  IF @msdb_role          = '' SELECT @msdb_role  = NULL
    
  EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                 '@proxy_id',
                                                 @proxy_name OUTPUT,
                                                 @proxy_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  EXECUTE @retval = sp_verify_login_identifiers  @login_name,
                                                 @fixed_server_role,
                                                 @msdb_role,
                                                 @name OUTPUT,
                                                 @sid OUTPUT,
                                                 @flags OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure
    
  -- is login member of sysadmin role?
  SELECT @is_sysadmin = 0
  IF (@login_name IS NOT NULL)
  BEGIN
     EXEC @is_sysadmin = sp_sqlagent_is_srvrolemember N'sysadmin', @login_name -- check role membership 
  END

  IF (@is_sysadmin = 1)
  BEGIN
   -- @name is sysadmin, it cannot granted to proxy
   -- issue a message and do nothing
   RAISERROR(14395, 10, 1, @name)
  END
  ELSE
  BEGIN
   --check if we already added an user for the pair subsystem-proxy
   IF (EXISTS(SELECT * FROM sysproxylogin WHERE proxy_id = @proxy_id 
               AND ISNULL(sid, 0) = ISNULL(@sid,0) 
               AND flags = @flags))
   BEGIN
      RAISERROR(14531, -1, -1)
      RETURN(1) -- Failure
   END

   INSERT INTO sysproxylogin
      (  proxy_id, sid,  flags )
      VALUES
      ( @proxy_id, @sid, @flags)
  END
END
go

/**************************************************************/
/* sp_revoke_login_from_proxy                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_revoke_login_from_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_revoke_login_from_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_revoke_login_from_proxy
go

CREATE PROCEDURE dbo.sp_revoke_login_from_proxy
   @name         NVARCHAR(256),
   @proxy_id        INT = NULL,
   @proxy_name    sysname = NULL
   -- must specify only one of above parameter to identify the proxy
AS
BEGIN
   DECLARE @retval   INT
   DECLARE @sid VARBINARY(85)
   DECLARE @is_sysadmin BIT
   
   SET NOCOUNT ON

   -- Remove any leading/trailing spaces from parameters
   SELECT @proxy_name              = LTRIM(RTRIM(@proxy_name))
   SELECT @name                    = LTRIM(RTRIM(@name))

   -- Turn [nullable] empty string parameters into NULLs
   IF @proxy_name         = '' SELECT @proxy_name = NULL
   IF @name               = '' SELECT @name = NULL
    
   EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                  '@proxy_id',
                                                   @proxy_name OUTPUT,
                                                   @proxy_id   OUTPUT
   IF (@retval <> 0)
     RETURN(1) -- Failure
    
  -- is login member of sysadmin role?
  SELECT @is_sysadmin = 0
  IF (@name IS NOT NULL)
  BEGIN
    EXEC @is_sysadmin = sp_sqlagent_is_srvrolemember N'sysadmin', @name -- check role membership 
  END

  IF (@is_sysadmin = 1)
  BEGIN
    -- @name is sysadmin, it cannot be revoked from proxy
    -- issue a message and do nothing
    RAISERROR(14395, 10, -1, @name)
  END
  ELSE
  BEGIN  
    --force case insensitive comparation for NT users
    SELECT @sid = SUSER_SID(@name, 0)
    IF @sid IS NULL -- then @name is a MSDB role
       SELECT @sid = sid FROM sys.database_principals
       WHERE  name = @name

    --check parametrs validity
    IF (EXISTS(SELECT * FROM sysproxylogin WHERE
       proxy_id                  = @proxy_id AND
       ISNULL(sid, 0)            = ISNULL(@sid, 0)))
    BEGIN
       DELETE FROM sysproxylogin WHERE
       proxy_id     = @proxy_id AND 
       ISNULL(sid, 0)= ISNULL(@sid, 0) 
    END
    ELSE
    BEGIN
       RAISERROR(14523, -1, -1, @name, @proxy_name)
       RETURN(1) -- Failure       
    END   
  END

  RETURN(0)
END
go

/**************************************************************/
/* sp_revoke_proxy_from_subsystem                                       */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_revoke_proxy_from_subsystem...'
IF (NOT OBJECT_ID(N'dbo.sp_revoke_proxy_from_subsystem', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_revoke_proxy_from_subsystem
go

CREATE PROCEDURE dbo.sp_revoke_proxy_from_subsystem
   @proxy_id          INT = NULL,
   @proxy_name      sysname = NULL,
   -- must specify only one of above parameter to identify the proxyAS
   @subsystem_id    INT = NULL,
   @subsystem_name sysname = NULL
   -- must specify only one of above parameter to identify the subsystem
AS
BEGIN
   DECLARE @retval   INT
   DECLARE @proxy_account sysname
   SET NOCOUNT ON

   -- Remove any leading/trailing spaces from parameters
   SELECT @subsystem_name          = LTRIM(RTRIM(@subsystem_name))
   SELECT @proxy_name              = LTRIM(RTRIM(@proxy_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF @subsystem_name    = '' SELECT @subsystem_name = NULL
  IF @proxy_name         = '' SELECT @proxy_name = NULL
    
   EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                  '@proxy_id',
                                                   @proxy_name OUTPUT,
                                                   @proxy_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

   EXECUTE @retval = sp_verify_subsystem_identifiers '@subsystem_name',
                                                  '@subsystem_id',
                                                   @subsystem_name OUTPUT,
                                                   @subsystem_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure
   
   
  --check parametrs validity
   IF (EXISTS(SELECT * FROM sysproxysubsystem WHERE
      subsystem_id = @subsystem_id AND 
      proxy_id     = @proxy_id ))
      BEGIN
        DELETE FROM sysproxysubsystem WHERE
           subsystem_id = @subsystem_id AND 
           proxy_id     = @proxy_id 
      END
   ELSE
      BEGIN
         RAISERROR(14600, -1, -1, @proxy_name, @subsystem_name)
         RETURN(1) -- Failure       
      END   

   RETURN(0)

END
go

/**************************************************************/
/* SP_ENUM_PROXY_FOR_SUBSYSTEM                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_enum_proxy_for_subsystem...'
IF (NOT OBJECT_ID(N'dbo.sp_enum_proxy_for_subsystem', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_enum_proxy_for_subsystem
go

CREATE PROCEDURE sp_enum_proxy_for_subsystem
   @proxy_id      int = NULL,
   @proxy_name    sysname = NULL,
   -- must specify only one of above parameter to identify the proxy or none
   @subsystem_id  int = NULL,
   @subsystem_name sysname = NULL
   -- must specify only one of above parameter to identify the subsystem or none
AS
BEGIN
   DECLARE @retval   INT
   SET NOCOUNT ON

   -- Remove any leading/trailing spaces from parameters
   SELECT @subsystem_name          = LTRIM(RTRIM(@subsystem_name))
   SELECT @proxy_name              = LTRIM(RTRIM(@proxy_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF @subsystem_name    = '' SELECT @subsystem_name = NULL
  IF @proxy_name         = '' SELECT @proxy_name = NULL

   IF @proxy_name IS NOT NULL OR @proxy_id IS NOT NULL
   BEGIN
      EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                          '@proxy_id',
                                          @proxy_name OUTPUT,
                                          @proxy_id   OUTPUT
      IF (@retval <> 0)
   RETURN(1) -- Failure
   END

   IF @subsystem_name IS NOT NULL OR @subsystem_id IS NOT NULL
   BEGIN
      EXECUTE @retval = sp_verify_subsystem_identifiers '@subsystem_name',
                                       '@subsystem_id',
                                       @subsystem_name OUTPUT,
                                       @subsystem_id   OUTPUT
      IF (@retval <> 0)
      RETURN(1) -- Failure
   END

  SELECT ps.subsystem_id AS subsystem_id, s.subsystem AS subsystem_name, ps.proxy_id AS proxy_id, p.name AS proxy_name
   FROM sysproxysubsystem ps JOIN sysproxies p ON ps.proxy_id = p.proxy_id
  JOIN syssubsystems s ON ps.subsystem_id = s.subsystem_id
   WHERE
        ISNULL(@subsystem_id, ps.subsystem_id) = ps.subsystem_id AND
        ISNULL(@proxy_id,     ps.proxy_id    ) = ps.proxy_id     
END
go

/**************************************************************/
/* sp_enum_login_for_proxy                                           */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_enum_login_for_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_enum_login_for_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_enum_login_for_proxy
go

CREATE PROCEDURE sp_enum_login_for_proxy
   @name         NVARCHAR(256) = NULL,
   @proxy_id        INT           = NULL,
   @proxy_name    sysname       = NULL
   -- must specify only one of above parameter to identify the proxy or none
AS
BEGIN
   DECLARE @retval   INT
  DECLARE @sid VARBINARY(85)
   SET NOCOUNT ON

   -- Remove any leading/trailing spaces from parameters
   SELECT @proxy_name              = LTRIM(RTRIM(@proxy_name))
   SELECT @name                    = LTRIM(RTRIM(@name))

  -- Turn [nullable] empty string parameters into NULLs
  IF @proxy_name         = '' SELECT @proxy_name = NULL
  IF @name                 = '' SELECT @name = NULL

   IF @proxy_name IS NOT NULL OR @proxy_id IS NOT NULL
   BEGIN
      EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                          '@proxy_id',
                                          @proxy_name OUTPUT,
                                          @proxy_id   OUTPUT
     IF (@retval <> 0)
       RETURN(1) -- Failure
   END

   IF (@name IS NOT NULL) AND 
     --force case insensitive comparation for NT users
     (ISNULL(SUSER_SID(@name, 0), 0) = 0) AND
     (ISNULL(IS_SRVROLEMEMBER(@name), -1) = -1) AND
      (ISNULL(IS_MEMBER(@name), -1) = -1)
   BEGIN
            RAISERROR(14520, -1, -1, @name)
            RETURN(1) -- Failure    
   END      

  --force case insensitive comparation for NT users
  SELECT @sid = SUSER_SID(@name, 0)
  IF @sid IS NULL -- then @name is a MSDB role
    SELECT @sid = sid FROM sys.database_principals
    WHERE  name = @name

  SELECT pl.proxy_id AS proxy_id, p.name AS proxy_name, pl.flags as flags, 
  CASE pl.flags
          WHEN  0 THEN SUSER_SNAME(pl.sid)  -- SQLLOGIN, NT USER/GROUP          
          WHEN  1 THEN SUSER_SNAME(pl.sid)  -- SQL fixed server role 
          WHEN  2 THEN USER_NAME(msdb.dbo.get_principal_id(pl.sid)) -- MSDB role
          ELSE         NULL -- should never be the case
  END AS name,  pl.sid AS sid, msdb.dbo.get_principal_id(pl.sid) AS principal_id
   FROM sysproxylogin pl JOIN sysproxies p ON pl.proxy_id = p.proxy_id
   WHERE  
        COALESCE(@proxy_id,     pl.proxy_id,     0 ) = ISNULL(pl.proxy_id, 0)  AND
        COALESCE(@sid,          pl.sid,          0 ) = ISNULL(pl.sid, 0) 
END
go

/**************************************************************/
/* SP_SQLAGENT_GET_STARTUP_INFO                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_get_startup_info...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_sqlagent_get_startup_info')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_get_startup_info
go
CREATE PROCEDURE sp_sqlagent_get_startup_info
AS
BEGIN
  DECLARE @tbu INT
  DECLARE @agentAllowed INT

  SET NOCOUNT ON

  IF (ServerProperty('InstanceName') IS NULL)
  BEGIN
    EXECUTE @tbu = master.dbo.xp_qv '1338198028'
    EXECUTE @agentAllowed = master.dbo.xp_qv '2858542058'
  END
  ELSE
  BEGIN
    DECLARE @instancename NVARCHAR(128)
    SELECT @instancename = CONVERT(NVARCHAR(128), ServerProperty('InstanceName'))
    EXECUTE @tbu = master.dbo.xp_qv '1338198028', @instancename
    EXECUTE @agentAllowed = master.dbo.xp_qv '2858542058', @instancename
  END

  IF (@tbu < 0)
    SELECT @tbu = 0

  IF (@agentAllowed < 0)
    SELECT @agentAllowed = 0

  SELECT (SELECT CASE WHEN compatibility_level >= 70 THEN 1 ELSE 0 END FROM sys.databases WHERE (name = 'msdb')) AS msdb_70_compatible,
         CASE WHEN DATABASEPROPERTYEX('msdb', 'Updateability') = 'READ_ONLY' THEN 1 ELSE 0 END  AS msdb_read_only,
         ( CASE WHEN DATABASEPROPERTYEX('msdb', 'Status') = 'ONLINE' THEN 1 ELSE 0 END  &
           CASE WHEN DATABASEPROPERTYEX('msdb', 'UserAccess') = 'MULTI_USER' THEN 1 ELSE 0 END)  AS msdb_available,
         CASE ISNULL((SELECT 1 WHERE 'a' = 'A'), 0)
             WHEN 1 THEN 0
             ELSE 1
         END AS case_sensitive_server,
         (SELECT value_in_use FROM sys.configurations WHERE (name = 'user connections')) AS max_user_connection,
         CONVERT(sysname, SERVERPROPERTY('SERVERNAME')) AS sql_server_name,
         ISNULL(@tbu, 0) AS tbu,
         PLATFORM() AS platform,
         ISNULL(CONVERT(sysname, SERVERPROPERTY('INSTANCENAME')), 'MSSQLSERVER') AS instance_name ,
         CONVERT(INT, SERVERPROPERTY('ISCLUSTERED')) AS is_clustered,
         @agentAllowed AS agent_allowed

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_SQLAGENT_HAS_SERVER_ACCESS                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_has_server_access...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_sqlagent_has_server_access')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_has_server_access
go
CREATE PROCEDURE sp_sqlagent_has_server_access
  @login_name         sysname = NULL,
  @is_sysadmin_member INT     = NULL OUTPUT
AS
BEGIN
  DECLARE @has_server_access BIT
  DECLARE @is_sysadmin       BIT
  DECLARE @actual_login_name sysname
  DECLARE @cachedate         DATETIME

  SET NOCOUNT ON

  SELECT @cachedate = NULL

  -- remove expired entries from the cache
  DELETE msdb.dbo.syscachedcredentials
  WHERE  DATEDIFF(MINUTE, cachedate, GETDATE()) >= 29

  -- query the cache
  SELECT  @is_sysadmin = is_sysadmin_member,
          @has_server_access = has_server_access,
          @cachedate = cachedate
  FROM    msdb.dbo.syscachedcredentials
  WHERE   login_name = @login_name
  AND     DATEDIFF(MINUTE, cachedate, GETDATE()) < 29

  IF (@cachedate IS NOT NULL)
  BEGIN
    -- no output variable
    IF (@is_sysadmin_member IS NULL)
    BEGIN
      -- Return result row
      SELECT has_server_access = @has_server_access,
             is_sysadmin       = @is_sysadmin,
             actual_login_name = @login_name
      RETURN
    END
    ELSE
    BEGIN
      SELECT @is_sysadmin_member = @is_sysadmin
      RETURN
    END
  END -- select from cache

  -- Set defaults
  SELECT @has_server_access = 0
  SELECT @is_sysadmin = 0
  SELECT @actual_login_name = FORMATMESSAGE(14205)

  IF (@login_name IS NULL)
  BEGIN
    SELECT has_server_access = 1,
           is_sysadmin       = IS_SRVROLEMEMBER(N'sysadmin'),
           actual_login_name = SUSER_SNAME()
    RETURN
  END

  IF (@login_name LIKE '%\%')
  BEGIN
    -- Handle the LocalSystem account ('NT AUTHORITY\SYSTEM') as a special case
    IF (UPPER(@login_name collate SQL_Latin1_General_CP1_CS_AS) = N'NT AUTHORITY\SYSTEM')
    BEGIN
      IF (EXISTS (SELECT *
                  FROM master.dbo.syslogins
                  WHERE (UPPER(loginname collate SQL_Latin1_General_CP1_CS_AS) = N'NT AUTHORITY\SYSTEM')))
      BEGIN
        SELECT @has_server_access = hasaccess,
               @is_sysadmin = sysadmin,
               @actual_login_name = loginname
        FROM master.dbo.syslogins
        WHERE (UPPER(loginname collate SQL_Latin1_General_CP1_CS_AS) = N'NT AUTHORITY\SYSTEM')
      END
      ELSE
      IF (EXISTS (SELECT *
                  FROM master.dbo.syslogins
                  WHERE (UPPER(loginname collate SQL_Latin1_General_CP1_CS_AS) = N'BUILTIN\ADMINISTRATORS')))
      BEGIN
        SELECT @has_server_access = hasaccess,
               @is_sysadmin = sysadmin,
               @actual_login_name = loginname
        FROM master.dbo.syslogins
        WHERE (UPPER(loginname collate SQL_Latin1_General_CP1_CS_AS) = N'BUILTIN\ADMINISTRATORS')
      END
    END
    ELSE
    BEGIN
      -- Check if the NT login has been explicitly denied access
      IF (EXISTS (SELECT *
                  FROM master.dbo.syslogins
                  WHERE (loginname = @login_name)
                    AND (denylogin = 1)))
      BEGIN
        SELECT @has_server_access = 0,
               @is_sysadmin = sysadmin,
               @actual_login_name = loginname
        FROM master.dbo.syslogins
        WHERE (loginname = @login_name)
      END
      ELSE
      BEGIN
        -- declare table variable for storing results
        DECLARE @xp_results TABLE
        (
        account_name      sysname      COLLATE database_default NOT NULL PRIMARY KEY,
        type              NVARCHAR(10) COLLATE database_default NOT NULL,
        privilege         NVARCHAR(10) COLLATE database_default NOT NULL,
        mapped_login_name sysname      COLLATE database_default NOT NULL,
        permission_path   sysname      COLLATE database_default NULL
        )

        -- Call xp_logininfo to determine server access
        INSERT INTO @xp_results
        EXECUTE master.dbo.xp_logininfo @login_name

        SELECT @has_server_access = CASE COUNT(*)
                                      WHEN 0 THEN 0
                                      ELSE 1
                                    END
        FROM @xp_results
        SELECT @actual_login_name = mapped_login_name,
               @is_sysadmin = CASE UPPER(privilege collate SQL_Latin1_General_CP1_CS_AS)
                                WHEN 'ADMIN' THEN 1
                                ELSE 0
                             END
        FROM @xp_results
      END
    END
  END
  ELSE
  BEGIN
    -- Standard login
    IF (EXISTS (SELECT *
                FROM master.dbo.syslogins
                WHERE (loginname = @login_name)))
    BEGIN
      SELECT @has_server_access = hasaccess,
             @is_sysadmin = sysadmin,
             @actual_login_name = loginname
      FROM master.dbo.syslogins
      WHERE (loginname = @login_name)
    END
  END

  -- update the cache only if something is found
  IF  (UPPER(@actual_login_name collate SQL_Latin1_General_CP1_CS_AS) <> '(UNKNOWN)')
  BEGIN
    -- Procedure starts its own transaction.
    BEGIN TRANSACTION;
    
    -- Modify database.
    -- use a try catch login to prevent any error when trying 
    -- to insert/update syscachedcredentials table
    -- no need to fail since the job owner has been validated
    BEGIN TRY      
      IF EXISTS (SELECT * FROM msdb.dbo.syscachedcredentials WITH (TABLOCKX) WHERE login_name = @login_name)
      BEGIN
        UPDATE msdb.dbo.syscachedcredentials
        SET    has_server_access = @has_server_access,
              is_sysadmin_member = @is_sysadmin,
              cachedate = GETDATE()
        WHERE  login_name = @login_name
      END
      ELSE
      BEGIN
        INSERT INTO msdb.dbo.syscachedcredentials(login_name, has_server_access, is_sysadmin_member) 
        VALUES(@login_name, @has_server_access, @is_sysadmin)
      END
      END TRY
      BEGIN CATCH
          -- If an error occurred we want to ignore it
      END CATCH
      
      -- The procedure must commit the transaction it started.
      COMMIT TRANSACTION;  
  END
  
  IF (@is_sysadmin_member IS NULL)
    -- Return result row
    SELECT has_server_access = @has_server_access,
           is_sysadmin       = @is_sysadmin,
           actual_login_name = @actual_login_name
  ELSE
    -- output variable only
    SELECT @is_sysadmin_member = @is_sysadmin
END
go


/**************************************************************/
/* SP_SEM_ADD_MESSAGE [used by SEM only]                      */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sem_add_message...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_sem_add_message')
              AND (type = 'P')))
  DROP PROCEDURE sp_sem_add_message
go
CREATE PROCEDURE sp_sem_add_message
  @msgnum   INT           = NULL,
  @severity SMALLINT      = NULL,
  @msgtext  NVARCHAR(255) = NULL,
  @lang     sysname       = NULL, -- Message language name
  @with_log VARCHAR(5)    = 'FALSE',
  @replace  VARCHAR(7)    = NULL
AS
BEGIN
  DECLARE @retval        INT
  DECLARE @language_name sysname

  SET NOCOUNT ON

  SET ROWCOUNT 1
  SELECT @language_name = name
  FROM sys.syslanguages
  WHERE msglangid = (SELECT number
                     FROM master.dbo.spt_values
                     WHERE (type = 'LNG')
                       AND (name = @lang))
  SET ROWCOUNT 0

  SELECT @language_name = ISNULL(@language_name, 'us_english')
  EXECUTE @retval = master.dbo.sp_addmessage @msgnum, @severity, @msgtext, @language_name, @with_log, @replace
  RETURN(@retval)
END
go

/**************************************************************/
/* SP_SEM_DROP_MESSAGE [used by SEM only]                     */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sem_drop_message...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_sem_drop_message')
              AND (type = 'P')))
  DROP PROCEDURE sp_sem_drop_message
go
CREATE PROCEDURE sp_sem_drop_message
  @msgnum int     = NULL,
  @lang   sysname = NULL -- Message language name
AS
BEGIN
  DECLARE @retval        INT
  DECLARE @language_name sysname

  SET NOCOUNT ON

  SET ROWCOUNT 1
  SELECT @language_name = name
  FROM sys.syslanguages
  WHERE msglangid = (SELECT number
                     FROM master.dbo.spt_values
                     WHERE (type = 'LNG')
                       AND (name = @lang))
  SET ROWCOUNT 0

  SELECT @language_name = ISNULL(@language_name, 'us_english')
  EXECUTE @retval = master.dbo.sp_dropmessage @msgnum, @language_name
  RETURN(@retval)
END
go

/**************************************************************/
/* SP_GET_MESSAGE_DESCRIPTION [used by SEM only]              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_get_message_description...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_get_message_description')
              AND (type = 'P')))
  DROP PROCEDURE sp_get_message_description
go
CREATE PROCEDURE sp_get_message_description
  @error INT
AS
BEGIN
  IF EXISTS (SELECT * FROM master.dbo.sysmessages WHERE (error = @error) AND (msglangid = (SELECT msglangid FROM sys.syslanguages WHERE (langid = @@langid))))
    SELECT description FROM master.dbo.sysmessages WHERE (error = @error) AND (msglangid = (SELECT msglangid FROM sys.syslanguages WHERE (langid = @@langid)))
  ELSE
    SELECT description FROM master.dbo.sysmessages WHERE (error = @error) AND (msglangid = 1033)
END
go

/**************************************************************/
/* SP_SQLAGENT_GET_PERF_COUNTERS                              */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_sqlagent_get_perf_counters...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_sqlagent_get_perf_counters')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_get_perf_counters
go
CREATE PROCEDURE sp_sqlagent_get_perf_counters
  @all_counters BIT = 0
AS
BEGIN

  SET NOCOUNT ON

  -- 32 bit fraction counter types
  DECLARE @perfTypeRawFraction INT
  DECLARE @perfTypeRawBase     INT

  -- A counter of type PERF_RAW_FRACTION, which is a 32-bit counter value.
  SET @perfTypeRawFraction = 537003008 --  In hex, 0x20020400.

   -- A count of type PERF_RAW_BASE, which is the 32-bit divisor used
   -- when handling PERF_RAW_FRACTION types. This counter type should
   -- not be displayed to the user since it is used for mathematical
   -- operations.
  SET @perfTypeRawBase     = 1073939459 -- In hex, 0x40030403.


  -- 64 bit fraction counter types
  DECLARE @perfTypeLargeRawFraction INT
  DECLARE @perfTypeLargeRawBase     INT

  -- A counter of type PERF_LARGE RAW_FRACTION, which is a 64-bit counter value.
  SET @perfTypeLargeRawFraction = 537003264 --  In hex, 0x20020500.

   -- A count of type PERF_LARGE_RAW_BASE, which is the 64-bit divisor used
   -- when handling PERF_LARGE_RAW_FRACTION types. This counter type should
   -- not be displayed to the user since it is used for mathematical
   -- operations.
  SET @perfTypeLargeRawBase     = 1073939712 -- In hex, 0x40030500.



  IF (@all_counters = 0)
  BEGIN

      SELECT 'object_name' = RTRIM(SUBSTRING(spi1.object_name, 1, 50)),
             'counter_name' = RTRIM(SUBSTRING(spi1.counter_name, 1, 50)),
             'instance_name' = CASE spi1.instance_name
                             WHEN N'' THEN NULL
                             ELSE RTRIM(spi1.instance_name)
                           END,
         'value' = CASE spi1.cntr_type
                     WHEN @perfTypeRawFraction -- 32 bit fraction
                       THEN CONVERT(FLOAT, spi1.cntr_value) / (SELECT CASE spi2.cntr_value WHEN 0 THEN 1 ELSE spi2.cntr_value END
                                                               FROM sys.dm_os_performance_counters spi2
                                                               WHERE (spi1.counter_name + ' ' = SUBSTRING(spi2.counter_name, 1, PATINDEX('% Base%', spi2.counter_name)))
                                                                 AND (spi1.instance_name = spi2.instance_name)
                                                                 AND (spi2.cntr_type = @perfTypeRawBase))
                     WHEN @perfTypeLargeRawFraction  -- 64 bit fraction
                       THEN CONVERT(FLOAT, spi1.cntr_value) / (SELECT CASE spi2.cntr_value WHEN 0 THEN 1 ELSE spi2.cntr_value END
                                                               FROM sys.dm_os_performance_counters spi2
                                                               WHERE (spi1.counter_name + ' ' = SUBSTRING(spi2.counter_name, 1, PATINDEX('% Base%', spi2.counter_name)))
                                                                 AND (spi1.instance_name = spi2.instance_name)
                                                                 AND (spi2.cntr_type = @perfTypeLargeRawBase))
                     ELSE spi1.cntr_value
                   END,
       'type' = spi1.cntr_type
        
        FROM sys.dm_os_performance_counters spi1,
        (
                SELECT DISTINCT SUBSTRING(performance_condition, 1, CHARINDEX('|', performance_condition, PATINDEX('%_|_%', performance_condition) + 2) - 1)
                 as performance_condition_s  FROM msdb.dbo.sysalerts
                WHERE (performance_condition IS NOT NULL)
                AND ISNULL(event_id, 0) <> 8 -- exclude WMI events that reuse performance_condition field
                AND (enabled = 1)
        ) tmp -- We want to select only those counters that have an enabled performance sysalert
        WHERE (spi1.cntr_type <> @perfTypeRawBase)      -- ignore 32-bit denominator counter type
          AND (spi1.cntr_type <> @perfTypeLargeRawBase) -- ignore 64-bit denominator counter type
          AND (tmp.performance_condition_s = RTRIM(spi1.object_name) + '|' + RTRIM(spi1.counter_name))

  END
  ELSE
  BEGIN

    SELECT 'object_name' = RTRIM(SUBSTRING(spi1.object_name, 1, 50)),
           'counter_name' = RTRIM(SUBSTRING(spi1.counter_name, 1, 50)),
           'instance_name' = CASE spi1.instance_name
                             WHEN N'' THEN NULL
                             ELSE RTRIM(spi1.instance_name)
                           END,
         'value' = CASE spi1.cntr_type
                     WHEN @perfTypeRawFraction -- 32 bit fraction
                       THEN CONVERT(FLOAT, spi1.cntr_value) / (SELECT CASE spi2.cntr_value WHEN 0 THEN 1 ELSE spi2.cntr_value END
                                                               FROM sys.dm_os_performance_counters spi2
                                                               WHERE (spi1.counter_name + ' ' = SUBSTRING(spi2.counter_name, 1, PATINDEX('% Base%', spi2.counter_name)))
                                                                 AND (spi1.instance_name = spi2.instance_name)
                                                                 AND (spi2.cntr_type = @perfTypeRawBase))
                     WHEN @perfTypeLargeRawFraction  -- 64 bit fraction
                       THEN CONVERT(FLOAT, spi1.cntr_value) / (SELECT CASE spi2.cntr_value WHEN 0 THEN 1 ELSE spi2.cntr_value END
                                                               FROM sys.dm_os_performance_counters spi2
                                                               WHERE (spi1.counter_name + ' ' = SUBSTRING(spi2.counter_name, 1, PATINDEX('% Base%', spi2.counter_name)))
                                                                 AND (spi1.instance_name = spi2.instance_name)
                                                                 AND (spi2.cntr_type = @perfTypeLargeRawBase))
                     ELSE spi1.cntr_value
                   END,
       'type' = spi1.cntr_type
    FROM sys.dm_os_performance_counters spi1
    WHERE (spi1.cntr_type <> @perfTypeRawBase)      -- ignore 32-bit denominator counter type
      AND (spi1.cntr_type <> @perfTypeLargeRawBase) -- ignore 64-bit denominator counter type
  END

END

GO


/**************************************************************/
/* SP_SQLAGENT_NOTIFY                                         */
/*                                                            */
/* NOTE: We define this procedure here instead of in the      */
/*      'Support procedures' section because of the many      */
/*       other procedures that reference it.                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_notify...'
go

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_sqlagent_notify')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_notify
go
CREATE PROCEDURE sp_sqlagent_notify
  @op_type     NCHAR(1),                -- One of: J (Job action [refresh or start/stop]),
                                        --         S (Schedule action [refresh only])
                                        --         A (Alert action [refresh only]),
                                        --         G (Re-cache all registry settings),
                                        --         D (Dump job [or job schedule] cache to errorlog)
                                        --         P (Force an immediate poll of the MSX)
                                        --         L (Cycle log file)
                                        --         T (Test WMI parameters (namespace and query))
  @job_id      UNIQUEIDENTIFIER = NULL, -- JobID (for OpTypes 'J', 'S' and 'D')
  @schedule_id INT              = NULL, -- ScheduleID (for OpType 'S')
  @alert_id    INT              = NULL, -- AlertID (for OpType 'A')
  @action_type NCHAR(1)         = NULL, -- For 'J' one of: R (Run - no service check),
                                        --                 S (Start - with service check),
                                        --                 I (Insert),
                                        --                 U (Update),
                                        --                 D (Delete),
                                        --                 C (Stop [Cancel])
                                        -- For 'S' or 'A' one of: I (Insert),
                                        --                        U (Update),
                                        --                        D (Delete)
  @error_flag  INT              = 1,    -- Set to 0 to suppress the error from xp_sqlagent_notify if SQLServer agent is not running
  @wmi_namespace nvarchar(128) = NULL,
  @wmi_query     nvarchar(512) = NULL
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @id_as_char     VARCHAR(10)
  DECLARE @job_id_as_char VARCHAR(36)
  DECLARE @nt_user_name   NVARCHAR(100)

  SET NOCOUNT ON

  SELECT @retval = 0 -- Success

  -- Make sure that we're dealing only with uppercase characters
  SELECT @op_type     = UPPER(@op_type collate SQL_Latin1_General_CP1_CS_AS)
  SELECT @action_type = UPPER(@action_type collate SQL_Latin1_General_CP1_CS_AS)

  -- Verify operation code
  IF (CHARINDEX(@op_type, N'JSAGDPLT') = 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@op_type', 'J, S, A, G, D, P, L, T')
    RETURN(1) -- Failure
  END

  -- Check the job id for those who use it
  IF (CHARINDEX(@op_type, N'JSD') <> 0)
  BEGIN
    IF (NOT ((@op_type = N'D' OR @op_type = N'S') AND (@job_id IS NULL))) -- For 'D' and 'S' job_id is optional
    BEGIN
      IF ((@job_id IS NULL) OR
          ((@action_type <> N'D') AND NOT EXISTS (SELECT *
                                                  FROM msdb.dbo.sysjobs_view
                                                  WHERE (job_id = @job_id))))
      BEGIN
        SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)
        RAISERROR(14262, -1, -1, '@job_id', @job_id_as_char)
        RETURN(1) -- Failure
      END
    END
  END

  -- Verify 'job' action parameters
  IF (@op_type = N'J')
  BEGIN
    SELECT @alert_id = 0
    IF (@schedule_id IS NULL) SELECT @schedule_id = 0

    -- The schedule_id (if specified) is the start step
    IF ((CHARINDEX(@action_type, N'RS') <> 0) AND (@schedule_id <> 0))
    BEGIN
      IF (NOT EXISTS (SELECT *
                      FROM msdb.dbo.sysjobsteps
                      WHERE (job_id = @job_id)
                        AND (step_id = @schedule_id)))
      BEGIN
        SELECT @id_as_char = ISNULL(CONVERT(VARCHAR, @schedule_id), '(null)')
        RAISERROR(14262, -1, -1, '@schedule_id', @id_as_char)
        RETURN(1) -- Failure
      END
    END
    ELSE
      SELECT @schedule_id = 0

    IF (CHARINDEX(@action_type, N'RSIUDC') = 0)
    BEGIN
      RAISERROR(14266, -1, -1, '@action_type', 'R, S, I, U, D, C')
      RETURN(1) -- Failure
    END
  END

  -- Verify 'schedule' action parameters
  IF (@op_type = N'S')
  BEGIN
    SELECT @alert_id = 0

    IF (CHARINDEX(@action_type, N'IUD') = 0)
    BEGIN
      RAISERROR(14266, -1, -1, '@action_type', 'I, U, D')
      RETURN(1) -- Failure
    END

    IF ((@schedule_id IS NULL) OR
        ((@action_type <> N'D') AND NOT EXISTS (SELECT *
                                                FROM msdb.dbo.sysschedules
                                                WHERE (schedule_id = @schedule_id))))
    BEGIN
      SELECT @id_as_char = ISNULL(CONVERT(VARCHAR, @schedule_id), '(null)')
      RAISERROR(14262, -1, -1, '@schedule_id', @id_as_char)
      RETURN(1) -- Failure
    END
  END

  -- Verify 'alert' action parameters
  IF (@op_type = N'A')
  BEGIN
    SELECT @job_id = 0x00
    SELECT @schedule_id = 0

    IF (CHARINDEX(@action_type, N'IUD') = 0)
    BEGIN
      RAISERROR(14266, -1, -1, '@action_type', 'I, U, D')
      RETURN(1) -- Failure
    END

    IF ((@alert_id IS NULL) OR
        ((@action_type <> N'D') AND NOT EXISTS (SELECT *
                                                FROM msdb.dbo.sysalerts
                                                WHERE (id = @alert_id))))
    BEGIN
      SELECT @id_as_char = ISNULL(CONVERT(VARCHAR, @alert_id), '(null)')
      RAISERROR(14262, -1, -1, '@alert_id', @id_as_char)
      RETURN(1) -- Failure
    END
  END

  -- Verify 'registry', 'job dump' and 'force MSX poll' and 'cycle log' action parameters
  IF (CHARINDEX(@op_type, N'GDPL') <> 0)
  BEGIN
    IF (@op_type <> N'D')
      SELECT @job_id = 0x00
    SELECT @alert_id = 0
    SELECT @schedule_id = 0
    SELECT @action_type = NULL
  END

  -- Parameters are valid, so now check execution permissions...

  -- For anything except a job (or schedule) action the caller must be SysAdmin, DBO, or DB_Owner
  IF (@op_type NOT IN (N'J', N'S'))
  BEGIN
    IF NOT ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) OR
            (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1) OR
            (UPPER(USER_NAME() collate SQL_Latin1_General_CP1_CS_AS) = N'DBO'))
    BEGIN
      RAISERROR(14260, -1, -1)
      RETURN(1) -- Failure
    END
  END

  -- For a Job Action the caller must be SysAdmin, DBO, DB_Owner, or the job owner
  IF (@op_type = N'J')
  BEGIN
    IF NOT ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) OR
            (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1) OR
            (UPPER(USER_NAME() collate SQL_Latin1_General_CP1_CS_AS) = N'DBO') OR
            (EXISTS (SELECT *
                     FROM msdb.dbo.sysjobs_view
                     WHERE (job_id = @job_id))))
    BEGIN
      RAISERROR(14252, -1, -1)
      RETURN(1) -- Failure
    END
  END

  --verify WMI parameters
  IF (@op_type = N'T')
  BEGIN
   SELECT @wmi_namespace = LTRIM(RTRIM(@wmi_namespace))
   SELECT @wmi_query = LTRIM(RTRIM(@wmi_query))  
    IF (@wmi_namespace IS NULL) or (@wmi_query IS NULL)
   BEGIN
          RAISERROR(14508, 16, 1)
          RETURN(1) -- Failure      
   END
  END

  -- Ok, let's do it...
  SELECT @nt_user_name = ISNULL(NT_CLIENT(), ISNULL(SUSER_SNAME(), FORMATMESSAGE(14205)))
  EXECUTE @retval = master.dbo.xp_sqlagent_notify @op_type, @job_id, @schedule_id, @alert_id, @action_type, @nt_user_name, @error_flag, @@trancount, @wmi_namespace, @wmi_query

  RETURN(@retval)
END
go

/**************************************************************/
/* SP_IS_SQLAGENT_STARTING                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_is_sqlagent_starting...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_is_sqlagent_starting')
              AND (type = 'P')))
  DROP PROCEDURE sp_is_sqlagent_starting
go
CREATE PROCEDURE sp_is_sqlagent_starting
AS
BEGIN
  DECLARE @retval INT

  SELECT @retval = 0
  EXECUTE master.dbo.xp_sqlagent_is_starting @retval OUTPUT
  IF (@retval = 1)
    RAISERROR(14258, -1, -1)

  RETURN(@retval)
END
go


/**************************************************************/
/* SP_VERIFY_JOB_IDENTIFIERS                                  */
/*                                                            */
/* NOTE: We define this procedure here instead of in the      */
/*      'Support procedures' section because of the many      */
/*       other procedures that reference it.                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_job_identifiers...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_job_identifiers')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_job_identifiers
go
CREATE PROCEDURE sp_verify_job_identifiers
  @name_of_name_parameter  VARCHAR(60),             -- Eg. '@job_name'
  @name_of_id_parameter    VARCHAR(60),             -- Eg. '@job_id'
  @job_name                sysname          OUTPUT, -- Eg. 'My Job'
  @job_id                  UNIQUEIDENTIFIER OUTPUT,
  @sqlagent_starting_test  VARCHAR(7) = 'TEST',      -- By default we DO want to test if SQLServerAgent is running (caller should specify 'NO_TEST' if not desired)
  @owner_sid                VARBINARY(85) = NULL OUTPUT  
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @job_id_as_char VARCHAR(36)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name_of_name_parameter = LTRIM(RTRIM(@name_of_name_parameter))
  SELECT @name_of_id_parameter   = LTRIM(RTRIM(@name_of_id_parameter))
  SELECT @job_name               = LTRIM(RTRIM(@job_name))

  IF (@job_name = N'') SELECT @job_name = NULL

  IF ((@job_name IS NULL)     AND (@job_id IS NULL)) OR
     ((@job_name IS NOT NULL) AND (@job_id IS NOT NULL))
  BEGIN
    RAISERROR(14294, -1, -1, @name_of_id_parameter, @name_of_name_parameter)
    RETURN(1) -- Failure
  END

  -- Check job id
  IF (@job_id IS NOT NULL)
  BEGIN
    SELECT @job_name = name,
           @owner_sid = owner_sid
    FROM msdb.dbo.sysjobs_view
    WHERE (job_id = @job_id)
    
    -- the view would take care of all the permissions issues.
    IF (@job_name IS NULL) 
    BEGIN
      SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)
      RAISERROR(14262, -1, -1, '@job_id', @job_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check job name
  IF (@job_name IS NOT NULL)
  BEGIN
    -- Check if the job name is ambiguous
    IF ((SELECT COUNT(*)
         FROM msdb.dbo.sysjobs_view
         WHERE (name = @job_name)) > 1)
    BEGIN
      RAISERROR(14293, -1, -1, @job_name, @name_of_id_parameter, @name_of_name_parameter)
      RETURN(1) -- Failure
    END

    -- The name is not ambiguous, so get the corresponding job_id (if the job exists)
    SELECT @job_id = job_id,
           @owner_sid = owner_sid
    FROM msdb.dbo.sysjobs_view
    WHERE (name = @job_name)
    
    -- the view would take care of all the permissions issues.
    IF (@job_id IS NULL) 
    BEGIN
      RAISERROR(14262, -1, -1, '@job_name', @job_name)
      RETURN(1) -- Failure
    END
  END

  IF (@sqlagent_starting_test = 'TEST')
  BEGIN
    -- Finally, check if SQLServerAgent is in the process of starting and if so prevent the
    -- calling SP from running
    EXECUTE @retval = msdb.dbo.sp_is_sqlagent_starting
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_VERIFY_SCHEDULE_IDENTIFIERS                             */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_schedule_identifiers...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_schedule_identifiers')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_schedule_identifiers
go
CREATE PROCEDURE sp_verify_schedule_identifiers
  @name_of_name_parameter   VARCHAR(60),             -- Eg. '@schedule_name'
  @name_of_id_parameter     VARCHAR(60),             -- Eg. '@schedule_id'
  @schedule_name            sysname             OUTPUT, 
  @schedule_id              INT                 OUTPUT,
  @owner_sid                VARBINARY(85)       OUTPUT,
  @orig_server_id           INT                 OUTPUT,
  @job_id_filter            UNIQUEIDENTIFIER    = NULL
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @schedule_id_as_char VARCHAR(36)
  DECLARE @sch_name_count INT

  SET NOCOUNT ON
  
  -- Remove any leading/trailing spaces from parameters
  SELECT @name_of_name_parameter = LTRIM(RTRIM(@name_of_name_parameter))
  SELECT @name_of_id_parameter   = LTRIM(RTRIM(@name_of_id_parameter))
  SELECT @schedule_name          = LTRIM(RTRIM(@schedule_name))
  SELECT @sch_name_count         = 0
  

  IF (@schedule_name = N'') SELECT @schedule_name = NULL

  IF ((@schedule_name IS NULL)     AND (@schedule_id IS NULL)) OR
     ((@schedule_name IS NOT NULL) AND (@schedule_id IS NOT NULL))
  BEGIN
    RAISERROR(14373, -1, -1, @name_of_id_parameter, @name_of_name_parameter)
    RETURN(1) -- Failure
  END

  -- Check schedule id
  IF (@schedule_id IS NOT NULL)
  BEGIN
    -- if Agent is calling look in all schedules not just the local server schedules
    if(PROGRAM_NAME() LIKE N'SQLAgent%')
    BEGIN
        -- Look at all schedules
        SELECT @schedule_name   = name,
           @owner_sid           = owner_sid,
           @orig_server_id      = originating_server_id
        FROM msdb.dbo.sysschedules
        WHERE (schedule_id = @schedule_id)
    END
    ELSE
    BEGIN
        --Look at local schedules only
        SELECT @schedule_name   = name,
           @owner_sid           = owner_sid,
           @orig_server_id      = originating_server_id
        FROM msdb.dbo.sysschedules_localserver_view
        WHERE (schedule_id = @schedule_id)
    END

    IF (@schedule_name IS NULL)
    BEGIN
     --If the schedule is from an MSX and a sysadmin is calling report a specific 'MSX' message
      IF(PROGRAM_NAME() NOT LIKE N'SQLAgent%' AND
         ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1 AND
         EXISTS(SELECT * 
                FROM msdb.dbo.sysschedules as sched
                  JOIN msdb.dbo.sysoriginatingservers_view as svr
                    ON sched.originating_server_id = svr.originating_server_id
                WHERE (schedule_id = @schedule_id) AND 
                      (svr.master_server = 1)))
     BEGIN
       RAISERROR(14274, -1, -1)
     END
      ELSE  
      BEGIN
        SELECT @schedule_id_as_char = CONVERT(VARCHAR(36), @schedule_id)
        RAISERROR(14262, -1, -1, '@schedule_id', @schedule_id_as_char)
      END

      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check job name
  IF (@schedule_name IS NOT NULL)
  BEGIN
    -- if a job_id is supplied use it as a filter. This helps with V8 legacy support
    IF(@job_id_filter IS NOT NULL)
    BEGIN
        -- Check if the job name is ambiguous and also get the schedule_id optimistically.
        -- If the name is not ambiguous this gets the corresponding schedule_id (if the schedule exists)
        SELECT @sch_name_count = COUNT(*),
               @schedule_id    = MIN(s.schedule_id),
               @owner_sid      = MIN(owner_sid),
               @orig_server_id = MIN(originating_server_id)
        FROM msdb.dbo.sysschedules_localserver_view as s
          JOIN msdb.dbo.sysjobschedules as js 
            ON s.schedule_id = js.schedule_id
        WHERE (name = @schedule_name) AND
              (js.job_id = @job_id_filter)
    END
    ELSE
    BEGIN
      -- Check if the job name is ambiguous from the count(*) result
        -- If the name is not ambiguous it is safe use the fields returned by the MIN() function
        SELECT @sch_name_count = COUNT(*),
         @schedule_id     = MIN(schedule_id),
            @owner_sid       = MIN(owner_sid),
            @orig_server_id  = MIN(originating_server_id)
        FROM msdb.dbo.sysschedules_localserver_view
        WHERE (name = @schedule_name)
    END

    IF(@sch_name_count > 1)
    BEGIN
        -- ambiguous, user needs to use a schedule_id instead of a schedule_name
        RAISERROR(14371, -1, -1, @schedule_name, @name_of_id_parameter, @name_of_name_parameter)
        RETURN(1) -- Failure
    END

    --schedule_id isn't visible to this user or doesn't exist
    IF (@schedule_id IS NULL)
    BEGIN
      --If the schedule is from an MSX and a sysadmin is calling report a specific 'MSX' message
      IF(PROGRAM_NAME() NOT LIKE N'SQLAgent%' AND
         ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1 AND
         EXISTS(SELECT * 
                FROM msdb.dbo.sysschedules as sched
                  JOIN msdb.dbo.sysoriginatingservers_view as svr
                    ON sched.originating_server_id = svr.originating_server_id
                  JOIN msdb.dbo.sysjobschedules as js 
                    ON sched.schedule_id = js.schedule_id
                WHERE (svr.master_server = 1) AND
                      (name = @schedule_name) AND
                      ((@job_id_filter IS NULL) OR (js.job_id = @job_id_filter))))
     BEGIN
       RAISERROR(14274, -1, -1)
     END
      ELSE
      BEGIN
        --If not a MSX schedule raise local error
        RAISERROR(14262, -1, -1, '@schedule_name', @schedule_name)
      END

      RETURN(1) -- Failure
    END
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_VERIFY_JOBPROC_CALLER                                   */
/*                                                            */
/* NOTE: We define this procedure here instead of in the      */
/*      'Support procedures' section because of the many      */
/*       other procedures that reference it.                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_jobproc_caller...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_jobproc_caller')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_jobproc_caller
go
CREATE PROCEDURE sp_verify_jobproc_caller
  @job_id       UNIQUEIDENTIFIER,
  @program_name sysname
AS
BEGIN
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @program_name = LTRIM(RTRIM(@program_name))

  IF (EXISTS (SELECT    *
              FROM      msdb.dbo.sysjobs_view
              WHERE     (job_id = @job_id)
              AND       (master_server = 1) )) -- master_server = 1 filters on MSX jobs in this TSX server
              AND       (PROGRAM_NAME() NOT LIKE @program_name)
  BEGIN
    RAISERROR(14274, -1, -1)
    RETURN(1) -- Failure
  END

  RETURN(0)
END
go

/**************************************************************/
/* SP_DOWNLOADED_ROW_LIMITER                                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_downloaded_row_limiter...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_downloaded_row_limiter')
              AND (type = 'P')))
  DROP PROCEDURE dbo.sp_downloaded_row_limiter
go
CREATE PROCEDURE sp_downloaded_row_limiter
  @server_name sysname -- Target server name
AS
BEGIN
  -- This trigger controls how many downloaded (status = 1) sysdownloadlist rows exist
  -- for any given server.  It does NOT control the absolute number of rows in the table.

  DECLARE @current_rows_per_server INT
  DECLARE @max_rows_per_server     INT -- This value comes from the resgistry (DownloadedMaxRows)
  DECLARE @rows_to_delete          INT
  DECLARE @quoted_server_name      NVARCHAR(514) -- enough room to accomodate the quoted name
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @server_name = LTRIM(RTRIM(@server_name))

  -- Check the server name (if it's bad we fail silently)
  IF (@server_name IS NULL) OR
     (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysdownloadlist
                  WHERE (target_server = @server_name)))
    RETURN(1) -- Failure

  SELECT @max_rows_per_server = 0

  -- Get the max-rows-per-server from the registry
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'DownloadedMaxRows',
                                         @max_rows_per_server OUTPUT,
                                         N'no_output'

  -- Check if we are limiting sysdownloadlist rows
  IF (ISNULL(@max_rows_per_server, -1) = -1)
    RETURN

  -- Check that max_rows_per_server is >= 0
  IF (@max_rows_per_server < -1)
  BEGIN
    -- It isn't, so default to 100 rows
    SELECT @max_rows_per_server = 100
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'DownloadedMaxRows',
                                            N'REG_DWORD',
                                            @max_rows_per_server
  END

  -- Get the number of downloaded rows in sysdownloadlist for the target server in question
  -- NOTE: Determining this [quickly] requires a [non-clustered] index on target_server
  SELECT @current_rows_per_server = COUNT(*)
  FROM msdb.dbo.sysdownloadlist
  WHERE (target_server = @server_name)
    AND (status = 1)

  -- Delete the oldest downloaded row(s) for the target server in question if the new row has
  -- pushed us over the per-server row limit
  SELECT @rows_to_delete = @current_rows_per_server - @max_rows_per_server
  IF (@rows_to_delete > 0)
  BEGIN
    WITH RowsToDelete AS (
      SELECT TOP (@rows_to_delete) *
      FROM msdb.dbo.sysdownloadlist
      WHERE (target_server = @server_name)
        AND (status = 1)
      ORDER BY instance_id
    )
    DELETE FROM RowsToDelete;
  END
END
go

/**************************************************************/
/* SP_POST_MSX_OPERATION                                      */
/*                                                            */
/* NOTE: We define this procedure here instead of in the      */
/*      'Support procedures' section because of the many      */
/*       other procedures that reference it.                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_post_msx_operation...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_post_msx_operation')
              AND (type = 'P')))
  DROP PROCEDURE sp_post_msx_operation
go
CREATE PROCEDURE sp_post_msx_operation
  @operation              VARCHAR(64),
  @object_type            VARCHAR(64)       = 'JOB',-- Can be JOB, SERVER or SCHEDULE
  @job_id                 UNIQUEIDENTIFIER  = NULL, -- NOTE: 0x00 means 'ALL' jobs
  @specific_target_server sysname           = NULL,
  @value                  INT               = NULL, -- For polling interval value
  @schedule_uid           UNIQUEIDENTIFIER  = NULL  -- schedule_uid if the @object_type = 'SCHEDULE'
AS
BEGIN
  DECLARE @operation_code            INT
  DECLARE @specific_target_server_id INT
  DECLARE @instructions_posted       INT
  DECLARE @job_id_as_char            VARCHAR(36)
  DECLARE @schedule_uid_as_char      VARCHAR(36)
  DECLARE @msx_time_zone_adjustment  INT
  DECLARE @local_machine_name        sysname
  DECLARE @retval                    INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @operation              = LTRIM(RTRIM(@operation))
  SELECT @object_type            = LTRIM(RTRIM(@object_type))
  SELECT @specific_target_server = LTRIM(RTRIM(@specific_target_server))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@specific_target_server = N'') SELECT @specific_target_server = NULL

  -- Only a sysadmin can do this, but fail silently for a non-sysadmin
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) 
    RETURN(0) -- Success (or more accurately a no-op)

  -- Check operation
  SELECT @operation = UPPER(@operation collate SQL_Latin1_General_CP1_CS_AS)
  SELECT @operation_code = CASE @operation
                             WHEN 'INSERT'    THEN 1
                             WHEN 'UPDATE'    THEN 2
                             WHEN 'DELETE'    THEN 3
                             WHEN 'START'     THEN 4
                             WHEN 'STOP'      THEN 5
                             WHEN 'RE-ENLIST' THEN 6
                             WHEN 'DEFECT'    THEN 7
                             WHEN 'SYNC-TIME' THEN 8
                             WHEN 'SET-POLL'  THEN 9
                             ELSE 0
                           END
  IF (@operation_code = 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@operation_code', 'INSERT, UPDATE, DELETE, START, STOP, RE-ENLIST, DEFECT, SYNC-TIME, SET-POLL')
    RETURN(1) -- Failure
  END

  -- Check object type (in 9.0 only 'JOB', 'SERVER' or 'SCHEDULE'are valid)
  IF ((@object_type <> 'JOB') AND (@object_type <> 'SERVER') AND (@object_type <> 'SCHEDULE'))
  BEGIN
    RAISERROR(14266, -1, -1, '@object_type', 'JOB, SERVER, SCHEDULE')
    RETURN(1) -- Failure
  END

  -- Check that for a object type of JOB a job_id has been supplied
  IF ((@object_type = 'JOB') AND (@job_id IS NULL))
  BEGIN
    RAISERROR(14233, -1, -1)
    RETURN(1) -- Failure
  END
  
    -- Check that for a object type of JOB a job_id has been supplied
  IF ((@object_type = 'SCHEDULE') AND (@schedule_uid IS NULL))
  BEGIN
    RAISERROR(14365, -1, -1)
    RETURN(1) -- Failure
  END

  -- Check polling interval value
  IF (@operation_code = 9) AND ((ISNULL(@value, 0) < 10) OR (ISNULL(@value, 0) > 28800))
  BEGIN
    RAISERROR(14266, -1, -1, '@value', '10..28800')
    RETURN(1) -- Failure
  END

  -- Check specific target server
  IF (@specific_target_server IS NOT NULL)
  BEGIN
    SELECT @specific_target_server = UPPER(@specific_target_server)

    -- Check if the local server is being targeted
    IF (@specific_target_server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))))
    BEGIN
      RETURN(0)
    END
    ELSE
    BEGIN
      SELECT @specific_target_server_id = server_id
      FROM msdb.dbo.systargetservers
      WHERE (UPPER(server_name) = @specific_target_server)
      IF (@specific_target_server_id IS NULL)
      BEGIN
        RAISERROR(14262, -1, -1, '@specific_target_server', @specific_target_server)
        RETURN(1) -- Failure
      END
    END
  END

  -- Check that this server is an MSX server
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.systargetservers) = 0)
  BEGIN
    RETURN(0)
  END

  -- Get local machine name
  EXECUTE @retval = master.dbo.xp_getnetname @local_machine_name OUTPUT
  IF (@retval <> 0) OR (@local_machine_name IS NULL)
  BEGIN
    RAISERROR(14225, -1, -1)
    RETURN(1)
  END

  -- Job-specific processing...
  IF (@object_type = 'JOB')
  BEGIN
    -- Validate the job (if supplied)
    IF (@job_id <> CONVERT(UNIQUEIDENTIFIER, 0x00))
    BEGIN
      SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)

      -- Check if the job exists
      IF (NOT EXISTS (SELECT *
                      FROM msdb.dbo.sysjobs_view
                      WHERE (job_id = @job_id)))
      BEGIN
        RAISERROR(14262, -1, -1, '@job_id', @job_id_as_char)
        RETURN(1) -- Failure
      END

      -- If this is a local job then there's nothing for us to do
      IF (EXISTS (SELECT *
                  FROM msdb.dbo.sysjobservers
                  WHERE (job_id = @job_id)
                    AND (server_id = 0))) -- 0 means local server
      OR (NOT EXISTS (SELECT *
                      FROM msdb.dbo.sysjobservers
                      WHERE (job_id = @job_id)))
      BEGIN
        RETURN(0)
      END
    END

    -- Generate the sysdownloadlist row(s)...
    IF (@operation_code = 1) OR  -- Insert
       (@operation_code = 2) OR  -- Update
       (@operation_code = 3) OR  -- Delete
       (@operation_code = 4) OR  -- Start
       (@operation_code = 5)     -- Stop
    BEGIN
      IF (@job_id = CONVERT(UNIQUEIDENTIFIER, 0x00)) -- IE. 'ALL'
      BEGIN
        -- All jobs

        -- Handle DELETE as a special case (rather than posting 1 instruction per job we just
        -- post a single instruction that means 'delete all jobs from the MSX')
        IF (@operation_code = 3)
        BEGIN
          INSERT INTO msdb.dbo.sysdownloadlist
                (source_server,
                 operation_code,
                 object_type,
                 object_id,
                 target_server)
          SELECT @local_machine_name,
                 @operation_code,
                 1,                -- 1 means 'JOB'
                 CONVERT(UNIQUEIDENTIFIER, 0x00),
                 sts.server_name
          FROM systargetservers sts
          WHERE ((@specific_target_server_id IS NULL) OR (sts.server_id = @specific_target_server_id))
            AND ((SELECT COUNT(*)
                  FROM msdb.dbo.sysjobservers
                  WHERE (server_id = sts.server_id)) > 0)
          SELECT @instructions_posted = @@rowcount
        END
        ELSE
        BEGIN
          INSERT INTO msdb.dbo.sysdownloadlist
                (source_server,
                 operation_code,
                 object_type,
                 object_id,
                 target_server)
          SELECT @local_machine_name,
                 @operation_code,
                 1,                -- 1 means 'JOB'
                 sjv.job_id,
                 sts.server_name
          FROM sysjobs_view     sjv,
               sysjobservers    sjs,
               systargetservers sts
          WHERE (sjv.job_id = sjs.job_id)
            AND (sjs.server_id = sts.server_id)
            AND (sjs.server_id <> 0) -- We want to exclude local jobs
            AND ((@specific_target_server_id IS NULL) OR (sjs.server_id = @specific_target_server_id))
          SELECT @instructions_posted = @@rowcount
        END
      END
      ELSE
      BEGIN
        -- Specific job (ie. @job_id is not 0x00)
        INSERT INTO msdb.dbo.sysdownloadlist
              (source_server,
               operation_code,
               object_type,
               object_id,
               target_server,
               deleted_object_name)
        SELECT @local_machine_name,
               @operation_code,
               1,                -- 1 means 'JOB'
               sjv.job_id,
               sts.server_name,
               CASE @operation_code WHEN 3 -- Delete
                                      THEN sjv.name
                                      ELSE NULL
                                    END
        FROM sysjobs_view     sjv,
             sysjobservers    sjs,
             systargetservers sts
        WHERE (sjv.job_id = @job_id)
          AND (sjv.job_id = sjs.job_id)
          AND (sjs.server_id = sts.server_id)
          AND (sjs.server_id <> 0) -- We want to exclude local jobs
          AND ((@specific_target_server_id IS NULL) OR (sjs.server_id = @specific_target_server_id))
        SELECT @instructions_posted = @@rowcount
      END
    END
    ELSE
    BEGIN
      RAISERROR(14266, -1, -1, '@operation_code', 'INSERT, UPDATE, DELETE, START, STOP')
      RETURN(1) -- Failure
    END
  END
  
  
  -- SCHEDULE specific processing for INSERT, UPDATE or DELETE schedule operations
  -- All msx jobs that use the specified @schedule_uid will be notified with an Insert operation. 
  -- This will cause agent to reload all schedules for each job. 
  -- This is compatible with the legacy shiloh servers that don't know about reusable schedules
  IF (@object_type = 'SCHEDULE')
  BEGIN
    -- Validate the schedule
    -- Check if the schedule exists
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysschedules_localserver_view
                    WHERE (schedule_uid = @schedule_uid)))
    BEGIN
      SELECT @schedule_uid_as_char = CONVERT(VARCHAR(36), @schedule_uid)
      
      RAISERROR(14262, -1, -1, '@schedule_uid', @schedule_uid_as_char)
      RETURN(1) -- Failure
    END

    -- If this schedule is only used locally (no target servers) then there's nothing to do
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysschedules    s,
                        msdb.dbo.sysjobschedules  js,
                        msdb.dbo.sysjobs_view     sjv,
                        msdb.dbo.sysjobservers    sjs,
                        msdb.dbo.systargetservers sts
                    WHERE (s.schedule_uid = @schedule_uid)
                    AND (s.schedule_id = js.schedule_id)
                    AND (sjv.job_id = js.job_id)
                    AND (sjv.job_id = sjs.job_id)
                    AND (sjs.server_id = sts.server_id)
                    AND (sjs.server_id <> 0)))                        
    BEGIN
      RETURN(0)
    END

    -- Generate the sysdownloadlist row(s)...
    IF (@operation_code = 1) OR  -- Insert
       (@operation_code = 2) OR  -- Update
       (@operation_code = 3)     -- Delete
    BEGIN
      -- Insert specific schedule into sysdownloadlist 
      -- We need to create a sysdownloadlist JOB INSERT record for each job that runs the schedule
     INSERT INTO msdb.dbo.sysdownloadlist
         (source_server,
          operation_code,
          object_type,
          object_id,
          target_server)
     SELECT @local_machine_name,
          1,             -- 1 means 'Insert'
          1,             -- 1 means 'JOB'
          sjv.job_id,
          sts.server_name
     FROM msdb.dbo.sysschedules     s,
           msdb.dbo.sysjobschedules  js,
           msdb.dbo.sysjobs_view     sjv,
         msdb.dbo.sysjobservers    sjs,
         systargetservers          sts
     WHERE (s.schedule_id = js.schedule_id)
        AND (js.job_id = sjv.job_id)
        AND (sjv.job_id = sjs.job_id)
      AND (sjs.server_id = sts.server_id)
        AND (s.schedule_uid = @schedule_uid)
      AND (sjs.server_id <> 0)            -- We want to exclude local jobs
      AND ((@specific_target_server_id IS NULL) OR (sjs.server_id = @specific_target_server_id))

      SELECT @instructions_posted = @@rowcount


    END
    ELSE
    BEGIN
      RAISERROR(14266, -1, -1, '@operation_code', 'UPDATE, DELETE')
      RETURN(1) -- Failure
    END
  END
  

  -- Server-specific processing...
  IF (@object_type = 'SERVER')
  BEGIN
    -- Generate the sysdownloadlist row(s)...
    IF (@operation_code = 6) OR  -- ReEnlist
       (@operation_code = 7) OR  -- Defect
       (@operation_code = 8) OR  -- Synchronize time (with MSX)
       (@operation_code = 9)     -- Set MSX polling interval (in seconds)
    BEGIN
      IF (@operation_code = 8)
      BEGIN
        EXECUTE master.dbo.xp_regread N'HKEY_LOCAL_MACHINE',
                                      N'SYSTEM\CurrentControlSet\Control\TimeZoneInformation',
                                      N'Bias',
                                      @msx_time_zone_adjustment OUTPUT,
                                      N'no_output'
        SELECT @msx_time_zone_adjustment = -ISNULL(@msx_time_zone_adjustment, 0)
      END

      INSERT INTO msdb.dbo.sysdownloadlist
            (source_server,
             operation_code,
             object_type,
             object_id,
             target_server)
      SELECT @local_machine_name,
             @operation_code,
             2,                  -- 2 means 'SERVER'
             CASE @operation_code
               WHEN 8 THEN CONVERT(UNIQUEIDENTIFIER, CONVERT(BINARY(16), -(@msx_time_zone_adjustment - sts.time_zone_adjustment)))
               WHEN 9 THEN CONVERT(UNIQUEIDENTIFIER, CONVERT(BINARY(16), @value))
               ELSE CONVERT(UNIQUEIDENTIFIER, 0x00)
             END,
             sts.server_name
      FROM systargetservers sts
      WHERE ((@specific_target_server_id IS NULL) OR (sts.server_id = @specific_target_server_id))
      SELECT @instructions_posted = @@rowcount
    END
    ELSE
    BEGIN
      RAISERROR(14266, -1, -1, '@operation_code', 'RE-ENLIST, DEFECT, SYNC-TIME, SET-POLL')
      RETURN(1) -- Failure
    END
  END


  -- Report number of rows inserted
  IF (@object_type = 'JOB') AND
     (@job_id = CONVERT(UNIQUEIDENTIFIER, 0x00)) AND
     (@instructions_posted = 0) AND
     (@specific_target_server_id IS NOT NULL)
    RAISERROR(14231, 0, 1, '@specific_target_server', @specific_target_server)
  ELSE
    RAISERROR(14230, 0, 1, @instructions_posted, @operation)

  -- Delete any [downloaded] instructions that are over the registry-defined limit
  IF (@specific_target_server IS NOT NULL)
    EXECUTE msdb.dbo.sp_downloaded_row_limiter @specific_target_server

  RETURN(0) -- 0 means success
END
go

/**************************************************************/
/* SP_VERIFY_PERFORMANCE_CONDITION                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_performance_condition...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_performance_condition')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_performance_condition
go
CREATE PROCEDURE sp_verify_performance_condition
  @performance_condition NVARCHAR(512)
AS
BEGIN
  DECLARE @delimiter_count INT
  DECLARE @temp_str        NVARCHAR(512)
  DECLARE @object_name     sysname
  DECLARE @counter_name    sysname
  DECLARE @instance_name   sysname
  DECLARE @pos             INT

  SET NOCOUNT ON
  
  -- The performance condition must have the format 'object|counter|instance|comparator|value'
  -- NOTE: 'instance' may be empty.
  IF (PATINDEX(N'%_|%_|%|[><=]|[0-9]%', @performance_condition) = 0)
  BEGIN
    RAISERROR(14507, 16, 1)
    RETURN(1) -- Failure
  END

  -- Parse the performance_condition
  SELECT @delimiter_count = 0
  SELECT @temp_str = @performance_condition
  SELECT @pos = CHARINDEX(N'|', @temp_str)
  WHILE (@pos <> 0)
  BEGIN
    SELECT @delimiter_count = @delimiter_count + 1
    IF (@delimiter_count = 1) SELECT @object_name = SUBSTRING(@temp_str, 1, @pos - 1)
    IF (@delimiter_count = 2) SELECT @counter_name = SUBSTRING(@temp_str, 1, @pos - 1)
    IF (@delimiter_count = 3) SELECT @instance_name = SUBSTRING(@temp_str, 1, @pos - 1)
    SELECT @temp_str = SUBSTRING(@temp_str, @pos + 1, (DATALENGTH(@temp_str) / 2) - @pos)
    SELECT @pos = CHARINDEX(N'|', @temp_str)
  END
  IF (@delimiter_count <> 4)
  BEGIN
    RAISERROR(14507, 16, 1)
    RETURN(1) -- Failure
  END

  -- Check the object_name
  IF (NOT EXISTS (SELECT *
                  FROM master.dbo.sysperfinfo
                  WHERE (object_name = @object_name)))
  BEGIN
    RAISERROR(14262, 16, 1, 'object_name', @object_name)
    RETURN(1) -- Failure
  END

  -- Check the counter_name
  IF (NOT EXISTS (SELECT *
                  FROM master.dbo.sysperfinfo
                  WHERE (object_name = @object_name)
                    AND (counter_name = @counter_name)))
  BEGIN
    RAISERROR(14262, 16, 1, 'counter_name', @counter_name)
    RETURN(1) -- Failure
  END

  -- Check the instance_name
  IF (@instance_name IS NOT NULL)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM master.dbo.sysperfinfo
                    WHERE (object_name = @object_name)
                      AND (counter_name = @counter_name)
                      AND (instance_name = @instance_name)))
    BEGIN
      RAISERROR(14262, 16, 1, 'instance_name', @instance_name)
      RETURN(1) -- Failure
    END
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_VERIFY_JOB_DATE                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_job_date...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_job_date')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_job_date
go
CREATE PROCEDURE sp_verify_job_date
  @date           INT,
  @date_name      VARCHAR(60) = 'date',
  @error_severity INT         = -1
AS
BEGIN
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @date_name = LTRIM(RTRIM(@date_name))

  IF ((ISDATE(CONVERT(VARCHAR, @date)) = 0) OR (@date < 19900101) OR (@date > 99991231))
  BEGIN
    RAISERROR(14266, @error_severity, -1, @date_name, '19900101..99991231')
    RETURN(1) -- Failure
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_VERIFY_JOB_TIME                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_job_time...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_job_time')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_job_time
go

CREATE PROCEDURE sp_verify_job_time
  @time           INT,
  @time_name      VARCHAR(60) = 'time',
  @error_severity INT = -1
AS
BEGIN
  DECLARE @hour      INT
  DECLARE @minute    INT
  DECLARE @second    INT
  DECLARE @part_name NVARCHAR(50)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @time_name = LTRIM(RTRIM(@time_name))

  IF ((@time < 0) OR (@time > 235959))
  BEGIN
    RAISERROR(14266, @error_severity, -1, @time_name, '000000..235959')
    RETURN(1) -- Failure
  END

  SELECT @hour   = (@time / 10000)
  SELECT @minute = (@time % 10000) / 100
  SELECT @second = (@time % 100)

  -- Check hour range
  IF (@hour > 23)
  BEGIN
    SELECT @part_name = FORMATMESSAGE(14218)
    RAISERROR(14287, @error_severity, -1, @time_name, @part_name)
    RETURN(1) -- Failure
  END

  -- Check minute range
  IF (@minute > 59)
  BEGIN
    SELECT @part_name = FORMATMESSAGE(14219)
    RAISERROR(14287, @error_severity, -1, @time_name, @part_name)
    RETURN(1) -- Failure
  END

  -- Check second range
  IF (@second > 59)
  BEGIN
    SELECT @part_name = FORMATMESSAGE(14220)
    RAISERROR(14287, @error_severity, -1, @time_name, @part_name)
    RETURN(1) -- Failure
  END

  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_VERIFY_ALERT                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_alert...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_alert')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_alert
go
CREATE PROCEDURE sp_verify_alert
  @name                          sysname,
  @message_id                    INT,
  @severity                      INT,
  @enabled                       TINYINT,
  @delay_between_responses       INT,
  @notification_message          NVARCHAR(512),
  @include_event_description_in  TINYINT,
  @database_name                 sysname,
  @event_description_keyword     NVARCHAR(100),
  @job_id                        UNIQUEIDENTIFIER OUTPUT,
  @job_name                      sysname          OUTPUT,
  @occurrence_count              INT,
  @raise_snmp_trap               TINYINT,
  @performance_condition         NVARCHAR(512),
  @category_name                 sysname,
  @category_id                   INT              OUTPUT,
  @count_reset_date              INT,
  @count_reset_time              INT,
  @wmi_namespace      NVARCHAR(512),      -- New for 9.0
  @wmi_query          NVARCHAR(512),      -- New for 9.0
  @event_id        INT     OUTPUT   -- New for 9.0
AS
BEGIN
  DECLARE @retval               INT
  DECLARE @non_alertable_errors VARCHAR(512)
  DECLARE @message_id_as_string VARCHAR(10)
  DECLARE @res_valid_range      NVARCHAR(100)
  DECLARE @alert_no_wmi_check   INT
  DECLARE @job_owner_sid      VARBINARY(85)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name                      = LTRIM(RTRIM(@name))
  SELECT @notification_message      = LTRIM(RTRIM(@notification_message))
  SELECT @database_name             = LTRIM(RTRIM(@database_name))
  SELECT @event_description_keyword = LTRIM(RTRIM(@event_description_keyword))
  SELECT @job_name                  = LTRIM(RTRIM(@job_name))
  SELECT @performance_condition     = LTRIM(RTRIM(@performance_condition))
  SELECT @category_name             = LTRIM(RTRIM(@category_name))
  SELECT @alert_no_wmi_check        = 0
  
  -- Only a sysadmin can do this
  
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check if the NewName is unique
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysalerts
              WHERE (name = @name)))
  BEGIN
    RAISERROR(14261, 16, 1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Check if the user has supplied MessageID OR Severity OR Performance-Condition OR WMI namespace/query
  IF ((@performance_condition IS NULL) AND (@message_id = 0) AND (@severity = 0) AND ((@wmi_namespace IS NULL) OR (@wmi_query IS NULL))) OR
     ((@performance_condition IS NOT NULL) AND ((@message_id <> 0) OR (@severity <> 0) OR (@wmi_namespace IS NOT NULL) OR (@wmi_query IS NOT NULL))) OR
     ((@message_id <> 0) AND ((@performance_condition IS NOT NULL) OR (@severity <> 0) OR (@wmi_namespace IS NOT NULL) OR (@wmi_query IS NOT NULL))) OR
     ((@severity <> 0) AND ((@performance_condition IS NOT NULL) OR (@message_id <> 0) OR (@wmi_namespace IS NOT NULL) OR (@wmi_query IS NOT NULL)))
  BEGIN
    RAISERROR(14500, 16, 1)
    RETURN(1) -- Failure
  END

  -- Check the Severity
  IF ((@severity < 0) OR (@severity > 25))
  BEGIN
    RAISERROR(14266, 16, 1, '@severity', '0..25')
    RETURN(1) -- Failure
  END

  -- Check the MessageID
  IF (@message_id <> 0) AND
     (NOT EXISTS (SELECT error
                  FROM master.dbo.sysmessages
                  WHERE (error = @message_id)))
  BEGIN
    SELECT @message_id_as_string = CONVERT(VARCHAR, @message_id)
    RAISERROR(14262, 16, 1, '@message_id', @message_id_as_string)
    RETURN(1) -- Failure
  END

  -- Check if it is legal to set an alert on this MessageID
  DECLARE @TempRetVal TABLE (RetVal INT)
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'NonAlertableErrors',
                                         @non_alertable_errors OUTPUT,
                                         N'no_output'
  IF (ISNULL(@non_alertable_errors, N'NULL') <> N'NULL')
  BEGIN
    DECLARE @message_id_as_char VARCHAR(10)

    SELECT @message_id_as_char = CONVERT(VARCHAR(10), @message_id)
    INSERT INTO @TempRetVal
    EXECUTE ('IF (' + @message_id_as_char + ' IN (' + @non_alertable_errors + ')) SELECT 1')
  END

  IF (EXISTS (SELECT *
              FROM @TempRetVal))
  BEGIN
    RAISERROR(14506, 16, 1, @message_id)
    RETURN(1) -- Failure
  END

  -- Enabled must be 0 or 1
  IF (@enabled NOT IN (0, 1))
  BEGIN
    RAISERROR(14266, 16, 1, '@enabled', '0, 1')
    RETURN(1) -- Failure
  END

  -- DelayBetweenResponses must be > 0
  IF (@delay_between_responses < 0)
  BEGIN
    SELECT @res_valid_range = FORMATMESSAGE(14206)
    RAISERROR(14266, 16, 1, '@delay_between_responses', @res_valid_range)
    RETURN(1) -- Failure
  END

  -- NOTE: We don't check the notification message

  -- Check IncludeEventDescriptionIn
  IF ((@include_event_description_in < 0) OR (@include_event_description_in > 7))
  BEGIN
    SELECT @res_valid_range = FORMATMESSAGE(14208)
    RAISERROR(14266, 16, 1, '@include_event_description_in', @res_valid_range)
    RETURN(1) -- Failure
  END

  -- Check the database name
  IF (@database_name IS NOT NULL) AND (DB_ID(@database_name) IS NULL)
  BEGIN
    RAISERROR(15010, 16, 1, @database_name)
    RETURN(1) -- Failure
  END

  -- NOTE: We don't check the event description keyword

  -- Check JobName/ID
  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    -- We use '' as a special value which means 'no job' (we cannot use NULL since this forces
    -- sp_update_alert to use the existing value)
    IF (@job_name = N'')
      SELECT @job_id = 0x00
    ELSE
    BEGIN
      EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                  '@job_id',
                                                   @job_name OUTPUT,
                                                   @job_id   OUTPUT,
                                       @owner_sid = @job_owner_sid OUTPUT
      IF (@retval <> 0)
        RETURN(1) -- Failure
        
     -- Check permissions beyond what's checked by the sysjobs_view
     -- SQLAgentReaderRole and SQLAgentOperatorRole can see all jobs but
     -- cannot modify them
     IF (@job_owner_sid <> SUSER_SID()                   -- does not own the job
        AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))   -- is not sysadmin
     BEGIN
       RAISERROR(14525, -1, -1); 
       RETURN(1) -- Failure
     END
        
      -- Check that the job is a local job
      IF (NOT EXISTS (SELECT *
                      FROM msdb.dbo.sysjobservers
                      WHERE (job_id = @job_id)
                        AND (server_id = 0)))
      BEGIN
        RAISERROR(14527, -1, -1, @job_name)
        RETURN(1) -- Failure
      END
    END
  END

  -- OccurrenceCount must be > 0
  IF (@occurrence_count < 0)
  BEGIN
    RAISERROR(14266, 16, 1, '@occurrence_count', '0..n')
    RETURN(1) -- Failure
  END

  -- RaiseSNMPTrap must be 0 or 1
  IF (@raise_snmp_trap NOT IN (0, 1))
  BEGIN
    RAISERROR(14266, 16, 1, '@raise_snmp_trap', '0, 1')
    RETURN(1) -- Failure
  END

  -- Check the performance condition (including invalid parameter combinations)
  IF (@performance_condition IS NOT NULL)
  BEGIN
    IF (@database_name IS NOT NULL)
    BEGIN
      RAISERROR(14505, 16, 1, '@database_name')
      RETURN(1) -- Failure
    END

    IF (@event_description_keyword IS NOT NULL)
    BEGIN
      RAISERROR(14505, 16, 1, '@event_description_keyword')
      RETURN(1) -- Failure
    END
    
    IF (@wmi_namespace IS NOT NULL)
    BEGIN
      RAISERROR(14505, 16, 1, '@wmi_namespace')
      RETURN(1) -- Failure
    END

    IF (@wmi_query IS NOT NULL)
    BEGIN
      RAISERROR(14505, 16, 1, '@wmi_query')
      RETURN(1) -- Failure
    END

    -- Verify the performance condition
    EXECUTE @retval = msdb.dbo.sp_verify_performance_condition @performance_condition
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check category name
  IF (@category_name = N'[DEFAULT]')
    SELECT @category_id = 98
  ELSE
  BEGIN
    SELECT @category_id = category_id
    FROM msdb.dbo.syscategories
    WHERE (category_class = 2) -- Alerts
      AND (category_type = 3) -- None
      AND (name = @category_name)
  END
  IF (@category_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@category_name', @category_name)
    RETURN(1) -- Failure
  END

  -- Check count reset date
  IF (@count_reset_date <> 0)
  BEGIN
    EXECUTE @retval = msdb.dbo.sp_verify_job_date @count_reset_date, '@count_reset_date'
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check count reset time
  IF (@count_reset_time <> 0)
  BEGIN
    EXECUTE @retval = msdb.dbo.sp_verify_job_time @count_reset_time, '@count_reset_time'
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check WMI parameters. Both must exist
  IF (@wmi_namespace IS NOT NULL)
  BEGIN
    IF (@wmi_query IS NULL)
   BEGIN
      RAISERROR(14509, 16, 1, '@wmi_query') 
     RETURN(1) -- Failure
   END
   
    IF (@database_name IS NOT NULL)
    BEGIN
      RAISERROR(14510, 16, 1, '@database_name') 
      RETURN(1) -- Failure
    END

    IF (@event_description_keyword IS NOT NULL)
    BEGIN
      RAISERROR(14510, 16, 1, '@event_description_keyword')
      RETURN(1) -- Failure
    END

    --do not check WMI properties if a registry setting is present
    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'AlertNoWmiCheck',
                                           @alert_no_wmi_check OUTPUT,
                                           'no_output'
    if (@alert_no_wmi_check <> 1)
    BEGIN
      EXECUTE @retval = msdb.dbo.sp_sqlagent_notify @op_type = N'T',
                    @wmi_namespace = @wmi_namespace,
               @wmi_query  = @wmi_query,
               @error_flag = 0
      IF (@retval <> 0)
     BEGIN
       RAISERROR(14511, 16, 1)
         RETURN(1) -- Failure
     END
    END

   -- Set event_id to indicate WMI alert   
    SELECT @event_id = 8
  END
  ELSE IF (@wmi_query IS NOT NULL)
  BEGIN
    RAISERROR(14512, 16, 1, '@wmi_namespace')
    RETURN(1) -- Failure
  END
  
  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_UPDATE_ALERT                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_alert...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_alert')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_alert
go
CREATE PROCEDURE sp_update_alert
  @name                         sysname,
  @new_name                     sysname          = NULL,
  @enabled                      TINYINT          = NULL,
  @message_id                   INT              = NULL,
  @severity                     INT              = NULL,
  @delay_between_responses      INT              = NULL,
  @notification_message         NVARCHAR(512)    = NULL,
  @include_event_description_in TINYINT          = NULL, -- 0 = None, 1 = Email, 2 = Pager. 4 = NetSend, 7 = All
  @database_name                sysname          = NULL,
  @event_description_keyword    NVARCHAR(100)    = NULL,
  @job_id                       UNIQUEIDENTIFIER = NULL, -- If provided must NOT also provide job_name
  @job_name                     sysname          = NULL, -- If provided must NOT also provide job_id
  @occurrence_count             INT              = NULL, -- Can only be set to 0
  @count_reset_date             INT              = NULL,
  @count_reset_time             INT              = NULL,
  @last_occurrence_date         INT              = NULL, -- Can only be set to 0
  @last_occurrence_time         INT              = NULL, -- Can only be set to 0
  @last_response_date           INT              = NULL, -- Can only be set to 0
  @last_response_time           INT              = NULL, -- Can only be set to 0
  @raise_snmp_trap              TINYINT          = NULL,
  @performance_condition        NVARCHAR(512)    = NULL, -- New for 7.0
  @category_name                sysname          = NULL, -- New for 7.0
  @wmi_namespace           sysname         = NULL, -- New for 9.0
  @wmi_query               NVARCHAR(512)   = NULL  -- New for 9.0
AS
BEGIN
  DECLARE @x_enabled                   TINYINT
  DECLARE @x_message_id                INT
  DECLARE @x_severity                  INT
  DECLARE @x_delay_between_responses   INT
  DECLARE @x_notification_message      NVARCHAR(512)
  DECLARE @x_include_event_description TINYINT
  DECLARE @x_database_name             sysname
  DECLARE @x_event_description_keyword NVARCHAR(100)
  DECLARE @x_occurrence_count          INT
  DECLARE @x_count_reset_date          INT
  DECLARE @x_count_reset_time          INT
  DECLARE @x_last_occurrence_date      INT
  DECLARE @x_last_occurrence_time      INT
  DECLARE @x_last_response_date        INT
  DECLARE @x_last_response_time        INT
  DECLARE @x_flags                     INT
  DECLARE @x_performance_condition     NVARCHAR(512)
  DECLARE @x_job_id                    UNIQUEIDENTIFIER
  DECLARE @x_category_id               INT
  DECLARE @x_event_id                  INT
  DECLARE @x_wmi_namespace          sysname
  DECLARE @x_wmi_query              NVARCHAR(512)

  DECLARE @include_event_desc_code     TINYINT
  DECLARE @return_code                 INT
  DECLARE @duplicate_name              sysname
  DECLARE @category_id                 INT
  DECLARE @alert_id                    INT
  DECLARE @cached_attribute_modified   INT
  DECLARE @event_id                 INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @new_name                  = LTRIM(RTRIM(@new_name))
  SELECT @job_name                  = LTRIM(RTRIM(@job_name))
  SELECT @notification_message      = LTRIM(RTRIM(@notification_message))
  SELECT @database_name             = LTRIM(RTRIM(@database_name))
  SELECT @event_description_keyword = LTRIM(RTRIM(@event_description_keyword))
  SELECT @performance_condition     = LTRIM(RTRIM(@performance_condition))
  SELECT @category_name             = LTRIM(RTRIM(@category_name))

  -- Are we modifying an attribute which SQLServerAgent caches?
  IF ((@new_name                     IS NOT NULL) OR
      (@enabled                      IS NOT NULL) OR
      (@message_id                   IS NOT NULL) OR
      (@severity                     IS NOT NULL) OR
      (@delay_between_responses      IS NOT NULL) OR
      (@notification_message         IS NOT NULL) OR
      (@include_event_description_in IS NOT NULL) OR
      (@database_name                IS NOT NULL) OR
      (@event_description_keyword    IS NOT NULL) OR
      (@job_id                       IS NOT NULL) OR
      (@job_name                     IS NOT NULL) OR
      (@last_response_date           IS NOT NULL) OR
      (@last_response_time           IS NOT NULL) OR
      (@raise_snmp_trap              IS NOT NULL) OR
      (@performance_condition        IS NOT NULL) OR
      (@wmi_namespace             IS NOT NULL) OR
      (@wmi_query              IS NOT NULL))  
    SELECT @cached_attribute_modified = 1
  ELSE
    SELECT @cached_attribute_modified = 0

  -- Map a job_id of 0 to the real value we use to mean 'no job'
  IF (@job_id = CONVERT(UNIQUEIDENTIFIER, 0x00)) AND (@job_name IS NULL)
    SELECT @job_name = N''

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1)
  END

  -- Check if SQLServerAgent is in the process of starting
  EXECUTE @return_code = msdb.dbo.sp_is_sqlagent_starting
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  -- Check if this Alert exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysalerts
                  WHERE (name = @name)))
  BEGIN
    RAISERROR(14262, 16, 1, '@name', @name)
    RETURN(1)
  END

  -- Certain values (if supplied) may only be updated to 0
  IF (@occurrence_count <> 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@occurrence_count', '0')
    RETURN(1) -- Failure
  END
  IF (@last_occurrence_date <> 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@last_occurrence_date', '0')
    RETURN(1) -- Failure
  END
  IF (@last_occurrence_time <> 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@last_occurrence_time', '0')
    RETURN(1) -- Failure
  END
  IF (@last_response_date <> 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@last_response_date', '0')
    RETURN(1) -- Failure
  END
  IF (@last_response_time <> 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@last_response_time', '0')
    RETURN(1) -- Failure
  END

  -- Get existing (@x_) values
  SELECT @alert_id                    = id,
         @x_enabled                   = enabled,
         @x_message_id                = message_id,
         @x_severity                  = severity,
         @x_delay_between_responses   = delay_between_responses,
         @x_notification_message      = notification_message,
         @x_include_event_description = include_event_description,
         @x_database_name             = database_name,
         @x_event_description_keyword = event_description_keyword,
         @x_occurrence_count          = occurrence_count,
         @x_count_reset_date          = count_reset_date,
         @x_count_reset_time          = count_reset_time,
         @x_job_id                    = job_id,
         @x_last_occurrence_date      = last_occurrence_date,
         @x_last_occurrence_time      = last_occurrence_time,
         @x_last_response_date        = last_response_date,
         @x_last_response_time        = last_response_time,
         @x_flags                     = flags,
         @x_performance_condition     = performance_condition,
         @x_category_id               = category_id,
       @x_event_id              = event_id
  FROM msdb.dbo.sysalerts
  WHERE (name = @name)
  
  SELECT @x_job_id = sjv.job_id
  FROM msdb.dbo.sysalerts    sa,
       msdb.dbo.sysjobs_view sjv
  WHERE (sa.job_id = sjv.job_id)
    AND (sa.name = @name)

  -- Fill out the values for all non-supplied parameters from the existsing values
  IF (@x_event_id = 8)
  BEGIN
   -- WMI alert type
   IF (@wmi_namespace IS NULL) SELECT @wmi_namespace = @x_database_name
   IF (@wmi_query IS NULL) SELECT @wmi_query = @x_performance_condition
  END
  ELSE
  BEGIN
   -- Non-WMI alert type
   IF (@database_name IS NULL) SELECT @database_name = @x_database_name
   IF (@performance_condition IS NULL) SELECT @performance_condition = @x_performance_condition
  END
   
  IF (@enabled                      IS NULL) SELECT @enabled                      = @x_enabled
  IF (@message_id                   IS NULL) SELECT @message_id                   = @x_message_id
  IF (@severity                     IS NULL) SELECT @severity                     = @x_severity
  IF (@delay_between_responses      IS NULL) SELECT @delay_between_responses      = @x_delay_between_responses
  IF (@notification_message         IS NULL) SELECT @notification_message         = @x_notification_message
  IF (@include_event_description_in IS NULL) SELECT @include_event_description_in = @x_include_event_description
  IF (@event_description_keyword    IS NULL) SELECT @event_description_keyword    = @x_event_description_keyword
  IF (@job_id IS NULL) AND (@job_name IS NULL) SELECT @job_id                     = @x_job_id
  IF (@occurrence_count             IS NULL) SELECT @occurrence_count             = @x_occurrence_count
  IF (@count_reset_date             IS NULL) SELECT @count_reset_date             = @x_count_reset_date
  IF (@count_reset_time             IS NULL) SELECT @count_reset_time             = @x_count_reset_time
  IF (@last_occurrence_date         IS NULL) SELECT @last_occurrence_date         = @x_last_occurrence_date
  IF (@last_occurrence_time         IS NULL) SELECT @last_occurrence_time         = @x_last_occurrence_time
  IF (@last_response_date           IS NULL) SELECT @last_response_date           = @x_last_response_date
  IF (@last_response_time           IS NULL) SELECT @last_response_time           = @x_last_response_time
  IF (@raise_snmp_trap              IS NULL) SELECT @raise_snmp_trap              = @x_flags & 0x1
  IF (@category_name                IS NULL) SELECT @category_name = name FROM msdb.dbo.syscategories WHERE (category_id = @x_category_id)

  IF (@category_name IS NULL)
  BEGIN
    SELECT @category_name = name
    FROM msdb.dbo.syscategories
    WHERE (category_id = 98)
  END

  -- Turn [nullable] empty string parameters into NULLs
  IF (@new_name                  = N'') SELECT @new_name                  = NULL
  IF (@notification_message      = N'') SELECT @notification_message      = NULL
  IF (@database_name             = N'') SELECT @database_name             = NULL
  IF (@event_description_keyword = N'') SELECT @event_description_keyword = NULL
  IF (@performance_condition     = N'') SELECT @performance_condition     = NULL
  IF (@wmi_namespace        = N'') SELECT @wmi_namespace         = NULL
  IF (@wmi_query            = N'') SELECT @wmi_query             = NULL

  -- Verify the Alert
  IF (@job_id = CONVERT(UNIQUEIDENTIFIER, 0x00))
    SELECT @job_id = NULL
  EXECUTE @return_code = sp_verify_alert @new_name,
                                         @message_id,
                                         @severity,
                                         @enabled,
                                         @delay_between_responses,
                                         @notification_message,
                                         @include_event_description_in,
                                         @database_name,
                                         @event_description_keyword,
                                         @job_id OUTPUT,
                                         @job_name OUTPUT,
                                         @occurrence_count,
                                         @raise_snmp_trap,
                                         @performance_condition,
                                         @category_name,
                                         @category_id OUTPUT,
                                         @count_reset_date,
                                         @count_reset_time,
                                         @wmi_namespace,
                                         @wmi_query,
                                     @event_id OUTPUT
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  -- If the user didn't supply a NewName, use the old one.
  -- NOTE: This must be done AFTER sp_verify_alert.
  IF (@new_name IS NULL)
    SELECT @new_name = @name

  -- Turn the 1st 'flags' bit on or off accordingly
  IF (@raise_snmp_trap = 0)
    SELECT @x_flags = @x_flags & 0xFFFE
  ELSE
    SELECT @x_flags = @x_flags | 0x0001

  -- For WMI alerts replace 
  -- database_name with wmi_namespace and 
  -- performance_conditon with wmi_query
  -- so we can store them in those columns in sysalerts table
  IF (@event_id = 8)
  BEGIN
   SELECT @database_name = @wmi_namespace
   SELECT @performance_condition = @wmi_query
  END

  -- Check if this Alert already exists
  SELECT @duplicate_name = FORMATMESSAGE(14205)
  SELECT @duplicate_name = name
  FROM msdb.dbo.sysalerts
  WHERE ((event_id = 8) AND 
       (ISNULL(performance_condition, N'') = ISNULL(@performance_condition, N'')) AND
       (ISNULL(database_name, N'') = ISNULL(@database_name, N''))) OR
      ((ISNULL(event_id,1) <> 8) AND 
       (ISNULL(performance_condition, N'apples') = ISNULL(@performance_condition, N'oranges'))) OR 
      ((performance_condition IS NULL) AND
         (message_id = @message_id) AND
         (severity = @severity) AND
         (ISNULL(database_name, N'') = ISNULL(@database_name, N'')) AND
         (ISNULL(event_description_keyword, N'') = ISNULL(@event_description_keyword, N'')))
  IF (@duplicate_name <> FORMATMESSAGE(14205) AND @duplicate_name <> @name)
  BEGIN
    RAISERROR(14501, 16, 1, @duplicate_name)
    RETURN(1) -- Failure
  END

  -- Finally, do the actual UPDATE
  UPDATE msdb.dbo.sysalerts
  SET name                        = @new_name,
      message_id                  = @message_id,
      severity                    = @severity,
      enabled                     = @enabled,
      delay_between_responses     = @delay_between_responses,
      notification_message        = @notification_message,
      include_event_description   = @include_event_description_in,
      database_name               = @database_name,
      event_description_keyword   = @event_description_keyword,
      job_id                      = ISNULL(@job_id, CONVERT(UNIQUEIDENTIFIER, 0x00)),
      occurrence_count            = @occurrence_count,
      count_reset_date            = @count_reset_date,
      count_reset_time            = @count_reset_time,
      last_occurrence_date        = @last_occurrence_date,
      last_occurrence_time        = @last_occurrence_time,
      last_response_date          = @last_response_date,
      last_response_time          = @last_response_time,
      flags                       = @x_flags,
      performance_condition       = @performance_condition,
      category_id                 = @category_id,
      event_id               = @event_id
  WHERE (name = @name)

  -- Notify SQLServerAgent of the change
  IF (@cached_attribute_modified = 1)
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'A',
                                        @alert_id    = @alert_id,
                                        @action_type = N'U'
  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_DELETE_JOB_REFERENCES                                   */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_job_references...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_job_references')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_job_references
go
CREATE PROCEDURE sp_delete_job_references
  @notify_sqlagent BIT = 1
AS
BEGIN
  DECLARE @deleted_job_id  UNIQUEIDENTIFIER
  DECLARE @task_id_as_char VARCHAR(10)
  DECLARE @job_is_cached   INT
  DECLARE @alert_name      sysname
  DECLARE @maintplan_plan_id  UNIQUEIDENTIFIER
  DECLARE @maintplan_subplan_id  UNIQUEIDENTIFIER

  -- Keep SQLServerAgent's cache in-sync and cleanup any 'webtask' cross-references to the deleted job(s)
  -- NOTE: The caller must have created a table called #temp_jobs_to_delete of the format
  --       (job_id UNIQUEIDENTIFIER NOT NULL, job_is_cached INT NOT NULL).

  DECLARE sqlagent_notify CURSOR LOCAL
  FOR
  SELECT job_id, job_is_cached
  FROM #temp_jobs_to_delete

  OPEN sqlagent_notify
  FETCH NEXT FROM sqlagent_notify INTO @deleted_job_id, @job_is_cached

  WHILE (@@fetch_status = 0)
  BEGIN
    -- NOTE: We only notify SQLServerAgent if we know the job has been cached
    IF(@job_is_cached = 1 AND @notify_sqlagent = 1)
      EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                          @job_id      = @deleted_job_id,
                                          @action_type = N'D'

    IF (EXISTS (SELECT *
                FROM master.dbo.sysobjects
                WHERE (name = N'sp_cleanupwebtask')
                  AND (type = 'P')))
    BEGIN
      SELECT @task_id_as_char = CONVERT(VARCHAR(10), task_id)
      FROM msdb.dbo.systaskids
      WHERE (job_id = @deleted_job_id)
      IF (@task_id_as_char IS NOT NULL)
        EXECUTE ('master.dbo.sp_cleanupwebtask @taskid = ' + @task_id_as_char)
    END

    -- Maintenance plan cleanup for SQL 2005.
    -- If this job came from another server and it runs a subplan of a
    -- maintenance plan, then delete the subplan record. If that was
    -- the last subplan still referencing that plan, delete the plan.
    -- This removes a distributed maintenance plan from a target server
    -- once all of jobs from the master server that used that maintenance
    -- plan are deleted.
    SELECT @maintplan_plan_id = plans.plan_id, @maintplan_subplan_id = plans.subplan_id
    FROM sysmaintplan_subplans plans, sysjobs_view sjv
    WHERE plans.job_id = @deleted_job_id
      AND plans.job_id = sjv.job_id
      AND sjv.master_server = 1 -- This means the job came from the master

    IF (@maintplan_subplan_id is not NULL)
    BEGIN
      EXECUTE sp_maintplan_delete_subplan @subplan_id = @maintplan_subplan_id, @delete_jobs = 0
      IF (NOT EXISTS (SELECT *
                      FROM sysmaintplan_subplans
                      where plan_id = @maintplan_plan_id))
      BEGIN
        DECLARE @plan_name sysname

        SELECT @plan_name = name
          FROM sysmaintplan_plans
          WHERE id = @maintplan_plan_id

        EXECUTE sp_ssis_deletepackage @name = @plan_name, @folderid = '08aa12d5-8f98-4dab-a4fc-980b150a5dc8' -- this is the guid for 'Maintenance Plans'
      END
    END

    FETCH NEXT FROM sqlagent_notify INTO @deleted_job_id, @job_is_cached
  END
  DEALLOCATE sqlagent_notify

  -- Remove systaskid references (must do this AFTER sp_cleanupwebtask stuff)
  DELETE FROM msdb.dbo.systaskids
  WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

  -- Remove sysdbmaintplan_jobs references (legacy maintenance plans prior to SQL 2005)
  DELETE FROM msdb.dbo.sysdbmaintplan_jobs
  WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

  -- Finally, clean up any dangling references in sysalerts to the deleted job(s)
  DECLARE sysalerts_cleanup CURSOR LOCAL
  FOR
  SELECT name
  FROM msdb.dbo.sysalerts
  WHERE (job_id IN (SELECT job_id FROM #temp_jobs_to_delete))

  OPEN sysalerts_cleanup
  FETCH NEXT FROM sysalerts_cleanup INTO @alert_name
  WHILE (@@fetch_status = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_update_alert @name   = @alert_name,
                                     @job_id = 0x00
    FETCH NEXT FROM sysalerts_cleanup INTO @alert_name
  END
  DEALLOCATE sysalerts_cleanup
END
go

/**************************************************************/
/* SP_DELETE_ALL_MSX_JOBS                                     */
/*                                                            */
/* NOTE: This is a separate procedure because SQLServerAgent  */
/*       needs to call it, as does sp_msx_defect and          */
/*       sp_delete_job.                                       */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_all_msx_jobs...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_all_msx_jobs')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_all_msx_jobs
go
CREATE PROCEDURE sp_delete_all_msx_jobs
  @msx_server   sysname,
  @jobs_deleted INT = NULL OUTPUT
AS
BEGIN
  SET NOCOUNT ON

  -- Change server name to always reflect real servername or servername\instancename
  IF (UPPER(@msx_server collate SQL_Latin1_General_CP1_CS_AS) = '(LOCAL)')
    SELECT @msx_server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

  -- Delete all the jobs that originated from the MSX
  -- Note: This temp table is referenced by msdb.dbo.sp_delete_job_references
  CREATE TABLE #temp_jobs_to_delete (job_id UNIQUEIDENTIFIER NOT NULL, job_is_cached INT NOT NULL, owner_sid VARBINARY(85) NOT NULL)

  -- Table of msx schedules to delete
  DECLARE @temp_schedules_to_delete TABLE (schedule_id INT NOT NULL)  

  -- Non-sysadmins can only delete jobs they own. sysjobs_view returns all jobs
  -- for members of SQLAgentReaderRole and SQLAgentOperatorRole, but they should
  -- not be able to delete those jobs
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
  BEGIN
   -- NOTE: The left outer-join here is to handle the [unlikely] case of missing sysjobservers rows
   INSERT INTO #temp_jobs_to_delete
   SELECT sjv.job_id, 
         CASE sjs.server_id WHEN 0 THEN 1 ELSE 0 END,
         sjv.owner_sid
   FROM msdb.dbo.sysjobs_view sjv
      LEFT OUTER JOIN msdb.dbo.sysjobservers sjs ON (sjv.job_id = sjs.job_id)
   WHERE (ISNULL(sjs.server_id, 0) = 0)
      AND (sjv.originating_server = @msx_server)
  END
  ELSE
  BEGIN
   -- NOTE: The left outer-join here is to handle the [unlikely] case of missing sysjobservers rows
   INSERT INTO #temp_jobs_to_delete
   SELECT sjv.job_id, 
         CASE sjs.server_id WHEN 0 THEN 1 ELSE 0 END,
         sjv.owner_sid
   FROM msdb.dbo.sysjobs_view sjv
      LEFT OUTER JOIN msdb.dbo.sysjobservers sjs ON (sjv.job_id = sjs.job_id)
   WHERE (ISNULL(sjs.server_id, 0) = 0)
      AND (sjv.originating_server = @msx_server)
      AND (sjv.owner_sid = SUSER_SID())
  END

  -- Must do this before deleting the job itself since sp_sqlagent_notify does a lookup on sysjobs_view
  EXECUTE msdb.dbo.sp_delete_job_references

  BEGIN TRANSACTION

    --Get the list of schedules to delete, these cant be deleted until the references are deleted in sysjobschedules
    INSERT INTO @temp_schedules_to_delete
    SELECT DISTINCT schedule_id 
    FROM   msdb.dbo.sysschedules
    WHERE (schedule_id IN 
            (SELECT schedule_id
            FROM msdb.dbo.sysjobschedules as js
           JOIN #temp_jobs_to_delete as tjd ON (js.job_id = tjd.job_id)))

    DELETE FROM msdb.dbo.sysjobschedules
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

    --Now OK to delete the schedule
    DELETE FROM msdb.dbo.sysschedules
    WHERE schedule_id IN 
    (SELECT schedule_id
        FROM @temp_schedules_to_delete)
    
    DELETE FROM msdb.dbo.sysjobservers
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)
    
    DELETE FROM msdb.dbo.sysjobsteps
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

    DELETE FROM msdb.dbo.sysjobs
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

    DELETE FROM msdb.dbo.sysjobhistory
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

   --Finally cleanup any orphaned sysschedules that were downloaded from the MSX
   DELETE msdb.dbo.sysschedules
   FROM msdb.dbo.sysschedules s
      JOIN msdb.dbo.sysoriginatingservers_view os ON (s.originating_server_id = os.originating_server_id)
   WHERE (os.originating_server = @msx_server)

  COMMIT TRANSACTION

  SELECT @jobs_deleted = COUNT(*)
  FROM #temp_jobs_to_delete

  DROP TABLE #temp_jobs_to_delete
END
go


/**************************************************************/
/* SP_GENERATE_TARGET_SERVER_JOB_ASSIGNMENT_SQL               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_generate_target_server_job_assignment_sql...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_generate_target_server_job_assignment_sql')
              AND (type = 'P')))
  DROP PROCEDURE sp_generate_target_server_job_assignment_sql
go
CREATE PROCEDURE sp_generate_target_server_job_assignment_sql
  @server_name     sysname = NULL, 
  @new_server_name sysname = NULL  -- Use this if the target server computer has been renamed
AS
BEGIN
  SET NOCOUNT ON

  -- Change server name to always reflect real servername or servername\instancename
  IF (@server_name IS NULL) OR (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = '(LOCAL)')
    SELECT @server_name = CONVERT(sysname, SERVERPROPERTY('ServerName'))
  
  IF (@server_name IS NOT NULL) 
    SELECT @server_name = UPPER(@server_name)

  -- Verify the server name
  IF (@server_name <> UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))) AND
     (NOT EXISTS (SELECT *
                  FROM msdb.dbo.systargetservers
                  WHERE (UPPER(server_name) = @server_name)))
  BEGIN
    RAISERROR(14262, 16, 1, '@server_name', @server_name)
    RETURN(1) -- Failure
  END

  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers    sjs,
                   msdb.dbo.systargetservers sts
              WHERE (sjs.server_id = sts.server_id)
                AND (UPPER(sts.server_name) = @server_name)))
  BEGIN
    -- Generate the SQL
    SELECT 'Execute this SQL to re-assign jobs to the target server' =
           'EXECUTE msdb.dbo.sp_add_jobserver @job_id = ''' + CONVERT(VARCHAR(36), sjs.job_id) +
           ''', @server_name = ''' +  ISNULL(@new_server_name, sts.server_name) + ''''
    FROM msdb.dbo.sysjobservers    sjs,
         msdb.dbo.systargetservers sts
    WHERE (sjs.server_id = sts.server_id)
      AND (UPPER(sts.server_name) = @server_name)
  END
  ELSE
    RAISERROR(14548, 10, 1, @server_name)

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_GENERATE_SERVER_DESCRIPTION                             */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_generate_server_description...'
go

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_generate_server_description')
              AND (type = 'P')))
  DROP PROCEDURE sp_generate_server_description
go
CREATE PROCEDURE sp_generate_server_description
  @description NVARCHAR(100) = NULL OUTPUT,
  @result_set  BIT = 0
AS
BEGIN
  SET NOCOUNT ON

  DECLARE @xp_results TABLE
  (
  id              INT           NOT NULL,
  name            NVARCHAR(30)  COLLATE database_default NOT NULL,
  internal_value  INT           NULL,
  character_value NVARCHAR(212) COLLATE database_default NULL
  )
  INSERT INTO @xp_results
  EXECUTE master.dbo.xp_msver

  UPDATE @xp_results
  SET character_value = FORMATMESSAGE(14205)
  WHERE (character_value IS NULL)

  SELECT @description = (SELECT character_value FROM @xp_results WHERE (id = 1)) + N' ' +
                        (SELECT character_value FROM @xp_results WHERE (id = 2)) + N' / Windows ' +
                        (SELECT character_value FROM @xp_results WHERE (id = 15)) + N' / ' +
                        (SELECT character_value FROM @xp_results WHERE (id = 16)) + N' ' +
                        (SELECT CASE character_value
                                  WHEN N'PROCESSOR_INTEL_386'     THEN N'386'
                                  WHEN N'PROCESSOR_INTEL_486'     THEN N'486'
                                  WHEN N'PROCESSOR_INTEL_PENTIUM' THEN N'Pentium'
                                  WHEN N'PROCESSOR_MIPS_R4000'    THEN N'MIPS'
                                  WHEN N'PROCESSOR_ALPHA_21064'   THEN N'Alpha'
                                  ELSE character_value
                                END
                         FROM @xp_results WHERE (id = 18)) + N' CPU(s) / ' +
                        (SELECT CONVERT(NVARCHAR, internal_value) FROM @xp_results WHERE (id = 19)) + N' MB RAM.'
  IF (@result_set = 1)
    SELECT @description
END
go

/**************************************************************/
/* SP_MSX_SET_ACCOUNT                                              */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_msx_set_account...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_msx_set_account')
              AND (type = 'P')))
  DROP PROCEDURE sp_msx_set_account
go
CREATE PROCEDURE sp_msx_set_account
  @credential_name sysname = NULL,
  @credential_id   INT = NULL
AS
BEGIN
  DECLARE @retval INT
  IF @credential_id IS NOT NULL OR @credential_name IS NOT NULL
  BEGIN
     EXECUTE @retval = sp_verify_credential_identifiers  '@credential_name',
                                                        '@credential_id',
                                                        @credential_name OUTPUT,
                                                        @credential_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
    
    --set credential_id to agent registry
    EXECUTE master.dbo.xp_instance_regwrite  'HKEY_LOCAL_MACHINE',
                                    'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                    'MSXCredentialID',
                                    'REG_DWORD', 
                                    @credential_id
    --set connections to standard
    EXECUTE master.dbo.xp_instance_regwrite  'HKEY_LOCAL_MACHINE',
                                    'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                    'RegularMSXConnections',
                                    'REG_DWORD', 
                                    1
  END
  ELSE
  BEGIN
    --just set connection to integrated
    EXECUTE master.dbo.xp_instance_regwrite  'HKEY_LOCAL_MACHINE',
                                    'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                    'RegularMSXConnections',
                                    'REG_DWORD', 
                                    0
  END
END
go

/**************************************************************/
/* SP_MSX_GET_ACCOUNT                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_msx_get_account...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_msx_get_account')
              AND (type = 'P')))
  DROP PROCEDURE sp_msx_get_account
go
CREATE PROCEDURE sp_msx_get_account
AS
BEGIN
  DECLARE @msx_connection INT
  DECLARE @credential_id  INT
  
  SELECT  @msx_connection  = 0    --integrated connections
  SELECT  @credential_id   = NULL      
  EXECUTE master.dbo.xp_instance_regread  N'HKEY_LOCAL_MACHINE',
                                          N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                          N'RegularMSXConnections',
                                          @msx_connection OUTPUT,
                                          N'no_output'
  IF @msx_connection = 1
  BEGIN
    EXECUTE master.dbo.xp_instance_regread  N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'MSXCredentialID',
                                            @credential_id OUTPUT,
                                            N'no_output'
    SELECT msx_connection = @msx_connection , msx_credential_id = @credential_id, 
           msx_credential_name = sc.name , msx_login_name = sc.credential_identity
    FROM   master.sys.credentials sc
    WHERE  credential_id = @credential_id    
  END
END
go

/**************************************************************/
/* SP_DELETE_OPERATOR                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_operator...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_operator')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_operator
go
CREATE PROCEDURE sp_delete_operator
  @name                 sysname,
  @reassign_to_operator sysname = NULL
AS
BEGIN
  DECLARE @id                         INT
  DECLARE @alert_fail_safe_operator   sysname
  DECLARE @job_id                     UNIQUEIDENTIFIER
  DECLARE @job_id_as_char             VARCHAR(36)
  DECLARE @notify_email_operator_id   INT
  DECLARE @notify_netsend_operator_id INT
  DECLARE @notify_page_operator_id    INT
  DECLARE @reassign_to_id             INT
  DECLARE @cmd                        NVARCHAR(1000)
  DECLARE @current_msx_server         sysname
  DECLARE @reassign_to_escaped        NVARCHAR(256)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name                 = LTRIM(RTRIM(@name))
  SELECT @reassign_to_operator = LTRIM(RTRIM(@reassign_to_operator))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@reassign_to_operator = N'') SELECT @reassign_to_operator = NULL

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check if this Operator exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysoperators
                  WHERE (name = @name)))
  BEGIN
    RAISERROR(14262, 16, 1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Check if this operator the FailSafe Operator
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'AlertFailSafeOperator',
                                         @alert_fail_safe_operator OUTPUT,
                                         N'no_output'

  -- If it is, we disallow the delete operation
  IF (LTRIM(RTRIM(@alert_fail_safe_operator)) = @name)
  BEGIN
    RAISERROR(14504, 16, 1, @name, @name)
    RETURN(1) -- Failure
  END

  -- Check if this operator is 'MSXOperator'
  IF (@name = N'MSXOperator')
  BEGIN
    DECLARE @server_type VARCHAR(3)

    -- Disallow the delete operation if we're an MSX or a TSX
    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'MSXServerName',
                                           @current_msx_server OUTPUT,
                                           N'no_output'
    IF (@current_msx_server IS NOT NULL)
      SELECT @server_type = 'TSX'

    IF ((SELECT COUNT(*)
         FROM msdb.dbo.systargetservers) > 0)
      SELECT @server_type = 'MSX'

    IF (@server_type IS NOT NULL)
    BEGIN
      RAISERROR(14223, 16, 1, 'MSXOperator', @server_type)
      RETURN(1) -- Failure
    END
  END

  -- Convert the Name to it's ID
  SELECT @id = id
  FROM msdb.dbo.sysoperators
  WHERE (name = @name)

  IF (@reassign_to_operator IS NOT NULL)
  BEGIN
    -- On a TSX or standalone server, disallow re-assigning to the MSXOperator
    IF (@reassign_to_operator = N'MSXOperator') AND
       (NOT EXISTS (SELECT *
                    FROM msdb.dbo.systargetservers))
    BEGIN
      RAISERROR(14251, -1, -1, @reassign_to_operator)
      RETURN(1) -- Failure
    END

    SELECT @reassign_to_id = id
    FROM msdb.dbo.sysoperators
    WHERE (name = @reassign_to_operator)

    IF (@reassign_to_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@reassign_to_operator', @reassign_to_operator)
      RETURN(1) -- Failure
    END
  END

  -- Double up any single quotes in @reassign_to_operator
  IF (@reassign_to_operator IS NOT NULL)
    SET @reassign_to_escaped  = REPLACE(@reassign_to_operator, N'''', N'''''')

  BEGIN TRANSACTION

    -- Reassign (or delete) any sysnotifications rows that reference this operator
    IF (@reassign_to_operator IS NOT NULL)
    BEGIN
      UPDATE msdb.dbo.sysnotifications
      SET operator_id = @reassign_to_id
      WHERE (operator_id = @id)
        AND (NOT EXISTS (SELECT *
                         FROM msdb.dbo.sysnotifications sn2
                         WHERE (sn2.alert_id = msdb.dbo.sysnotifications.alert_id)
                           AND (sn2.operator_id = @reassign_to_id)))
    END

    DELETE FROM msdb.dbo.sysnotifications
    WHERE (operator_id = @id)

    -- Update any jobs that reference this operator
    DECLARE jobs_referencing_this_operator CURSOR LOCAL
    FOR
    SELECT job_id,
           notify_email_operator_id,
           notify_netsend_operator_id,
           notify_page_operator_id
    FROM msdb.dbo.sysjobs
    WHERE (notify_email_operator_id = @id)
       OR (notify_netsend_operator_id = @id)
       OR (notify_page_operator_id = @id)

    OPEN jobs_referencing_this_operator
    FETCH NEXT FROM jobs_referencing_this_operator INTO @job_id,
                                                        @notify_email_operator_id,
                                                        @notify_netsend_operator_id,
                                                        @notify_page_operator_id
    WHILE (@@fetch_status = 0)
    BEGIN
      SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)
      SELECT @cmd = N'msdb.dbo.sp_update_job @job_id = ''' + @job_id_as_char + N''', '

      IF (@notify_email_operator_id = @id)
        IF (@reassign_to_operator IS NOT NULL)
          SELECT @cmd = @cmd + N'@notify_email_operator_name = N''' + @reassign_to_escaped  + N''', '
        ELSE
          SELECT @cmd = @cmd + N'@notify_email_operator_name = N'''', @notify_level_email = 0, '

      IF (@notify_netsend_operator_id = @id)
        IF (@reassign_to_operator IS NOT NULL)
          SELECT @cmd = @cmd + N'@notify_netsend_operator_name = N''' + @reassign_to_escaped  + N''', '
        ELSE
          SELECT @cmd = @cmd + N'@notify_netsend_operator_name = N'''', @notify_level_netsend = 0, '

      IF (@notify_page_operator_id = @id)
        IF (@reassign_to_operator IS NOT NULL)
          SELECT @cmd = @cmd + N'@notify_page_operator_name = N''' + @reassign_to_escaped  + N''', '
        ELSE
          SELECT @cmd = @cmd + N'@notify_page_operator_name = N'''', @notify_level_page = 0, '

      SELECT @cmd = SUBSTRING(@cmd, 1, (DATALENGTH(@cmd) / 2) - 2)
      EXECUTE (N'EXECUTE ' + @cmd)

      FETCH NEXT FROM jobs_referencing_this_operator INTO @job_id,
                                                          @notify_email_operator_id,
                                                          @notify_netsend_operator_id,
                                                          @notify_page_operator_id
    END
    DEALLOCATE jobs_referencing_this_operator

    -- Finally, do the actual DELETE
    DELETE FROM msdb.dbo.sysoperators
    WHERE (id = @id)

  COMMIT TRANSACTION

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_MSX_DEFECT                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_msx_defect...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_msx_defect')
              AND (type = 'P')))
  DROP PROCEDURE sp_msx_defect
go
CREATE PROCEDURE sp_msx_defect
  @forced_defection BIT = 0
AS
BEGIN
  DECLARE @current_msx_server sysname
  DECLARE @retval             INT
  DECLARE @jobs_deleted       INT
  DECLARE @polling_interval   INT
  DECLARE @nt_user            NVARCHAR(100)

  SET NOCOUNT ON

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) 
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  SELECT @retval = 0
  SELECT @jobs_deleted = 0

  -- Get the current MSX server name from the registry
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'MSXServerName',
                                         @current_msx_server OUTPUT,
                                         N'no_output'

  SELECT @current_msx_server = UPPER(LTRIM(RTRIM(@current_msx_server)))
  IF ((@current_msx_server IS NULL) OR (@current_msx_server = N''))
  BEGIN
    RAISERROR(14298, -1, -1)
    RETURN(1) -- Failure
  END

  SELECT @nt_user = ISNULL(NT_CLIENT(), ISNULL(SUSER_SNAME(), FORMATMESSAGE(14205)))

  EXECUTE @retval = master.dbo.xp_msx_enlist 1, @current_msx_server, @nt_user

  IF (@retval <> 0) AND (@forced_defection = 0)
    RETURN(1) -- Failure

  -- Clear the MSXServerName registry entry
  EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                          N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                          N'MSXServerName',
                                          N'REG_SZ',
                                          N''

  -- Delete the MSXPollingInterval registry entry
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'MSXPollInterval',
                                         @polling_interval OUTPUT,
                                         N'no_output'
  IF (@polling_interval IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regdeletevalue N'HKEY_LOCAL_MACHINE',
                                                  N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                                  N'MSXPollInterval'

  -- Remove the entry from sqlagent_info
  DELETE FROM msdb.dbo.sqlagent_info
  WHERE (attribute = N'DateEnlisted')

  -- Delete all the jobs that originated from the MSX
  -- NOTE: We can't use sp_delete_job here since sp_delete_job checks if the caller is
  --       SQLServerAgent (only SQLServerAgent can delete non-local jobs).
  EXECUTE msdb.dbo.sp_delete_all_msx_jobs @current_msx_server, @jobs_deleted OUTPUT
  RAISERROR(14227, 0, 1, @current_msx_server, @jobs_deleted)

  -- Now delete the old msx server record
  DELETE msdb.dbo.sysoriginatingservers 
  WHERE (originating_server = @current_msx_server)
    AND (master_server = 1)

  -- If a forced defection was performed, attempt to notify the MSXOperator
  IF (@forced_defection = 1)
  BEGIN
    DECLARE @network_address    NVARCHAR(100)
    DECLARE @command            NVARCHAR(512)
    DECLARE @local_machine_name sysname
    DECLARE @res_warning        NVARCHAR(300)

    SELECT @network_address = netsend_address
    FROM msdb.dbo.sysoperators
    WHERE (name = N'MSXOperator')

    IF (@network_address IS NOT NULL)
    BEGIN
      EXECUTE @retval = master.dbo.xp_getnetname @local_machine_name OUTPUT
      IF (@retval <> 0)
        RETURN(1) -- Failure
      SELECT @res_warning = FORMATMESSAGE(14217)
      SELECT @command = N'NET SEND ' + @network_address + N' ' + @res_warning
      SELECT @command = STUFF(@command, PATINDEX(N'%[%%]s%', @command), 2, NT_CLIENT())
      SELECT @command = STUFF(@command, PATINDEX(N'%[%%]s%', @command), 2, @local_machine_name)
      EXECUTE master.dbo.xp_cmdshell @command, no_output
    END
  END

  -- Delete the 'MSXOperator' (must do this last)
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysoperators
              WHERE (name = N'MSXOperator')))
    EXECUTE msdb.dbo.sp_delete_operator @name = N'MSXOperator'

  RETURN(0) -- 0 means success
END
go

/**************************************************************/
/* SP_MSX_ENLIST                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_msx_enlist...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_msx_enlist')
              AND (type = 'P')))
  DROP PROCEDURE sp_msx_enlist
go
CREATE PROCEDURE sp_msx_enlist
  @msx_server_name sysname,
  @location        NVARCHAR(100) = NULL -- The procedure will supply a default
AS
BEGIN
  DECLARE @current_msx_server       sysname
  DECLARE @local_machine_name       sysname
  DECLARE @msx_originating_server   sysname
  DECLARE @retval                   INT
  DECLARE @time_zone_adjustment     INT
  DECLARE @local_time               NVARCHAR(100)
  DECLARE @nt_user                  NVARCHAR(100)
  DECLARE @poll_interval            INT

  SET NOCOUNT ON

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) 
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Only an NT server can be enlisted
  IF ((PLATFORM() & 0x1) <> 0x1) -- NT
  BEGIN
    RAISERROR(14540, -1, 1)
    RETURN(1) -- Failure
  END

  -- Only SBS, Standard, or Enterprise editions of SQL Server can be enlisted
  IF ((PLATFORM() & 0x100) = 0x100) -- Desktop package
  BEGIN
    RAISERROR(14539, -1, -1)
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @msx_server_name  = UPPER(LTRIM(RTRIM(@msx_server_name)))
  SELECT @location         = LTRIM(RTRIM(@location))
  SELECT @local_machine_name = UPPER(CONVERT(NVARCHAR(30), SERVERPROPERTY('ServerName')))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@location = N'') SELECT @location = NULL

  SELECT @retval = 0

  -- Get the values that we'll need for the [re]enlistment operation (except the local time
  -- which we get right before we call xp_msx_enlist to that it's as accurate as possible)
  SELECT @nt_user = ISNULL(NT_CLIENT(), ISNULL(SUSER_SNAME(), FORMATMESSAGE(14205)))
  EXECUTE master.dbo.xp_regread N'HKEY_LOCAL_MACHINE',
                                N'SYSTEM\CurrentControlSet\Control\TimeZoneInformation',
                                N'Bias',
                                @time_zone_adjustment OUTPUT,
                                N'no_output'
  IF ((PLATFORM() & 0x1) = 0x1) -- NT
    SELECT @time_zone_adjustment = -ISNULL(@time_zone_adjustment, 0)
  ELSE
    SELECT @time_zone_adjustment = -CONVERT(INT, CONVERT(BINARY(2), ISNULL(@time_zone_adjustment, 0)))

  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'MSXPollInterval',
                                         @poll_interval OUTPUT,
                                         N'no_output'
  SELECT @poll_interval = ISNULL(@poll_interval, 60) -- This should be the same as DEF_REG_MSX_POLL_INTERVAL
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'MSXServerName',
                                         @current_msx_server OUTPUT,
                                         N'no_output'
  SELECT @current_msx_server = LTRIM(RTRIM(@current_msx_server))

  -- Check if this machine is an MSX (and therefore cannot be enlisted into another MSX)
  IF (EXISTS (SELECT *
              FROM msdb.dbo.systargetservers))
  BEGIN
   --Get local server/instance name  
    RAISERROR(14299, -1, -1, @local_machine_name)
    RETURN(1) -- Failure
  END

  -- Check if the MSX supplied is the same as the local machine (this is not allowed)
  IF (UPPER(@local_machine_name) = @msx_server_name)
  BEGIN
    RAISERROR(14297, -1, -1)
    RETURN(1) -- Failure
  END

  -- Check if MSDB has be re-installed since we enlisted
  IF (@current_msx_server IS NOT NULL) AND
     (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sqlagent_info
                  WHERE (attribute = 'DateEnlisted')))
  BEGIN
    -- User is tring to [re]enlist after a re-install, so we have to forcefully defect before
    -- we can fully enlist again
    EXECUTE msdb.dbo.sp_msx_defect @forced_defection = 1
    SELECT @current_msx_server = NULL
  END

  -- Check if we are already enlisted, in which case we re-enlist
  IF ((@current_msx_server IS NOT NULL) AND (@current_msx_server <> N''))
  BEGIN
    IF (UPPER(@current_msx_server) = @msx_server_name)
    BEGIN
      -- Update the [existing] enlistment
      SELECT @local_time = CONVERT(NVARCHAR, GETDATE(), 112) + N' ' + CONVERT(NVARCHAR, GETDATE(), 108)
      EXECUTE @retval = master.dbo.xp_msx_enlist 2, @msx_server_name, @nt_user, @location, @time_zone_adjustment, @local_time, @poll_interval
      RETURN(@retval) -- 0 means success
    END
    ELSE
    BEGIN
      RAISERROR(14296, -1, -1, @current_msx_server)
      RETURN(1) -- Failure
    END
  END

  -- If we get this far then we're dealing with a new enlistment...
  

  -- If no location is supplied, generate one (such as we can)
  IF (@location IS NULL)
    EXECUTE msdb.dbo.sp_generate_server_description @location OUTPUT

  SELECT @local_time = CONVERT(NVARCHAR, GETDATE(), 112) + ' ' + CONVERT(NVARCHAR, GETDATE(), 108)
  EXECUTE @retval = master.dbo.xp_msx_enlist 0, @msx_server_name, @nt_user, @location, @time_zone_adjustment, @local_time, @poll_interval

  IF (@retval = 0)
  BEGIN
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'MSXServerName',
                                            N'REG_SZ',
                                            @msx_server_name

    IF (@current_msx_server IS NOT NULL)
      RAISERROR(14228, 0, 1, @current_msx_server, @msx_server_name)
    ELSE
      RAISERROR(14229, 0, 1, @msx_server_name)

    -- Update the sysoriginatingservers table with the msx server name. May need to clean up if it already has an msx entry
    SELECT @msx_originating_server = NULL
    -- Get the msx server name 
    SELECT @msx_originating_server = originating_server 
    FROM msdb.dbo.sysoriginatingservers
    WHERE (master_server = 1)
    
    IF(@msx_originating_server IS NULL)
    BEGIN
        -- Good. No msx server found so just add the new one
        INSERT INTO msdb.dbo.sysoriginatingservers(originating_server, master_server) VALUES (@msx_server_name, 1)
    END
    ELSE
    BEGIN
        -- Found a previous entry. If it isn't the same server we need to clean up any existing msx jobs
        IF(@msx_originating_server != @msx_server_name) 
        BEGIN
            INSERT INTO msdb.dbo.sysoriginatingservers(originating_server, master_server) VALUES (@msx_server_name, 1)
            -- Optimistically try and remove any msx jobs left over from the previous msx enlistment. 
            EXECUTE msdb.dbo.sp_delete_all_msx_jobs @msx_originating_server 
            -- And finally delete the old msx server record
            DELETE msdb.dbo.sysoriginatingservers 
            WHERE (originating_server = @msx_originating_server)
              AND (master_server = 1)
        END
    END

    -- Add entry to sqlagent_info
    INSERT INTO msdb.dbo.sqlagent_info (attribute, value) VALUES ('DateEnlisted', CONVERT(VARCHAR(10), GETDATE(), 112))
  END

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_TARGETSERVER                                     */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_targetserver...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_targetserver')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_targetserver
go
CREATE PROCEDURE sp_delete_targetserver
  @server_name        sysname,
  @clear_downloadlist BIT = 1,
  @post_defection     BIT = 1
AS
BEGIN
  DECLARE @server_id INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @server_name = UPPER(LTRIM(RTRIM(@server_name)))

  -- Check server name
  SELECT @server_id = server_id
  FROM msdb.dbo.systargetservers
  WHERE (UPPER(server_name) = @server_name)

  IF (@server_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@server_name', @server_name)
    RETURN(1) -- Failure
  END

  BEGIN TRANSACTION

    IF (@clear_downloadlist = 1)
    BEGIN
      DELETE FROM msdb.dbo.sysdownloadlist
      WHERE (target_server = @server_name)
    END

    IF (@post_defection = 1)
    BEGIN
      -- Post a defect instruction to the server
      -- NOTE: We must do this BEFORE deleting the systargetservers row
      EXECUTE msdb.dbo.sp_post_msx_operation 'DEFECT', 'SERVER', 0x00, @server_name
    END

    DELETE FROM msdb.dbo.systargetservers
    WHERE (server_id = @server_id)

    DELETE FROM msdb.dbo.systargetservergroupmembers
    WHERE (server_id = @server_id)

    DELETE FROM msdb.dbo.sysjobservers
    WHERE (server_id = @server_id)

  COMMIT TRANSACTION

  RETURN(@@error) -- 0 means success
END
go


/**************************************************************/
/* SP_ENLIST_TSX                                              */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_enlist_tsx' 
go

IF EXISTS (SELECT name FROM sysobjects 
         WHERE name = 'sp_enlist_tsx' AND type = 'P')
   DROP PROCEDURE sp_enlist_tsx
GO

create proc sp_enlist_tsx
   @Action int,            -- 0 - enlist; 1 - defect; 2 - update
   @ServerName  sysname,      -- tsx server name
   @Location  nvarchar(200),  -- tsx server location
   @TimeZoneAdjustment int,   -- tsx server time zone adjustment
   @LocalTime datetime,    -- tsx server local time
   @NTUserName nvarchar(100), -- name of the user performing the enlistment
   @PollInterval int,          -- polling interval
    @TSX_Version int = 0        -- VersionMajor: ((@TSX_Version / 0x1000000) & 0xff)
                                -- VersionMinor: ((@TSX_Version / 0x10000) & 0xff)
                                -- Build no:      (@TSX_Version & 0xFFFF)
as
begin
   SET NOCOUNT ON

   /* check permissions */
   IF (ISNULL(IS_MEMBER(N'TargetServersRole'), 0) = 0) AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0)
   begin
   raiserror(15003,-1,-1, N'TargetServersRole')
   return 1
   end

   --9.0 and above servers set this version param
   if(@TSX_Version is null)
      set @TSX_Version = 0

   --Only check version during enlistment
   if(@Action = 0 AND ((@TSX_Version / 0x1000000) & 0xff) < 9)
   begin
      DECLARE @majorVer int, @minorVer int, @buildNo int
      SELECT @majorVer = ((@@microsoftversion / 0x1000000) & 0xff),
             @minorVer = ((@@microsoftversion / 0x10000) & 0xff),
             @buildNo = (@@microsoftversion & 0xfff)

      raiserror(14306, -1, -1, @majorVer, @minorVer, @buildNo )
      return 12
   end

   /* check input parameters */
   if @ServerName is null
   begin
   raiserror(14043, -1, -1, '@ServerName')
   return 2
   end

   select @ServerName = LTRIM(@ServerName)
   select @ServerName = RTRIM(@ServerName)
   if @ServerName = ''
   begin
   raiserror(21263, -1, -1, '@ServerName')
   return 3
   end

   select @ServerName = UPPER(@ServerName)

   if @Action <> 1 And @Action <> 2
   begin
   /* default action is to enlist */
   select @Action = 0
   end

  if @Action = 0 /* enlisting */
  begin
   /* check input parameters */
   if @NTUserName is null
   begin
      raiserror(14043, -1, -1, '@NTUserName')
      return 4
   end

   select @NTUserName = LTRIM(@NTUserName)
   select @NTUserName = RTRIM(@NTUserName)
   if @NTUserName = ''
   begin
     raiserror(21263, -1, -1, '@NTUserName')
     return 5
   end

   /* check if local server is already configured as TSX machine */
   declare @msx_server_name sysname
   select @msx_server_name = N''

   execute master.dbo.xp_instance_regread 
      N'HKEY_LOCAL_MACHINE',
      N'Software\Microsoft\MSSQLServer\SQLServerAgent',
      N'MSXServerName',
      @msx_server_name OUTPUT

   select @msx_server_name = LTRIM(@msx_server_name)
   select @msx_server_name = RTRIM(@msx_server_name)
   if @msx_server_name <> N''
   begin
      raiserror(14360, -1, -1, @@SERVERNAME)
      return 6
   end

   /* 
   * check that local server is not running a desktop SKU, 
   * i.e. Win9x, Office, or MSDE
   */
   if( PLATFORM() & 0x100 = 0x100 )
   begin
      raiserror(14362, -1, -1)
      return 8
   end

   /* check if we have any MSXOperators defined */
   if not exists (SELECT * FROM msdb.dbo.sysoperators WHERE name = N'MSXOperator')
   begin
      raiserror(14363, -1, -1)
      return 9
   end

   /* all checks have passed, insert new row into systargetservers table */
   INSERT INTO msdb.dbo.systargetservers 
   (
   server_name, 
   location, 
   time_zone_adjustment, 
   enlist_date, 
   last_poll_date, 
   status, 
   local_time_at_last_poll, 
   enlisted_by_nt_user, 
   poll_interval
   ) 
   VALUES 
   (
   @ServerName, 
   @Location, 
   @TimeZoneAdjustment, 
   GETDATE(), 
   GETDATE(), 
   1, 
   @LocalTime, 
   @NTUserName, 
   @PollInterval
   )

   /* delete left behind rows from sysdownloadlist */
   DELETE FROM msdb.dbo.sysdownloadlist 
   WHERE target_server = @ServerName
   end

   if @Action = 2 /* updating existing enlistment */
   begin
   /* check if we have any MSXOperators defined */
   if not exists (SELECT * FROM msdb.dbo.sysoperators WHERE name = N'MSXOperator')
   begin
      raiserror(14363, -1, -1)
      return 10
   end

   /* check if TSX machine is already enlisted */
   If not exists (SELECT * FROM msdb.dbo.systargetservers WHERE UPPER(server_name) = @ServerName)
   begin
      raiserror(14364, -1, -1)
      return 11
   end

   if @Location is null /* don't update the location if it is not supplied */
   begin
      UPDATE msdb.dbo.systargetservers SET 
      time_zone_adjustment = @TimeZoneAdjustment, 
      poll_interval = @PollInterval
      WHERE (UPPER(server_name) = @ServerName)
   end
   else
   begin
      UPDATE msdb.dbo.systargetservers SET 
      location = @Location, 
      time_zone_adjustment = @TimeZoneAdjustment, 
      poll_interval = @PollInterval
      WHERE (UPPER(server_name) = @ServerName)
   end
   end

  if @Action = 1 /* defecting */
  begin
   if (exists (SELECT * FROM msdb.dbo.systargetservers WHERE UPPER(server_name) = @ServerName)) 
   begin
      execute msdb.dbo.sp_delete_targetserver 
         @server_name = @ServerName, 
         @post_defection = 0 
   end
   else
   begin
      DELETE FROM msdb.dbo.sysdownloadlist 
      WHERE (target_server = @ServerName)
   end
  end

  if @Action = 0 Or @Action = 2 /* enlisting or updating existing enlistment */
  begin
   /* select resultset to return to the caller */
   SELECT 
   id,
   name, 
   enabled, 
   email_address, 
   pager_address, 
   netsend_address, 
   weekday_pager_start_time, 
   weekday_pager_end_time, 
   saturday_pager_start_time, 
   saturday_pager_end_time, 
   sunday_pager_start_time, 
   sunday_pager_end_time, 
   pager_days 
   FROM 
   msdb.dbo.sysoperators WHERE (name = N'MSXOperator')
   end
end
go


/**************************************************************/
/* SP_GET_SQLAGENT_PROPERTIES                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_get_sqlagent_properties...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_get_sqlagent_properties')
              AND (type = 'P')))
  DROP PROCEDURE sp_get_sqlagent_properties
go
CREATE PROCEDURE sp_get_sqlagent_properties
AS
BEGIN
  DECLARE @auto_start                  INT
  DECLARE @startup_account             NVARCHAR(100)
  DECLARE @msx_server_name             sysname

  -- Non-SQLDMO exposed properties
  DECLARE @sqlserver_restart           INT
  DECLARE @jobhistory_max_rows         INT
  DECLARE @jobhistory_max_rows_per_job INT
  DECLARE @errorlog_file               NVARCHAR(255)
  DECLARE @errorlogging_level          INT
  DECLARE @error_recipient             NVARCHAR(30)
  DECLARE @monitor_autostart           INT
  DECLARE @local_host_server           sysname
  DECLARE @job_shutdown_timeout        INT
  DECLARE @cmdexec_account             VARBINARY(64)
  DECLARE @regular_connections         INT
  DECLARE @host_login_name             sysname
  DECLARE @host_login_password         VARBINARY(512)
  DECLARE @login_timeout               INT
  DECLARE @idle_cpu_percent            INT
  DECLARE @idle_cpu_duration           INT
  DECLARE @oem_errorlog                INT
  DECLARE @email_profile               NVARCHAR(64)
  DECLARE @email_save_in_sent_folder   INT
  DECLARE @cpu_poller_enabled          INT
  DECLARE @alert_replace_runtime_tokens INT

  SET NOCOUNT ON

  -- NOTE: We return all SQLServerAgent properties at one go for performance reasons

  -- Read the values from the registry
  IF ((PLATFORM() & 0x1) = 0x1) -- NT
  BEGIN
    DECLARE @key NVARCHAR(200)

    SELECT @key = N'SYSTEM\CurrentControlSet\Services\'
    IF (SERVERPROPERTY('INSTANCENAME') IS NOT NULL)
      SELECT @key = @key + N'SQLAgent$' + CONVERT (sysname, SERVERPROPERTY('INSTANCENAME'))
    ELSE
      SELECT @key = @key + N'SQLServerAgent'

    EXECUTE master.dbo.xp_regread N'HKEY_LOCAL_MACHINE',
                                  @key,
                                  N'Start',
                                  @auto_start OUTPUT,
                                  N'no_output'
    EXECUTE master.dbo.xp_regread N'HKEY_LOCAL_MACHINE',
                                  @key,
                                  N'ObjectName',
                                  @startup_account OUTPUT,
                                  N'no_output'
  END
  ELSE
  BEGIN
    SELECT @auto_start = 3 -- Manual start
    SELECT @startup_account = NULL
  END
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'MSXServerName',
                                         @msx_server_name OUTPUT,
                                         N'no_output'

  -- Non-SQLDMO exposed properties
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'RestartSQLServer',
                                         @sqlserver_restart OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'JobHistoryMaxRows',
                                         @jobhistory_max_rows OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'JobHistoryMaxRowsPerJob',
                                         @jobhistory_max_rows_per_job OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'ErrorLogFile',
                                         @errorlog_file OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'ErrorLoggingLevel',
                                         @errorlogging_level OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'ErrorMonitor',
                                         @error_recipient OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'MonitorAutoStart',
                                         @monitor_autostart OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'ServerHost',
                                         @local_host_server OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'JobShutdownTimeout',
                                         @job_shutdown_timeout OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'CmdExecAccount',
                                         @cmdexec_account OUTPUT,
                                         N'no_output'
  SET @regular_connections = 0
  SET @host_login_name = NULL
  SET @host_login_password = NULL

  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'LoginTimeout',
                                         @login_timeout OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'IdleCPUPercent',
                                         @idle_cpu_percent OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'IdleCPUDuration',
                                         @idle_cpu_duration OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'OemErrorLog',
                                         @oem_errorlog OUTPUT,
                                         N'no_output'

  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'EmailProfile',
                                         @email_profile OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'EmailSaveSent',
                                         @email_save_in_sent_folder OUTPUT,
                                         N'no_output'

  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'AlertReplaceRuntimeTokens',
                                         @alert_replace_runtime_tokens OUTPUT,
                                         N'no_output'

  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'CoreEngineMask',
                                         @cpu_poller_enabled OUTPUT,
                                         N'no_output'
  IF (@cpu_poller_enabled IS NOT NULL)
    SELECT @cpu_poller_enabled = CASE WHEN (@cpu_poller_enabled & 32) = 32 THEN 0 ELSE 1 END

  -- Return the values to the client
  SELECT auto_start = CASE @auto_start
                        WHEN 2 THEN 1 -- 2 means auto-start
                        WHEN 3 THEN 0 -- 3 means don't auto-start
                        ELSE 0        -- Safety net
                      END,
         msx_server_name = @msx_server_name,
         sqlagent_type = (SELECT CASE
                                    WHEN (COUNT(*) = 0) AND (ISNULL(DATALENGTH(@msx_server_name), 0) = 0) THEN 1 -- Standalone
                                    WHEN (COUNT(*) = 0) AND (ISNULL(DATALENGTH(@msx_server_name), 0) > 0) THEN 2 -- TSX
                                    WHEN (COUNT(*) > 0) AND (ISNULL(DATALENGTH(@msx_server_name), 0) = 0) THEN 3 -- MSX
                                    WHEN (COUNT(*) > 0) AND (ISNULL(DATALENGTH(@msx_server_name), 0) > 0) THEN 0 -- Multi-Level MSX (currently invalid)
                                    ELSE 0 -- Invalid
                                  END
                           FROM msdb.dbo.systargetservers),
         startup_account = @startup_account,

         -- Non-SQLDMO exposed properties
         sqlserver_restart = ISNULL(@sqlserver_restart, 1),
         jobhistory_max_rows = @jobhistory_max_rows,
         jobhistory_max_rows_per_job = @jobhistory_max_rows_per_job,
         errorlog_file = @errorlog_file,
         errorlogging_level = ISNULL(@errorlogging_level, 7),
         error_recipient = @error_recipient,
         monitor_autostart = ISNULL(@monitor_autostart, 0),
         local_host_server = @local_host_server,
         job_shutdown_timeout = ISNULL(@job_shutdown_timeout, 15),
         cmdexec_account = @cmdexec_account,
         regular_connections = ISNULL(@regular_connections, 0),
         host_login_name = @host_login_name,
         host_login_password = @host_login_password,
         login_timeout = ISNULL(@login_timeout, 30),
         idle_cpu_percent = ISNULL(@idle_cpu_percent, 10),
         idle_cpu_duration = ISNULL(@idle_cpu_duration, 600),
         oem_errorlog = ISNULL(@oem_errorlog, 0),
         sysadmin_only = NULL,
         email_profile = @email_profile,
         email_save_in_sent_folder = ISNULL(@email_save_in_sent_folder, 0),
         cpu_poller_enabled = ISNULL(@cpu_poller_enabled, 0),
         alert_replace_runtime_tokens = ISNULL(@alert_replace_runtime_tokens, 0)
END
go

/**************************************************************/
/* SP_SET_SQLAGENT_PROPERTIES                                 */
/**************************************************************/
IF EXISTS (SELECT * FROM msdb.dbo.sysobjects WHERE name = N'sp_set_sqlagent_properties' AND type = 'P')
BEGIN
  DROP PROCEDURE dbo.sp_set_sqlagent_properties
END
go

PRINT ''
PRINT 'Create procedure sp_set_sqlagent_properties...'
go

CREATE PROCEDURE dbo.sp_set_sqlagent_properties
  @auto_start                  INT           = NULL, -- 1 or 0
  -- Non-SQLDMO exposed properties
  @sqlserver_restart           INT           = NULL, -- 1 or 0
  @jobhistory_max_rows         INT           = NULL, -- No maximum = -1, otherwise must be > 1
  @jobhistory_max_rows_per_job INT           = NULL, -- 1 to @jobhistory_max_rows
  @errorlog_file               NVARCHAR(255) = NULL, -- Full drive\path\name of errorlog file
  @errorlogging_level          INT           = NULL, -- 1 = error, 2 = warning, 4 = information
  @error_recipient             NVARCHAR(30)  = NULL, -- Network address of error popup recipient
  @monitor_autostart           INT           = NULL, -- 1 or 0
  @local_host_server           sysname      = NULL, -- Alias of local host server
  @job_shutdown_timeout        INT           = NULL, -- 5 to 600 seconds
  @cmdexec_account             VARBINARY(64) = NULL, -- CmdExec account information
  @regular_connections         INT           = NULL, -- obsolete
  @host_login_name             sysname       = NULL, -- obsolete
  @host_login_password         VARBINARY(512) = NULL, -- obsolete
  @login_timeout               INT           = NULL, -- 5 to 45 (seconds)
  @idle_cpu_percent            INT           = NULL, -- 1 to 100
  @idle_cpu_duration           INT           = NULL, -- 20 to 86400 seconds
  @oem_errorlog                INT           = NULL, -- 1 or 0
  @sysadmin_only               INT           = NULL, -- not applicable to Yukon server, for backwards compatibility only
  @email_profile               NVARCHAR(64)  = NULL, -- Email profile name
  @email_save_in_sent_folder   INT           = NULL, -- 1 or 0
  @cpu_poller_enabled          INT           = NULL, -- 1 or 0
  @alert_replace_runtime_tokens INT          = NULL  -- 1 or 0
AS
BEGIN
  -- NOTE: We set all SQLServerAgent properties at one go for performance reasons.
  -- NOTE: You cannot set the value of the properties msx_server_name, is_msx or
  --       startup_account - they are all read only.

  DECLARE @res_valid_range           NVARCHAR(100)
  DECLARE @existing_core_engine_mask INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @errorlog_file     = LTRIM(RTRIM(@errorlog_file))
  SELECT @error_recipient   = LTRIM(RTRIM(@error_recipient))
  SELECT @local_host_server = LTRIM(RTRIM(@local_host_server))
  SELECT @host_login_name   = LTRIM(RTRIM(@host_login_name))
  SELECT @email_profile     = LTRIM(RTRIM(@email_profile))

  -- Make sure values (if supplied) are good
  IF (@auto_start IS NOT NULL)
  BEGIN
    -- NOTE: When setting the the services start value, 2 == auto-start, 3 == Don't auto-start
    SELECT @auto_start = CASE @auto_start
                           WHEN 0 THEN 3
                           WHEN 1 THEN 2
                           ELSE 3 -- Assume non auto-start if passed a junk value
                          END
  END

  -- Non-SQLDMO exposed properties
  IF ((@sqlserver_restart IS NOT NULL) AND (@sqlserver_restart <> 0))
    SELECT @sqlserver_restart = 1

  IF (@jobhistory_max_rows IS NOT NULL)
  BEGIN
    SELECT @res_valid_range = FORMATMESSAGE(14207)
    IF ((@jobhistory_max_rows < -1) OR (@jobhistory_max_rows = 0))
    BEGIN
      RAISERROR(14266, -1, -1, '@jobhistory_max_rows', @res_valid_range)
      RETURN(1) -- Failure
    END
  END
  ELSE
  BEGIN
    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'JobHistoryMaxRows',
                                           @jobhistory_max_rows OUTPUT,
                                           N'no_output'
    SELECT @jobhistory_max_rows = ISNULL(@jobhistory_max_rows, -1)
  END

  IF (@jobhistory_max_rows_per_job IS NOT NULL)
  BEGIN
    IF (@jobhistory_max_rows = -1)
      SELECT @jobhistory_max_rows_per_job = 0
    ELSE
    BEGIN
      IF ((@jobhistory_max_rows_per_job < 1) OR (@jobhistory_max_rows_per_job > @jobhistory_max_rows))
      BEGIN
        SELECT @res_valid_range = N'1..' + CONVERT(NVARCHAR, @jobhistory_max_rows)
        RAISERROR(14266, -1, -1, '@jobhistory_max_rows', @res_valid_range)
        RETURN(1) -- Failure
      END
    END
  END

  IF (@errorlogging_level IS NOT NULL) AND ((@errorlogging_level < 1) OR (@errorlogging_level > 7))
  BEGIN
    RAISERROR(14266, -1, -1, '@errorlogging_level', '1..7')
    RETURN(1) -- Failure
  END

  IF (@monitor_autostart IS NOT NULL) AND ((@monitor_autostart < 0) OR (@monitor_autostart > 1))
  BEGIN
    RAISERROR(14266, -1, -1, '@monitor_autostart', '0, 1')
    RETURN(1) -- Failure
  END

  IF (@job_shutdown_timeout IS NOT NULL) AND ((@job_shutdown_timeout < 5) OR (@job_shutdown_timeout > 600))
  BEGIN
    RAISERROR(14266, -1, -1, '@job_shutdown_timeout', '5..600')
    RETURN(1) -- Failure
  END

  IF (@login_timeout IS NOT NULL) AND ((@login_timeout < 5) OR (@login_timeout > 45))
  BEGIN
    RAISERROR(14266, -1, -1, '@login_timeout', '5..45')
    RETURN(1) -- Failure
  END

  IF ((@idle_cpu_percent IS NOT NULL) AND ((@idle_cpu_percent < 1) OR (@idle_cpu_percent > 100)))
  BEGIN
    RAISERROR(14266, -1, -1, '@idle_cpu_percent', '10..100')
    RETURN(1) -- Failure
  END

  IF ((@idle_cpu_duration IS NOT NULL) AND ((@idle_cpu_duration < 20) OR (@idle_cpu_duration > 86400)))
  BEGIN
    RAISERROR(14266, -1, -1, '@idle_cpu_duration', '20..86400')
    RETURN(1) -- Failure
  END

  IF (@oem_errorlog IS NOT NULL) AND ((@oem_errorlog < 0) OR (@oem_errorlog > 1))
  BEGIN
    RAISERROR(14266, -1, -1, '@oem_errorlog', '0, 1')
    RETURN(1) -- Failure
  END

  IF (@sysadmin_only IS NOT NULL)
  BEGIN
    RAISERROR(14378, -1, -1)
    RETURN(1) -- Failure
  END

  IF (@email_save_in_sent_folder IS NOT NULL) AND ((@email_save_in_sent_folder < 0) OR (@email_save_in_sent_folder > 1))
  BEGIN
    RAISERROR(14266, -1, -1, 'email_save_in_sent_folder', '0, 1')
    RETURN(1) -- Failure
  END

  IF (@cpu_poller_enabled IS NOT NULL) AND ((@cpu_poller_enabled < 0) OR (@cpu_poller_enabled > 1))
  BEGIN
    RAISERROR(14266, -1, -1, 'cpu_poller_enabled', '0, 1')
    RETURN(1) -- Failure
  END

  IF (@alert_replace_runtime_tokens IS NOT NULL) AND ((@alert_replace_runtime_tokens < 0) OR (@alert_replace_runtime_tokens > 1))
  BEGIN
    RAISERROR(14266, -1, -1, 'alert_replace_runtime_tokens', '0, 1')
    RETURN(1) -- Failure
  END

  -- Write out the values
  IF (@auto_start IS NOT NULL)
  BEGIN
    IF ((PLATFORM() & 0x1) = 0x1) -- NT
    BEGIN
      DECLARE @key NVARCHAR(200)

      SELECT @key = N'SYSTEM\CurrentControlSet\Services\'
      IF (SERVERPROPERTY('INSTANCENAME') IS NOT NULL)
        SELECT @key = @key + N'SQLAgent$' + CONVERT (sysname, SERVERPROPERTY('INSTANCENAME'))
      ELSE
        SELECT @key = @key + N'SQLServerAgent'

      EXECUTE master.dbo.xp_regwrite N'HKEY_LOCAL_MACHINE',
                                     @key,
                                     N'Start',
                                     N'REG_DWORD',
                                     @auto_start
    END
    ELSE
      RAISERROR(14546, 16, 1, '@auto_start')
  END

  -- Non-SQLDMO exposed properties
  IF (@sqlserver_restart IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'RestartSQLServer',
                                            N'REG_DWORD',
                                            @sqlserver_restart
  IF (@jobhistory_max_rows IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'JobHistoryMaxRows',
                                            N'REG_DWORD',
                                            @jobhistory_max_rows
  IF (@jobhistory_max_rows_per_job IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'JobHistoryMaxRowsPerJob',
                                            N'REG_DWORD',
                                            @jobhistory_max_rows_per_job
  IF (@errorlog_file IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'ErrorLogFile',
                                            N'REG_SZ',
                                            @errorlog_file
  IF (@errorlogging_level IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'ErrorLoggingLevel',
                                            N'REG_DWORD',
                                            @errorlogging_level
  IF (@error_recipient IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'ErrorMonitor',
                                            N'REG_SZ',
                                            @error_recipient
  IF (@monitor_autostart IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'MonitorAutoStart',
                                            N'REG_DWORD',
                                            @monitor_autostart
  IF (@local_host_server IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'ServerHost',
                                            N'REG_SZ',
                                            @local_host_server
  IF (@job_shutdown_timeout IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'JobShutdownTimeout',
                                            N'REG_DWORD',
                                            @job_shutdown_timeout
  IF (@cmdexec_account IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'CmdExecAccount',
                                            N'REG_BINARY',
                                            @cmdexec_account

  IF (@login_timeout IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'LoginTimeout',
                                            N'REG_DWORD',
                                            @login_timeout
  IF (@idle_cpu_percent IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'IdleCPUPercent',
                                            N'REG_DWORD',
                                            @idle_cpu_percent
  IF (@idle_cpu_duration IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'IdleCPUDuration',
                                            N'REG_DWORD',
                                            @idle_cpu_duration
  IF (@oem_errorlog IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'OemErrorLog',
                                            N'REG_DWORD',
                                            @oem_errorlog

  IF (@email_profile IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'EmailProfile',
                                            N'REG_SZ',
                                            @email_profile
  IF (@email_save_in_sent_folder IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'EmailSaveSent',
                                            N'REG_DWORD',
                                            @email_save_in_sent_folder

  IF (@alert_replace_runtime_tokens IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'AlertReplaceRuntimeTokens',
                                            N'REG_DWORD',
                                            @alert_replace_runtime_tokens  
  IF (@cpu_poller_enabled IS NOT NULL)
  BEGIN
    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'CoreEngineMask',
                                           @existing_core_engine_mask OUTPUT,
                                           N'no_output'

  

    IF ((@existing_core_engine_mask IS NOT NULL) OR (@cpu_poller_enabled = 1))
    BEGIN
      IF (@cpu_poller_enabled = 1)
        SELECT @cpu_poller_enabled = (ISNULL(@existing_core_engine_mask, 0) & ~32)
      ELSE
        SELECT @cpu_poller_enabled = (ISNULL(@existing_core_engine_mask, 0) | 32)

      IF ((@existing_core_engine_mask IS NOT NULL) AND (@cpu_poller_enabled = 32))
        EXECUTE master.dbo.xp_instance_regdeletevalue N'HKEY_LOCAL_MACHINE',
                                                      N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                                      N'CoreEngineMask'
      ELSE
        EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                                N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                                N'CoreEngineMask',
                                                N'REG_DWORD',
                                                @cpu_poller_enabled
    END
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_ADD_TARGETSERVERGROUP                                   */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_targetservergroup...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_targetservergroup')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_targetservergroup
go
CREATE PROCEDURE sp_add_targetservergroup
  @name sysname
AS
BEGIN
  SET NOCOUNT ON

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) 
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @name = LTRIM(RTRIM(@name))

  -- Check if the group already exists
  IF (EXISTS (SELECT *
              FROM msdb.dbo.systargetservergroups
              WHERE name = @name))
  BEGIN
    RAISERROR(14261, -1, -1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Disallow names with commas in them (since sp_apply_job_to_targets parses a comma-separated list of group names)
  IF (@name LIKE N'%,%')
  BEGIN
    RAISERROR(14289, -1, -1, '@name', ',')
    RETURN(1) -- Failure
  END

  INSERT INTO msdb.dbo.systargetservergroups (name)
  VALUES (@name)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_UPDATE_TARGETSERVERGROUP                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_targetservergroup...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_targetservergroup')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_targetservergroup
go
CREATE PROCEDURE sp_update_targetservergroup
  @name     sysname,
  @new_name sysname
AS
BEGIN
  SET NOCOUNT ON

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) 
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @name     = LTRIM(RTRIM(@name))
  SELECT @new_name = LTRIM(RTRIM(@new_name))

  -- Check if the group exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.systargetservergroups
                  WHERE (name = @name)))
  BEGIN
    RAISERROR(14262, -1, -1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Check if a group with the new name already exists
  IF (EXISTS (SELECT *
              FROM msdb.dbo.systargetservergroups
              WHERE (name = @new_name)))
  BEGIN
    RAISERROR(14261, -1, -1, '@new_name', @new_name)
    RETURN(1) -- Failure
  END

  -- Disallow names with commas in them (since sp_apply_job_to_targets parses a comma-separated list of group names)
  IF (@new_name LIKE N'%,%')
  BEGIN
    RAISERROR(14289, -1, -1, '@new_name', ',')
    RETURN(1) -- Failure
  END

  -- Update the group's name
  UPDATE msdb.dbo.systargetservergroups
  SET name = @new_name
  WHERE (name = @name)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_TARGETSERVERGROUP                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_targetservergroup...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_targetservergroup')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_targetservergroup
go
CREATE PROCEDURE sp_delete_targetservergroup
  @name sysname
AS
BEGIN
  DECLARE @servergroup_id INT

  SET NOCOUNT ON

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) 
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @name = LTRIM(RTRIM(@name))

  -- Check if the group exists
  SELECT @servergroup_id = servergroup_id
  FROM msdb.dbo.systargetservergroups
  WHERE (name = @name)

  IF (@servergroup_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Remove the group members
  DELETE FROM msdb.dbo.systargetservergroupmembers
  WHERE (servergroup_id = @servergroup_id)

  -- Remove the group
  DELETE FROM msdb.dbo.systargetservergroups
  WHERE (name = @name)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_HELP_TARGETSERVERGROUP                                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_targetservergroup...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_targetservergroup')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_targetservergroup
go
CREATE PROCEDURE sp_help_targetservergroup
  @name sysname = NULL
AS
BEGIN
  DECLARE @servergroup_id INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name = LTRIM(RTRIM(@name))

  IF (@name IS NULL)
  BEGIN
    -- Show all groups
    SELECT servergroup_id, name
    FROM msdb.dbo.systargetservergroups
    RETURN(@@error) -- 0 means success
  END
  ELSE
  BEGIN
    -- Check if the group exists
    SELECT @servergroup_id = servergroup_id
    FROM msdb.dbo.systargetservergroups
    WHERE (name = @name)

    IF (@servergroup_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@name', @name)
      RETURN(1) -- Failure
    END

    -- Return the members of the group
    SELECT sts.server_id,
           sts.server_name
    FROM msdb.dbo.systargetservers sts,
         msdb.dbo.systargetservergroupmembers stsgm
    WHERE (stsgm.servergroup_id = @servergroup_id)
      AND (stsgm.server_id = sts.server_id)

    RETURN(@@error) -- 0 means success
  END
END
go

/**************************************************************/
/* SP_ADD_TARGETSVRGRP_MEMBER                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_targetsvgrp_member...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_targetsvrgrp_member')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_targetsvrgrp_member
go
CREATE PROCEDURE sp_add_targetsvrgrp_member
  @group_name  sysname,
  @server_name sysname
AS
BEGIN
  DECLARE @servergroup_id INT
  DECLARE @server_id      INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @group_name = LTRIM(RTRIM(@group_name))
  SELECT @server_name = UPPER(LTRIM(RTRIM(@server_name)))

  -- Check if the group exists
  SELECT @servergroup_id = servergroup_id
  FROM msdb.dbo.systargetservergroups
  WHERE (name = @group_name)

  IF (@servergroup_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@group_name', @group_name)
    RETURN(1) -- Failure
  END

  -- Check if the server exists
  SELECT @server_id = server_id
  FROM msdb.dbo.systargetservers
  WHERE (UPPER(server_name) = @server_name)

  IF (@server_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@server_name', @server_name)
    RETURN(1) -- Failure
  END

  -- Check if the server is already in this group
  IF (EXISTS (SELECT *
              FROM msdb.dbo.systargetservergroupmembers
              WHERE (servergroup_id = @servergroup_id)
                AND (server_id = @server_id)))
  BEGIN
    RAISERROR(14263, -1, -1, @server_name, @group_name)
    RETURN(1) -- Failure
  END

  -- Add the row to systargetservergroupmembers
  INSERT INTO msdb.dbo.systargetservergroupmembers
  VALUES (@servergroup_id, @server_id)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_TARGETSVRGRP_MEMBER                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_targetsvrgrp_member...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_targetsvrgrp_member')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_targetsvrgrp_member
go
CREATE PROCEDURE sp_delete_targetsvrgrp_member
  @group_name  sysname,
  @server_name sysname
AS
BEGIN
  DECLARE @servergroup_id INT
  DECLARE @server_id      INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @group_name = LTRIM(RTRIM(@group_name))
  SELECT @server_name = UPPER(LTRIM(RTRIM(@server_name)))

  -- Check if the group exists
  SELECT @servergroup_id = servergroup_id
  FROM msdb.dbo.systargetservergroups
  WHERE (name = @group_name)

  IF (@servergroup_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@group_name', @group_name)
    RETURN(1) -- Failure
  END

  -- Check if the server exists
  SELECT @server_id = server_id
  FROM msdb.dbo.systargetservers
  WHERE (UPPER(server_name) = @server_name)

  IF (@server_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@server_name', @server_name)
    RETURN(1) -- Failure
  END

  -- Check if the server is in the group
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.systargetservergroupmembers
                  WHERE (servergroup_id = @servergroup_id)
                    AND (server_id = @server_id)))
  BEGIN
    RAISERROR(14264, -1, -1, @server_name, @group_name)
    RETURN(1) -- Failure
  END

  -- Delete the row from systargetservergroupmembers
  DELETE FROM msdb.dbo.systargetservergroupmembers
  WHERE (servergroup_id = @servergroup_id)
    AND (server_id = @server_id)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_VERIFY_CATEGORY                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_category...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_category')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_category
go
CREATE PROCEDURE sp_verify_category
  @class          VARCHAR(8),
  @type           VARCHAR(12)  = NULL, -- Supply NULL only if you don't want it checked
  @name           sysname      = NULL, -- Supply NULL only if you don't want it checked
  @category_class INT OUTPUT,
  @category_type  INT OUTPUT           -- Supply NULL only if you don't want the return value
AS
BEGIN
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @class = LTRIM(RTRIM(@class))
  SELECT @type  = LTRIM(RTRIM(@type))
  SELECT @name  = LTRIM(RTRIM(@name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@type = '') SELECT @type = NULL
  IF (@name = N'') SELECT @name = NULL

  -- Check class
  SELECT @class = UPPER(@class collate SQL_Latin1_General_CP1_CS_AS)
  SELECT @category_class = CASE @class
                             WHEN 'JOB'      THEN 1
                             WHEN 'ALERT'    THEN 2
                             WHEN 'OPERATOR' THEN 3
                             ELSE 0
                           END
  IF (@category_class = 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@class', 'JOB, ALERT, OPERATOR')
    RETURN(1) -- Failure
  END

  -- Check name
  IF ((@name IS NOT NULL) AND (@name = N'[DEFAULT]'))
  BEGIN
    RAISERROR(14200, -1, -1, '@name')
    RETURN(1) -- Failure
  END

  -- Check type [optionally]
  IF (@type IS NOT NULL)
  BEGIN
    IF (@class = 'JOB')
    BEGIN
      SELECT @type = UPPER(@type collate SQL_Latin1_General_CP1_CS_AS)
      SELECT @category_type = CASE @type
                                WHEN 'LOCAL'        THEN 1
                                WHEN 'MULTI-SERVER' THEN 2
                                ELSE 0
                              END
      IF (@category_type = 0)
      BEGIN
        RAISERROR(14266, -1, -1, '@type', 'LOCAL, MULTI-SERVER')
        RETURN(1) -- Failure
      END
    END
    ELSE
    BEGIN
      IF (@type <> 'NONE')
      BEGIN
        RAISERROR(14266, -1, -1, '@type', 'NONE')
        RETURN(1) -- Failure
      END
      ELSE
        SELECT @category_type = 3
    END
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_ADD_CATEGORY                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_category...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_category')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_category
go
CREATE PROCEDURE sp_add_category
  @class VARCHAR(8)   = 'JOB',   -- JOB or ALERT or OPERATOR
  @type  VARCHAR(12)  = 'LOCAL', -- LOCAL or MULTI-SERVER (for JOB) or NONE otherwise
  @name  sysname
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @category_type  INT
  DECLARE @category_class INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @class = LTRIM(RTRIM(@class))
  SELECT @type  = LTRIM(RTRIM(@type))
  SELECT @name  = LTRIM(RTRIM(@name))

  EXECUTE @retval = sp_verify_category @class,
                                       @type,
                                       @name,
                                       @category_class OUTPUT,
                                       @category_type  OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check name
  IF (EXISTS (SELECT *
              FROM msdb.dbo.syscategories
              WHERE (category_class = @category_class)
                AND (name = @name)))
  BEGIN
    RAISERROR(14261, -1, -1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Add the row
  INSERT INTO msdb.dbo.syscategories (category_class, category_type, name)
  VALUES (@category_class, @category_type, @name)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_UPDATE_CATEGORY                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_category...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_category')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_category
go
CREATE PROCEDURE sp_update_category
  @class    VARCHAR(8),  -- JOB or ALERT or OPERATOR
  @name     sysname,
  @new_name sysname
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @category_id    INT
  DECLARE @category_class INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @class    = LTRIM(RTRIM(@class))
  SELECT @name     = LTRIM(RTRIM(@name))
  SELECT @new_name = LTRIM(RTRIM(@new_name))

  --turn empy parametrs tu null parameters
  IF @name = ''  SELECT @name = NULL

  EXECUTE @retval = sp_verify_category @class,
                                       NULL,
                                       @new_name,
                                       @category_class OUTPUT,
                                       NULL
  IF (@retval <> 0)
    RETURN(1) -- Failure

  --ID @name not null check id such a category exists
  --check name - it should exist if not null
  IF @name IS NOT NULL AND
     NOT EXISTS(SELECT * FROM msdb.dbo.syscategories WHERE name = @name 
      AND category_class = @category_class)
  BEGIN
      RAISERROR(14526, -1, -1, @name, @category_class)
      RETURN(1) -- Failure
  END  

  -- Check name
  SELECT @category_id = category_id
  FROM msdb.dbo.syscategories
  WHERE (category_class = @category_class)
    AND (name = @new_name)
  IF (@category_id IS NOT NULL)
  BEGIN
    RAISERROR(14261, -1, -1, '@new_name', @new_name)
    RETURN(1) -- Failure
  END

  -- Make sure that we're not updating one of the permanent categories (id's 0 - 99)
  IF (@category_id < 100)
  BEGIN
    RAISERROR(14276, -1, -1, @name, @class)
    RETURN(1) -- Failure
  END

  -- Update the category name
  UPDATE msdb.dbo.syscategories
  SET name = @new_name
  WHERE (category_class = @category_class)
    AND (name = @name)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_CATEGORY                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_category...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_category')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_category
go
CREATE PROCEDURE sp_delete_category
  @class VARCHAR(8),  -- JOB or ALERT or OPERATOR
  @name  sysname
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @category_id    INT
  DECLARE @category_class INT
  DECLARE @category_type  INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @class = LTRIM(RTRIM(@class))
  SELECT @name  = LTRIM(RTRIM(@name))

  EXECUTE @retval = sp_verify_category @class,
                                       NULL,
                                       NULL,
                                       @category_class OUTPUT,
                                       NULL
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check name
  SELECT @category_id = category_id,
         @category_type = category_type
  FROM msdb.dbo.syscategories
  WHERE (category_class = @category_class)
    AND (name = @name)
  IF (@category_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Make sure that we're not deleting one of the permanent categories (id's 0 - 99)
  IF (@category_id < 100)
  BEGIN
    RAISERROR(14276, -1, -1, @name, @class)
    RETURN(1) -- Failure
  END

  BEGIN TRANSACTION

    -- Clean-up any Jobs that reference the deleted category
    UPDATE msdb.dbo.sysjobs
    SET category_id = CASE @category_type
                        WHEN 1 THEN 0 -- [Uncategorized (Local)]
                        WHEN 2 THEN 2 -- [Uncategorized (Multi-Server)]
                      END
    WHERE (category_id = @category_id)

    -- Clean-up any Alerts that reference the deleted category
    UPDATE msdb.dbo.sysalerts
    SET category_id = 98
    WHERE (category_id = @category_id)

    -- Clean-up any Operators that reference the deleted category
    UPDATE msdb.dbo.sysoperators
    SET category_id = 99
    WHERE (category_id = @category_id)

    -- Finally, delete the category itself
    DELETE FROM msdb.dbo.syscategories
    WHERE (category_id = @category_id)

  COMMIT TRANSACTION

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_HELP_CATEGORY                                           */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_category...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_category')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_category
go
CREATE PROCEDURE sp_help_category
  @class  VARCHAR(8)   = 'JOB', -- JOB, ALERT or OPERATOR
  @type   VARCHAR(12)  = NULL,  -- LOCAL, MULTI-SERVER, or NONE
  @name   sysname      = NULL,
  @suffix BIT          = 0      -- 0 = no suffix, 1 = add suffix
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @type_in        VARCHAR(12)
  DECLARE @category_type  INT
  DECLARE @category_class INT
  DECLARE @where_clause   NVARCHAR(500)
  DECLARE @cmd            NVARCHAR(max)

  SET NOCOUNT ON

  -- Both name and type can be NULL (this is valid, indeed it is how SQLDMO populates
  -- the JobCategory collection)

  -- Remove any leading/trailing spaces from parameters
  SELECT @class = LTRIM(RTRIM(@class))
  SELECT @type  = LTRIM(RTRIM(@type))
  SELECT @name  = LTRIM(RTRIM(@name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@type = '') SELECT @type = NULL
  IF (@name = N'') SELECT @name = NULL

  -- Check the type and class
  IF (@class = 'JOB') AND (@type IS NULL)
    SELECT @type_in = 'LOCAL' -- This prevents sp_verify_category from failing
  ELSE
  IF (@class <> 'JOB') AND (@type IS NULL)
    SELECT @type_in = 'NONE'
  ELSE
    SELECT @type_in = @type

  EXECUTE @retval = sp_verify_category @class,
                                       @type_in,
                                       NULL,
                                       @category_class OUTPUT,
                                       @category_type  OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Make sure that 'suffix' is either 0 or 1
  IF (@suffix <> 0)
    SELECT @suffix = 1

  --check name - it should exist if not null
  IF @name IS NOT NULL AND
     NOT EXISTS(SELECT * FROM msdb.dbo.syscategories WHERE name = @name
      AND category_class = @category_class)
  BEGIN
      DECLARE @category_class_string NVARCHAR(25)
      SET @category_class_string = CAST(@category_class AS nvarchar(25))
      RAISERROR(14526, -1, -1, @name, @category_class_string)
      RETURN(1) -- Failure
  END
       

  -- Build the WHERE qualifier
  SELECT @where_clause = N'WHERE (category_class = ' + CONVERT(NVARCHAR, @category_class) + N') '
  IF (@name IS NOT NULL)
    SELECT @where_clause = @where_clause + N'AND (name = N' + QUOTENAME(@name, '''') + N') '
  IF (@type IS NOT NULL)
    SELECT @where_clause = @where_clause + N'AND (category_type = ' + CONVERT(NVARCHAR, @category_type) + N') '

  -- Construct the query
  SELECT @cmd = N'SELECT category_id, '
  IF (@suffix = 1)
  BEGIN
    SELECT @cmd = @cmd + N'''category_type'' = '
    SELECT @cmd = @cmd + N'CASE category_type '
    SELECT @cmd = @cmd + N'WHEN 0 THEN ''NONE'' '
    SELECT @cmd = @cmd + N'WHEN 1 THEN ''LOCAL'' '
    SELECT @cmd = @cmd + N'WHEN 2 THEN ''MULTI-SERVER'' '
    SELECT @cmd = @cmd + N'WHEN 3 THEN ''NONE'' '
    SELECT @cmd = @cmd + N'ELSE FORMATMESSAGE(14205) '
    SELECT @cmd = @cmd + N'END, '
  END
  ELSE
  BEGIN
    SELECT @cmd = @cmd + N'category_type, '
  END
  SELECT @cmd = @cmd + N'name '
  SELECT @cmd = @cmd + N'FROM msdb.dbo.syscategories '

  -- Execute the query
  EXECUTE (@cmd + @where_clause + N'ORDER BY category_type, name')

  RETURN(@@error) -- 0 means success
END
go


/**************************************************************/
/* SP_HELP_TARGETSERVER                                       */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_targetserver...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_targetserver')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_targetserver
go
CREATE PROCEDURE sp_help_targetserver
  @server_name sysname = NULL
AS
BEGIN
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @server_name = UPPER(LTRIM(RTRIM(@server_name)))

  IF (@server_name IS NOT NULL)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.systargetservers
                    WHERE (UPPER(server_name) = @server_name)))
    BEGIN
      RAISERROR(14262, -1, -1, '@server_name', @server_name)
      RETURN(1) -- Failure
    END
  END

  DECLARE @unread_instructions TABLE
  (
  target_server       sysname COLLATE database_default,
  unread_instructions INT
  )

  INSERT INTO @unread_instructions
  SELECT target_server, COUNT(*)
  FROM msdb.dbo.sysdownloadlist
  WHERE (status = 0)
  GROUP BY target_server

  SELECT sts.server_id,
         sts.server_name,
         sts.location,
         sts.time_zone_adjustment,
         sts.enlist_date,
         sts.last_poll_date,
        'status' = sts.status |
                   CASE WHEN DATEDIFF(ss, sts.last_poll_date, GETDATE()) > (3 * sts.poll_interval) THEN 0x2 ELSE 0 END |
                   CASE WHEN ((SELECT COUNT(*)
                               FROM msdb.dbo.sysdownloadlist sdl
                               WHERE (sdl.target_server = sts.server_name)
                                 AND (sdl.error_message IS NOT NULL)) > 0) THEN 0x4 ELSE 0 END,
        'unread_instructions' = ISNULL(ui.unread_instructions, 0),
        'local_time' = DATEADD(SS, DATEDIFF(SS, sts.last_poll_date, GETDATE()), sts.local_time_at_last_poll),
        sts.enlisted_by_nt_user,
        sts.poll_interval
  FROM msdb.dbo.systargetservers sts LEFT OUTER JOIN
       @unread_instructions      ui  ON (sts.server_name = ui.target_server)
  WHERE ((@server_name IS NULL) OR (UPPER(sts.server_name) = @server_name))
  ORDER BY server_name

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_RESYNC_TARGETSERVER                                     */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_resync_targetserver...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_resync_targetserver')
              AND (type = 'P')))
  DROP PROCEDURE sp_resync_targetserver
go
CREATE PROCEDURE sp_resync_targetserver
  @server_name sysname
AS
BEGIN
  SET NOCOUNT ON

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) 
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @server_name = LTRIM(RTRIM(@server_name))

  IF (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) <> N'ALL')
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.systargetservers
                    WHERE (UPPER(server_name) = UPPER(@server_name))))
    BEGIN
      RAISERROR(14262, -1, -1, '@server_name', @server_name)
      RETURN(1) -- Failure
    END

    -- We want the target server to:
    -- a) delete all their current MSX jobs, and
    -- b) download all their jobs again.
    -- So we delete all the current instructions and post a new set
    DELETE FROM msdb.dbo.sysdownloadlist
    WHERE (target_server = @server_name)
    EXECUTE msdb.dbo.sp_post_msx_operation 'DELETE', 'JOB', 0x00, @server_name
    EXECUTE msdb.dbo.sp_post_msx_operation 'INSERT', 'JOB', 0x00, @server_name
  END
  ELSE
  BEGIN
    -- We want ALL target servers to:
    -- a) delete all their current MSX jobs, and
    -- b) download all their jobs again.
    -- So we delete all the current instructions and post a new set
    TRUNCATE TABLE msdb.dbo.sysdownloadlist
    EXECUTE msdb.dbo.sp_post_msx_operation 'DELETE', 'JOB', 0x00, NULL
    EXECUTE msdb.dbo.sp_post_msx_operation 'INSERT', 'JOB', 0x00, NULL
  END

  RETURN(@@error) -- 0 means success
END
go

CHECKPOINT
go

/**************************************************************/
/* SP_PURGE_JOBHISTORY                                        */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_purge_jobhistory...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_purge_jobhistory')
              AND (type = 'P')))
  DROP PROCEDURE sp_purge_jobhistory
go
CREATE PROCEDURE sp_purge_jobhistory
  @job_name     sysname          = NULL,
  @job_id       UNIQUEIDENTIFIER = NULL,
  @oldest_date  DATETIME         = NULL
AS
BEGIN
  DECLARE @rows_affected INT
  DECLARE @total_rows    INT
  DECLARE @datepart      INT
  DECLARE @timepart      INT
  DECLARE @retval        INT
  DECLARE @job_owner_sid VARBINARY(85)

  SET NOCOUNT ON

  IF(@oldest_date IS NOT NULL)
  BEGIN
    SET @datepart = CONVERT(INT, CONVERT(VARCHAR, @oldest_date, 112))
    SET @timepart = (DATEPART(hh, @oldest_date) * 10000) + (DATEPART(mi, @oldest_date) * 100) + (DATEPART(ss, @oldest_date))
  END
  ELSE
  BEGIN
    SET @datepart = 99999999
    SET @timepart = 0
  END

  IF ((@job_name IS NOT NULL) OR (@job_id IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT,
                                                 @owner_sid = @job_owner_sid OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
      
    -- Check permissions beyond what's checked by the sysjobs_view
    -- SQLAgentReader role that can see all jobs but
    -- cannot purge history of jobs they do not own
    IF (@job_owner_sid <> SUSER_SID()                      -- does not own the job
       AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)       -- is not sysadmin
       AND (ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) <> 1)) -- is not SQLAgentOperatorRole
    BEGIN
     RAISERROR(14392, -1, -1);
     RETURN(1) -- Failure
    END

    -- Delete the histories for this job
    DELETE FROM msdb.dbo.sysjobhistory
    WHERE (job_id = @job_id) AND
          ((run_date < @datepart) OR 
           (run_date <= @datepart AND run_time < @timepart))
    SELECT @rows_affected = @@rowcount
  END
  ELSE
  BEGIN
    -- Only a sysadmin or SQLAgentOperatorRole can do this
   IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)           -- is not sysadmin
       AND (ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) <> 1)) -- is not SQLAgentOperatorRole
    BEGIN
      RAISERROR(14392, -1, -1)
      RETURN(1) -- Failure
    END

    IF(@oldest_date IS NOT NULL)
    BEGIN
        DELETE FROM msdb.dbo.sysjobhistory
        WHERE ((run_date < @datepart) OR 
               (run_date <= @datepart AND run_time < @timepart))
    END
    ELSE
    BEGIN
        DELETE FROM msdb.dbo.sysjobhistory
    END
   
   SELECT @rows_affected = @@rowcount
  END

  RAISERROR(14226, 0, 1, @rows_affected)

  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_HELP_JOBHISTORY_FULL                                    */
/**************************************************************/
use [msdb]

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobhistory_full')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobhistory_full
go
CREATE PROCEDURE sp_help_jobhistory_full
               @job_id               UNIQUEIDENTIFIER,
               @job_name             sysname,
               @step_id              INT,
               @sql_message_id       INT,
               @sql_severity         INT,
               @start_run_date       INT,
               @end_run_date         INT,
               @start_run_time       INT,
               @end_run_time         INT,
               @minimum_run_duration INT,
               @run_status           INT,
               @minimum_retries      INT,
               @oldest_first         INT,
               @server               sysname,
               @mode                 VARCHAR(7),
               @order_by             INT,
               @distributed_job_history BIT
AS
IF(@distributed_job_history = 1)
  SELECT null as instance_id, 
     sj.job_id,
     job_name = sj.name,
     null as step_id,
     null as step_name,
     null as sql_message_id,
     null as sql_severity,
     sjh.last_outcome_message as message,
     sjh.last_run_outcome as run_status,
     sjh.last_run_date as run_date,
     sjh.last_run_time as run_time,
    sjh.last_run_duration as run_duration,
     null as operator_emailed,
     null as operator_netsentname,
     null as operator_paged,
     null as retries_attempted,
     sts.server_name as server
  FROM msdb.dbo.sysjobservers                sjh
  JOIN msdb.dbo.systargetservers sts ON (sts.server_id = sjh.server_id)
  JOIN msdb.dbo.sysjobs_view     sj  ON(sj.job_id = sjh.job_id)
  WHERE 
  (@job_id = sjh.job_id)
  AND ((@start_run_date       IS NULL) OR (sjh.last_run_date >= @start_run_date))
  AND ((@end_run_date         IS NULL) OR (sjh.last_run_date <= @end_run_date))
  AND ((@start_run_time       IS NULL) OR (sjh.last_run_time >= @start_run_time))
  AND ((@minimum_run_duration IS NULL) OR (sjh.last_run_duration >= @minimum_run_duration))
  AND ((@run_status           IS NULL) OR (@run_status = sjh.last_run_outcome))
  AND ((@server               IS NULL) OR (sts.server_name = @server))
ELSE
  SELECT sjh.instance_id, -- This is included just for ordering purposes
     sj.job_id,
     job_name = sj.name,
     sjh.step_id,
     sjh.step_name,
     sjh.sql_message_id,
     sjh.sql_severity,
     sjh.message,
     sjh.run_status,
     sjh.run_date,
     sjh.run_time,
     sjh.run_duration,
     operator_emailed = so1.name,
     operator_netsent = so2.name,
     operator_paged = so3.name,
     sjh.retries_attempted,
     sjh.server
  FROM msdb.dbo.sysjobhistory                sjh
     LEFT OUTER JOIN msdb.dbo.sysoperators so1  ON (sjh.operator_id_emailed = so1.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so2  ON (sjh.operator_id_netsent = so2.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so3  ON (sjh.operator_id_paged = so3.id),
     msdb.dbo.sysjobs_view sj
  WHERE (sj.job_id = sjh.job_id)
  AND ((@job_id               IS NULL) OR (@job_id = sjh.job_id))
  AND ((@step_id              IS NULL) OR (@step_id = sjh.step_id))
  AND ((@sql_message_id       IS NULL) OR (@sql_message_id = sjh.sql_message_id))
  AND ((@sql_severity         IS NULL) OR (@sql_severity = sjh.sql_severity))
  AND ((@start_run_date       IS NULL) OR (sjh.run_date >= @start_run_date))
  AND ((@end_run_date         IS NULL) OR (sjh.run_date <= @end_run_date))
  AND ((@start_run_time       IS NULL) OR (sjh.run_time >= @start_run_time))
  AND ((@end_run_time         IS NULL) OR (sjh.run_time <= @end_run_time))
  AND ((@minimum_run_duration IS NULL) OR (sjh.run_duration >= @minimum_run_duration))
  AND ((@run_status           IS NULL) OR (@run_status = sjh.run_status))
  AND ((@minimum_retries      IS NULL) OR (sjh.retries_attempted >= @minimum_retries))
  AND ((@server               IS NULL) OR (sjh.server = @server))
  ORDER BY (sjh.instance_id * @order_by)

GO

/**************************************************************/
/* SP_HELP_JOBHISTORY_SUMMARY                                 */
/**************************************************************/
use [msdb]

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobhistory_summary')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobhistory_summary
go
CREATE PROCEDURE sp_help_jobhistory_summary
               @job_id               UNIQUEIDENTIFIER,
               @job_name             sysname,
               @step_id              INT,
               @sql_message_id       INT,
               @sql_severity         INT,
               @start_run_date       INT,
               @end_run_date         INT,
               @start_run_time       INT,
               @end_run_time         INT,
               @minimum_run_duration INT,
               @run_status           INT,
               @minimum_retries      INT,
               @oldest_first         INT,
               @server               sysname,
               @mode                 VARCHAR(7),
               @order_by             INT,
               @distributed_job_history BIT
AS
-- Summary format: same WHERE clause as for full, just a different SELECT list
IF(@distributed_job_history = 1)
  SELECT sj.job_id,
     job_name = sj.name,
     sjh.last_run_outcome as run_status,
     sjh.last_run_date as run_date,
     sjh.last_run_time as run_time,
     sjh.last_run_duration as run_duration,
     null as operator_emailed,
     null as operator_netsentname,
     null as operator_paged,
     null as retries_attempted,
     sts.server_name as server
  FROM msdb.dbo.sysjobservers                sjh
  JOIN msdb.dbo.systargetservers sts ON (sts.server_id = sjh.server_id)
  JOIN msdb.dbo.sysjobs_view     sj  ON(sj.job_id = sjh.job_id)
  WHERE 
  (@job_id = sjh.job_id)
  AND ((@start_run_date       IS NULL) OR (sjh.last_run_date >= @start_run_date))
  AND ((@end_run_date         IS NULL) OR (sjh.last_run_date <= @end_run_date))
  AND ((@start_run_time       IS NULL) OR (sjh.last_run_time >= @start_run_time))
  AND ((@minimum_run_duration IS NULL) OR (sjh.last_run_duration >= @minimum_run_duration))
  AND ((@run_status           IS NULL) OR (@run_status = sjh.last_run_outcome))
  AND ((@server               IS NULL) OR (sts.server_name = @server))
ELSE
  SELECT sj.job_id,
     job_name = sj.name,
     sjh.run_status,
     sjh.run_date,
     sjh.run_time,
     sjh.run_duration,
     operator_emailed = substring(so1.name, 1, 20),
     operator_netsent = substring(so2.name, 1, 20),
     operator_paged = substring(so3.name, 1, 20),
     sjh.retries_attempted,
     sjh.server
  FROM msdb.dbo.sysjobhistory                sjh
     LEFT OUTER JOIN msdb.dbo.sysoperators so1  ON (sjh.operator_id_emailed = so1.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so2  ON (sjh.operator_id_netsent = so2.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so3  ON (sjh.operator_id_paged = so3.id),
     msdb.dbo.sysjobs_view                 sj
  WHERE (sj.job_id = sjh.job_id)
  AND ((@job_id               IS NULL) OR (@job_id = sjh.job_id))
  AND ((@step_id              IS NULL) OR (@step_id = sjh.step_id))
  AND ((@sql_message_id       IS NULL) OR (@sql_message_id = sjh.sql_message_id))
  AND ((@sql_severity         IS NULL) OR (@sql_severity = sjh.sql_severity))
  AND ((@start_run_date       IS NULL) OR (sjh.run_date >= @start_run_date))
  AND ((@end_run_date         IS NULL) OR (sjh.run_date <= @end_run_date))
  AND ((@start_run_time       IS NULL) OR (sjh.run_time >= @start_run_time))
  AND ((@end_run_time         IS NULL) OR (sjh.run_time <= @end_run_time))
  AND ((@minimum_run_duration IS NULL) OR (sjh.run_duration >= @minimum_run_duration))
  AND ((@run_status           IS NULL) OR (@run_status = sjh.run_status))
  AND ((@minimum_retries      IS NULL) OR (sjh.retries_attempted >= @minimum_retries))
  AND ((@server               IS NULL) OR (sjh.server = @server))
  ORDER BY (sjh.instance_id * @order_by)

GO

/**************************************************************/
/* SP_HELP_JOBHISTORY_SEM                                     */
/**************************************************************/
use [msdb]

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobhistory_sem')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobhistory_sem
go

CREATE PROCEDURE sp_help_jobhistory_sem
               @job_id               UNIQUEIDENTIFIER,
               @job_name             sysname,
               @step_id              INT,
               @sql_message_id       INT,
               @sql_severity         INT,
               @start_run_date       INT,
               @end_run_date         INT,
               @start_run_time       INT,
               @end_run_time         INT,
               @minimum_run_duration INT,
               @run_status           INT,
               @minimum_retries      INT,
               @oldest_first         INT,
               @server               sysname,
               @mode                 VARCHAR(7),
               @order_by             INT,
               @distributed_job_history BIT
AS
-- SQL Enterprise Manager format
IF(@distributed_job_history = 1)
  SELECT sj.job_id,
     null as step_name,
     sjh.last_outcome_message as message,
     sjh.last_run_outcome as run_status,
     sjh.last_run_date as run_date,
     sjh.last_run_time as run_time,
     sjh.last_run_duration as run_duration,
     null as operator_emailed,
     null as operator_netsentname,
     null as operator_paged
  FROM msdb.dbo.sysjobservers                sjh
  JOIN msdb.dbo.systargetservers sts ON (sts.server_id = sjh.server_id)
  JOIN msdb.dbo.sysjobs_view     sj  ON(sj.job_id = sjh.job_id)
  WHERE 
  (@job_id = sjh.job_id)
ELSE
  SELECT sjh.step_id,
     sjh.step_name,
     sjh.message,
     sjh.run_status,
     sjh.run_date,
     sjh.run_time,
     sjh.run_duration,
     operator_emailed = so1.name,
     operator_netsent = so2.name,
     operator_paged = so3.name
  FROM msdb.dbo.sysjobhistory                sjh
     LEFT OUTER JOIN msdb.dbo.sysoperators so1  ON (sjh.operator_id_emailed = so1.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so2  ON (sjh.operator_id_netsent = so2.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so3  ON (sjh.operator_id_paged = so3.id),
     msdb.dbo.sysjobs_view                 sj
  WHERE (sj.job_id = sjh.job_id)
  AND (@job_id = sjh.job_id)
  ORDER BY (sjh.instance_id * @order_by)
GO

/**************************************************************/
/* SP_HELP_JOBHISTORY                                         */
/**************************************************************/
use [msdb]

PRINT ''
PRINT 'Creating procedure sp_help_jobhistory...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobhistory')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobhistory
go
CREATE PROCEDURE [dbo].[sp_help_jobhistory]
  @job_id               UNIQUEIDENTIFIER = NULL,
  @job_name             sysname          = NULL,
  @step_id              INT              = NULL,
  @sql_message_id       INT              = NULL,
  @sql_severity         INT              = NULL,
  @start_run_date       INT              = NULL,     -- YYYYMMDD
  @end_run_date         INT              = NULL,     -- YYYYMMDD
  @start_run_time       INT              = NULL,     -- HHMMSS
  @end_run_time         INT              = NULL,     -- HHMMSS
  @minimum_run_duration INT              = NULL,     -- HHMMSS
  @run_status           INT              = NULL,     -- SQLAGENT_EXEC_X code
  @minimum_retries      INT              = NULL,
  @oldest_first         INT              = 0,        -- Or 1
  @server               sysname          = NULL,
  @mode                 VARCHAR(7)       = 'SUMMARY' -- Or 'FULL' or 'SEM'
AS
BEGIN
  DECLARE @retval   INT
  DECLARE @order_by INT  -- Must be INT since it can be -1

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @server   = LTRIM(RTRIM(@server))
  SELECT @mode     = LTRIM(RTRIM(@mode))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@server = N'')   SELECT @server = NULL

  -- Check job id/name (if supplied)
  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check @start_run_date
  IF (@start_run_date IS NOT NULL)
  BEGIN
    EXECUTE @retval = sp_verify_job_date @start_run_date, '@start_run_date'
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check @end_run_date
  IF (@end_run_date IS NOT NULL)
  BEGIN
    EXECUTE @retval = sp_verify_job_date @end_run_date, '@end_run_date'
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check @start_run_time
  EXECUTE @retval = sp_verify_job_time @start_run_time, '@start_run_time'
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check @end_run_time
  EXECUTE @retval = sp_verify_job_time @end_run_time, '@end_run_time'
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check @run_status
  IF ((@run_status < 0) OR (@run_status > 5))
  BEGIN
    RAISERROR(14198, -1, -1, '@run_status', '0..5')
    RETURN(1) -- Failure
  END

  -- Check mode
  SELECT @mode = UPPER(@mode collate SQL_Latin1_General_CP1_CS_AS)
  IF (@mode NOT IN ('SUMMARY', 'FULL', 'SEM'))
  BEGIN
    RAISERROR(14266, -1, -1, '@mode', 'SUMMARY, FULL, SEM')
    RETURN(1) -- Failure
  END

  SELECT @order_by = -1
  IF (@oldest_first = 1)
    SELECT @order_by = 1

  DECLARE @distributed_job_history BIT 
  SET @distributed_job_history = 0
  
  IF (@job_id IS NOT NULL) AND ( EXISTS (SELECT *
                              FROM msdb.dbo.sysjobs       sj,
                                 msdb.dbo.sysjobservers sjs
                              WHERE (sj.job_id = sjs.job_id)
                                 AND (sj.job_id = @job_id)
                                 AND (sjs.server_id <> 0)))
   SET @distributed_job_history = 1

  -- Return history information filtered by the supplied parameters.
  -- Having actual queries in subprocedures allows better query plans because query optimizer sniffs correct parameters
  IF (@mode = 'FULL')
  BEGIN
  -- NOTE: SQLDMO relies on the 'FULL' format; ** DO NOT CHANGE IT **
      EXECUTE sp_help_jobhistory_full
         @job_id,
         @job_name,
         @step_id,
         @sql_message_id,
         @sql_severity,
         @start_run_date,
         @end_run_date,
         @start_run_time,
         @end_run_time,
         @minimum_run_duration,
         @run_status,
         @minimum_retries,
         @oldest_first,
         @server,
         @mode,
         @order_by,
         @distributed_job_history
  END
  ELSE
  IF (@mode = 'SUMMARY')
  BEGIN
    -- Summary format: same WHERE clause as for full, just a different SELECT list
    EXECUTE sp_help_jobhistory_summary
         @job_id,
         @job_name,
         @step_id,
         @sql_message_id,
         @sql_severity,
         @start_run_date,
         @end_run_date,
         @start_run_time,
         @end_run_time,
         @minimum_run_duration,
         @run_status,
         @minimum_retries,
         @oldest_first,
         @server,
         @mode,
         @order_by,
         @distributed_job_history
  END
  ELSE
  IF (@mode = 'SEM')
  BEGIN
    -- SQL Enterprise Manager format
    EXECUTE sp_help_jobhistory_sem
         @job_id,
         @job_name,
         @step_id,
         @sql_message_id,
         @sql_severity,
         @start_run_date,
         @end_run_date,
         @start_run_time,
         @end_run_time,
         @minimum_run_duration,
         @run_status,
         @minimum_retries,
         @oldest_first,
         @server,
         @mode,
         @order_by,
         @distributed_job_history
  END
  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_ADD_JOBSERVER                                           */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_jobserver...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_jobserver')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_jobserver
go

CREATE PROCEDURE sp_add_jobserver
  @job_id         UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name       sysname          = NULL, -- Must provide either this or job_id
  @server_name    sysname         = NULL, -- if NULL will default to serverproperty('ServerName')
  @automatic_post BIT = 1                  -- Flag for SEM use only
AS
BEGIN
  DECLARE @retval                    INT
  DECLARE @server_id                 INT
  DECLARE @job_type                  VARCHAR(12)
  DECLARE @current_job_category_type VARCHAR(12)
  DECLARE @msx_operator_id           INT
  DECLARE @local_server_name         sysname
  DECLARE @is_sysadmin               INT
  DECLARE @job_owner                 sysname
  DECLARE @owner_sid                 VARBINARY(85)
  DECLARE @owner_name                sysname

  SET NOCOUNT ON

  IF (@server_name IS NULL) OR (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = N'(LOCAL)')
    SELECT @server_name = CONVERT(sysname, SERVERPROPERTY('ServerName'))

  -- Remove any leading/trailing spaces from parameters
  SELECT @server_name = UPPER(LTRIM(RTRIM(@server_name)))

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- First, check if the server is the local server
  SELECT @local_server_name = CONVERT(NVARCHAR,SERVERPROPERTY ('SERVERNAME'))

  IF (@server_name = UPPER(@local_server_name))
    SELECT @server_name = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

  -- For a multi-server job...
  IF (@server_name <> UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))))
  BEGIN
    -- 1) Only sysadmin can add a multi-server job
    IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0) 
    BEGIN
       RAISERROR(14398, -1, -1);
       RETURN(1) -- Failure
    END

    -- 2) Job must be owned by sysadmin
    SELECT @owner_sid = owner_sid, @owner_name = dbo.SQLAGENT_SUSER_SNAME(owner_sid)
    FROM msdb.dbo.sysjobs
    WHERE (job_id = @job_id)

    IF @owner_sid = 0xFFFFFFFF
    BEGIN
      SELECT @is_sysadmin = 1
    END
    ELSE
    BEGIN
      SELECT @is_sysadmin = 0
      EXECUTE msdb.dbo.sp_sqlagent_has_server_access @login_name = @owner_name, @is_sysadmin_member = @is_sysadmin OUTPUT
    END
    
    IF (@is_sysadmin = 0)
    BEGIN
      RAISERROR(14544, -1, -1, @owner_name, N'sysadmin')
      RETURN(1) -- Failure
    END

    -- 3) Check if any of the TSQL steps have a non-null database_user_name
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobsteps
                WHERE (job_id = @job_id)
                  AND (subsystem = N'TSQL')
                  AND (database_user_name IS NOT NULL)))
    BEGIN
      RAISERROR(14542, -1, -1, N'database_user_name')
      RETURN(1) -- Failure
    END

    SELECT @server_id = server_id
    FROM msdb.dbo.systargetservers
    WHERE (UPPER(server_name) = @server_name)
    IF (@server_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@server_name', @server_name)
      RETURN(1) -- Failure
    END
  END
  ELSE
    SELECT @server_id = 0

  -- Check that this job has not already been targeted at this server
  IF (EXISTS (SELECT *
               FROM msdb.dbo.sysjobservers
               WHERE (job_id = @job_id)
                 AND (server_id = @server_id)))
  BEGIN
    RAISERROR(14269, -1, -1, @job_name, @server_name)
    RETURN(1) -- Failure
  END

  -- Prevent the job from being targeted at both the local AND remote servers
  SELECT @job_type = 'UNKNOWN'
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id = 0)))
    SELECT @job_type = 'LOCAL'
  ELSE
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
    SELECT @job_type = 'MULTI-SERVER'

  IF ((@server_id = 0) AND (@job_type = 'MULTI-SERVER'))
  BEGIN
    RAISERROR(14290, -1, -1)
    RETURN(1) -- Failure
  END
  IF ((@server_id <> 0) AND (@job_type = 'LOCAL'))
  BEGIN
    RAISERROR(14291, -1, -1)
    RETURN(1) -- Failure
  END

  -- For a multi-server job, check that any notifications are to the MSXOperator
  IF (@job_type = 'MULTI-SERVER')
  BEGIN
    SELECT @msx_operator_id = id
    FROM msdb.dbo.sysoperators
    WHERE (name = N'MSXOperator')

    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobs
                WHERE (job_id = @job_id)
                  AND (((notify_email_operator_id <> 0)   AND (notify_email_operator_id <> @msx_operator_id)) OR
                       ((notify_page_operator_id <> 0)    AND (notify_page_operator_id <> @msx_operator_id))  OR
                       ((notify_netsend_operator_id <> 0) AND (notify_netsend_operator_id <> @msx_operator_id)))))
    BEGIN
      RAISERROR(14221, -1, -1, 'MSXOperator')
      RETURN(1) -- Failure
    END
  END

  -- Insert the sysjobservers row
  INSERT INTO msdb.dbo.sysjobservers
         (job_id,
          server_id,
          last_run_outcome,
          last_outcome_message,
          last_run_date,
          last_run_time,
          last_run_duration)
  VALUES (@job_id,
          @server_id,
          5,  -- ie. SQLAGENT_EXEC_UNKNOWN (can't use 0 since this is SQLAGENT_EXEC_FAIL)
          NULL,
          0,
          0,
          0)

  -- Re-categorize the job (if necessary)
  SELECT @current_job_category_type = CASE category_type
                                        WHEN 1 THEN 'LOCAL'
                                        WHEN 2 THEN 'MULTI-SERVER'
                                      END
  FROM msdb.dbo.sysjobs_view  sjv,
       msdb.dbo.syscategories sc
  WHERE (sjv.category_id = sc.category_id)
    AND (sjv.job_id = @job_id)

  IF (@server_id = 0) AND (@current_job_category_type = 'MULTI-SERVER')
  BEGIN
    UPDATE msdb.dbo.sysjobs
    SET category_id = 0 -- [Uncategorized (Local)]
    WHERE (job_id = @job_id)
  END
  IF (@server_id <> 0) AND (@current_job_category_type = 'LOCAL')
  BEGIN
    UPDATE msdb.dbo.sysjobs
    SET category_id = 2 -- [Uncategorized (Multi-Server)]
    WHERE (job_id = @job_id)
  END

  -- Instruct the new server to pick up the job
  IF (@automatic_post = 1)
    EXECUTE @retval = sp_post_msx_operation 'INSERT', 'JOB', @job_id, @server_name

  -- If the job is local, make sure that SQLServerAgent caches it
  IF (@server_id = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                        @job_id      = @job_id,
                                        @action_type = N'I'
  END

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_JOBSERVER                                        */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_jobserver...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_jobserver')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_jobserver
go
CREATE PROCEDURE sp_delete_jobserver
  @job_id      UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name    sysname          = NULL, -- Must provide either this or job_id
  @server_name sysname
AS
BEGIN
  DECLARE @retval             INT
  DECLARE @server_id          INT
  DECLARE @local_machine_name sysname

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @server_name = LTRIM(RTRIM(@server_name))

  IF (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = '(LOCAL)')
    SELECT @server_name = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- First, check if the server is the local server
  EXECUTE @retval = master.dbo.xp_getnetname @local_machine_name OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure
  IF (@local_machine_name IS NOT NULL) AND (UPPER(@server_name) = UPPER(@local_machine_name))
    SELECT @server_name = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

  -- Check server name
  IF (UPPER(@server_name) <> UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))))
  BEGIN
    SELECT @server_id = server_id
    FROM msdb.dbo.systargetservers
    WHERE (UPPER(server_name) = @server_name)
    IF (@server_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@server_name', @server_name)
      RETURN(1) -- Failure
    END
  END
  ELSE
    SELECT @server_id = 0

  -- Check that the job is indeed targeted at the server
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysjobservers
                  WHERE (job_id = @job_id)
                    AND (server_id = @server_id)))
  BEGIN
    RAISERROR(14270, -1, -1, @job_name, @server_name)
    RETURN(1) -- Failure
  END

  -- Instruct the deleted server to purge the job
  -- NOTE: We must do this BEFORE we delete the sysjobservers row
  EXECUTE @retval = sp_post_msx_operation 'DELETE', 'JOB', @job_id, @server_name

  -- Delete the sysjobservers row
  DELETE FROM msdb.dbo.sysjobservers
  WHERE (job_id = @job_id)
    AND (server_id = @server_id)

  -- We used to change the category_id to 0 when removing the last job server
  -- from a job. We no longer do this.
--  IF (NOT EXISTS (SELECT *
--                  FROM msdb.dbo.sysjobservers
--                  WHERE (job_id = @job_id)))
--  BEGIN
--    UPDATE msdb.dbo.sysjobs
--    SET category_id = 0 -- [Uncategorized (Local)]
--    WHERE (job_id = @job_id)
--  END

  -- If the job is local, make sure that SQLServerAgent removes it from cache
  IF (@server_id = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                        @job_id      = @job_id,
                                        @action_type = N'D'
  END

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_HELP_JOBSERVER                                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_jobserver...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobserver')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobserver
go
CREATE PROCEDURE sp_help_jobserver
  @job_id                UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name              sysname          = NULL, -- Must provide either this or job_id
  @show_last_run_details TINYINT          = 0     -- Controls if last-run execution information is part of the result set (1 = yes, 0 = no)
AS
BEGIN
  DECLARE @retval INT

  SET NOCOUNT ON

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- The show-last-run-details flag must be either 1 or 0
  IF (@show_last_run_details <> 0)
    SELECT @show_last_run_details = 1

  IF (@show_last_run_details = 1)
  BEGIN
    -- List the servers that @job_name has been targeted at (INCLUDING last-run details)
    SELECT stsv.server_id,
           stsv.server_name,
           stsv.enlist_date,
           stsv.last_poll_date,
           sjs.last_run_date,
           sjs.last_run_time,
           sjs.last_run_duration,
           sjs.last_run_outcome,  -- Same as JOB_OUTCOME_CODE (SQLAGENT_EXEC_x)
           sjs.last_outcome_message
    FROM msdb.dbo.sysjobservers         sjs  LEFT OUTER JOIN
         msdb.dbo.systargetservers_view stsv ON (sjs.server_id = stsv.server_id)
    WHERE (sjs.job_id = @job_id)
  END
  ELSE
  BEGIN
    -- List the servers that @job_name has been targeted at (EXCLUDING last-run details)
    SELECT stsv.server_id,
           stsv.server_name,
           stsv.enlist_date,
           stsv.last_poll_date
    FROM msdb.dbo.sysjobservers         sjs  LEFT OUTER JOIN
         msdb.dbo.systargetservers_view stsv ON (sjs.server_id = stsv.server_id)
    WHERE (sjs.job_id = @job_id)
  END

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_HELP_DOWNLOADLIST                                       */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_downloadlist...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_downloadlist')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_downloadlist
go
CREATE PROCEDURE sp_help_downloadlist
  @job_id          UNIQUEIDENTIFIER = NULL, -- If provided must NOT also provide job_name
  @job_name        sysname          = NULL, -- If provided must NOT also provide job_id
  @operation       VARCHAR(64)      = NULL,
  @object_type     VARCHAR(64)      = NULL, -- Only 'JOB' or 'SERVER' are valid in 7.0
  @object_name     sysname          = NULL,
  @target_server   sysname         = NULL,
  @has_error       TINYINT          = NULL, -- NULL or 1
  @status          TINYINT          = NULL,
  @date_posted     DATETIME         = NULL  -- Include all entries made on OR AFTER this date
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @operation_code INT
  DECLARE @object_type_id TINYINT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @operation     = LTRIM(RTRIM(@operation))
  SELECT @object_type   = LTRIM(RTRIM(@object_type))
  SELECT @object_name   = LTRIM(RTRIM(@object_name))
  SELECT @target_server = UPPER(LTRIM(RTRIM(@target_server)))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@operation     = '') SELECT @operation = NULL
  IF (@object_type   = '') SELECT @object_type = NULL
  IF (@object_name   = N'') SELECT @object_name = NULL
  IF (@target_server = N'') SELECT @target_server = NULL

  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check operation
  IF (@operation IS NOT NULL)
  BEGIN
    SELECT @operation = UPPER(@operation collate SQL_Latin1_General_CP1_CS_AS)
    SELECT @operation_code = CASE @operation
                               WHEN 'INSERT'    THEN 1
                               WHEN 'UPDATE'    THEN 2
                               WHEN 'DELETE'    THEN 3
                               WHEN 'START'     THEN 4
                               WHEN 'STOP'      THEN 5
                               WHEN 'RE-ENLIST' THEN 6
                               WHEN 'DEFECT'    THEN 7
                               WHEN 'SYNC-TIME' THEN 8
                               WHEN 'SET-POLL'  THEN 9
                               ELSE 0
                             END
    IF (@operation_code = 0)
    BEGIN
      RAISERROR(14266, -1, -1, '@operation_code', 'INSERT, UPDATE, DELETE, START, STOP, RE-ENLIST, DEFECT, SYNC-TIME, SET-POLL')
      RETURN(1) -- Failure
    END
  END

  -- Check object type (in 7.0 only 'JOB' and 'SERVER' are valid)
  IF (@object_type IS NOT NULL)
  BEGIN
    SELECT @object_type = UPPER(@object_type collate SQL_Latin1_General_CP1_CS_AS)
    IF ((@object_type <> 'JOB') AND (@object_type <> 'SERVER'))
    BEGIN
      RAISERROR(14266, -1, -1, '@object_type', 'JOB, SERVER')
      RETURN(1) -- Failure
    END
    ELSE
      SELECT @object_type_id = CASE @object_type
                                 WHEN 'JOB'    THEN 1
                                 WHEN 'SERVER' THEN 2
                                 ELSE 0
                               END
  END

  -- If object-type is supplied then object-name must also be supplied
  IF ((@object_type IS NOT NULL) AND (@object_name IS NULL)) OR
     ((@object_type IS NULL)     AND (@object_name IS NOT NULL))
  BEGIN
    RAISERROR(14272, -1, -1)
    RETURN(1) -- Failure
  END

  -- Check target server
  IF (@target_server IS NOT NULL) AND NOT EXISTS (SELECT *
                                                  FROM msdb.dbo.systargetservers
                                                  WHERE UPPER(server_name) = @target_server)
  BEGIN
    RAISERROR(14262, -1, -1, '@target_server', @target_server)
    RETURN(1) -- Failure
  END

  -- Check has-error
  IF (@has_error IS NOT NULL) AND (@has_error <> 1)
  BEGIN
    RAISERROR(14266, -1, -1, '@has_error', '1, NULL')
    RETURN(1) -- Failure
  END

  -- Check status
  IF (@status IS NOT NULL) AND (@status <> 0) AND (@status <> 1)
  BEGIN
    RAISERROR(14266, -1, -1, '@status', '0, 1')
    RETURN(1) -- Failure
  END

  -- Return the result set
  SELECT sdl.instance_id,
         sdl.source_server,
        'operation_code' = CASE sdl.operation_code
                             WHEN 1 THEN '1 (INSERT)'
                             WHEN 2 THEN '2 (UPDATE)'
                             WHEN 3 THEN '3 (DELETE)'
                             WHEN 4 THEN '4 (START)'
                             WHEN 5 THEN '5 (STOP)'
                             WHEN 6 THEN '6 (RE-ENLIST)'
                             WHEN 7 THEN '7 (DEFECT)'
                             WHEN 8 THEN '8 (SYNC-TIME)'
                             WHEN 9 THEN '9 (SET-POLL)'
                             ELSE CONVERT(VARCHAR, sdl.operation_code) + ' ' + FORMATMESSAGE(14205)
                           END,
        'object_name' = ISNULL(sjv.name, CASE
                                           WHEN (sdl.operation_code >= 1) AND (sdl.operation_code <= 5) AND (sdl.object_id = CONVERT(UNIQUEIDENTIFIER, 0x00)) THEN FORMATMESSAGE(14212) -- '(all jobs)'
                                           WHEN (sdl.operation_code  = 3) AND (sdl.object_id <> CONVERT(UNIQUEIDENTIFIER, 0x00)) THEN sdl.deleted_object_name -- Special case handling for a deleted job
                                           WHEN (sdl.operation_code >= 1) AND (sdl.operation_code <= 5) AND (sdl.object_id <> CONVERT(UNIQUEIDENTIFIER, 0x00)) THEN FORMATMESSAGE(14580) -- 'job' (safety belt: should never appear)
                                           WHEN (sdl.operation_code >= 6) AND (sdl.operation_code <= 9) THEN sdl.target_server
                                           ELSE FORMATMESSAGE(14205)
                                         END),
        'object_id' = ISNULL(sjv.job_id, CASE sdl.object_id
                                           WHEN CONVERT(UNIQUEIDENTIFIER, 0x00) THEN CONVERT(UNIQUEIDENTIFIER, 0x00)
                                           ELSE sdl.object_id
                                         END),
         sdl.target_server,
         sdl.error_message,
         sdl.date_posted,
         sdl.date_downloaded,
         sdl.status
  FROM msdb.dbo.sysdownloadlist sdl LEFT OUTER JOIN
       msdb.dbo.sysjobs_view    sjv ON (sdl.object_id = sjv.job_id)
  WHERE ((@operation_code IS NULL) OR (operation_code = @operation_code))
    AND ((@object_type_id IS NULL) OR (object_type = @object_type_id))
    AND ((@job_id         IS NULL) OR (object_id = @job_id))
    AND ((@target_server  IS NULL) OR (target_server = @target_server))
    AND ((@has_error      IS NULL) OR (DATALENGTH(error_message) >= 1 * @has_error))
    AND ((@status         IS NULL) OR (status = @status))
    AND ((@date_posted    IS NULL) OR (date_posted >= @date_posted))
  ORDER BY sdl.instance_id

  RETURN(@@error) -- 0 means success

END
go

/**************************************************************/
/* SP_ENUM_SQLAGENT_SUBSYSTEMS_INTERNAL                       */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_enum_sqlagent_subsystems_internal...'
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_enum_sqlagent_subsystems_internal')
              AND (type = 'P')))
  DROP PROCEDURE sp_enum_sqlagent_subsystems_internal
go
CREATE PROCEDURE sp_enum_sqlagent_subsystems_internal
   @syssubsytems_refresh_needed BIT = 0
AS
BEGIN
  DECLARE @retval INT
  SET NOCOUNT ON
  -- this call will populate subsystems table if necessary
  EXEC @retval = msdb.dbo.sp_verify_subsystems @syssubsytems_refresh_needed
  IF @retval <> 0
     RETURN(@retval)

  -- Check if replication is installed
  DECLARE @replication_installed INT
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\Replication',
                                         N'IsInstalled',
                                         @replication_installed OUTPUT,
                                         N'no_output'
  SELECT @replication_installed = ISNULL(@replication_installed, 0)

  IF @replication_installed = 0
      SELECT  subsystem,
            description = FORMATMESSAGE(description_id),
            subsystem_dll,
            agent_exe,
            start_entry_point,
            event_entry_point,
            stop_entry_point,
            max_worker_threads,
            subsystem_id
      FROM syssubsystems
      WHERE (subsystem NOT IN (N'Distribution', N'LogReader', N'Merge', N'Snapshot', N'QueueReader'))
      ORDER by subsystem
   ELSE
      SELECT  subsystem,
            description = FORMATMESSAGE(description_id),
            subsystem_dll,
            agent_exe,
            start_entry_point,
            event_entry_point,
            stop_entry_point,
            max_worker_threads,
            subsystem_id
      FROM syssubsystems
      ORDER by subsystem_id
      
  RETURN(0)      
END
go

/**************************************************************/
/* SP_ENUM_SQLAGENT_SUBSYSTEMS                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_enum_sqlagent_subsystems...'
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_enum_sqlagent_subsystems')
              AND (type = 'P')))
  DROP PROCEDURE sp_enum_sqlagent_subsystems
go
CREATE PROCEDURE sp_enum_sqlagent_subsystems
AS
BEGIN
  DECLARE @retval         INT
  EXEC @retval = msdb.dbo.sp_enum_sqlagent_subsystems_internal
  RETURN(@retval)
END
go


/**************************************************************/
/* SP_VERIFY_SUBSYSTEM                                        */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_subsystem...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_subsystem')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_subsystem
go
CREATE PROCEDURE sp_verify_subsystem
  @subsystem NVARCHAR(40)
AS
BEGIN
  DECLARE @retval         INT
  SET NOCOUNT ON

  -- this call will populate subsystems table if necessary
  EXEC @retval = msdb.dbo.sp_verify_subsystems
  IF @retval <> 0
     RETURN(@retval)

  -- Remove any leading/trailing spaces from parameters
  SELECT @subsystem = LTRIM(RTRIM(@subsystem))

  -- Make sure Dts is translated into new subsystem's name SSIS
  IF (@subsystem IS NOT NULL AND UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = N'DTS')
  BEGIN
    SET @subsystem = N'SSIS'
  END

  IF EXISTS (SELECT * FROM syssubsystems 
          WHERE  UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) =
                 UPPER(subsystem collate SQL_Latin1_General_CP1_CS_AS))
    RETURN(0) -- Success
  ELSE
  BEGIN
    RAISERROR(14234, -1, -1, '@subsystem', 'sp_enum_sqlagent_subsystems')
    RETURN(1) -- Failure
  END
END
go

/**************************************************************/
/* SP_VERIFY_SCHEDULE                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_schedule...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_schedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_schedule
go
CREATE PROCEDURE sp_verify_schedule
  @schedule_id            INT,
  @name                   sysname,
  @enabled                TINYINT,
  @freq_type              INT,          
  @freq_interval          INT OUTPUT,   -- Output because we may set it to 0 if Frequency Type is one-time or auto-start
  @freq_subday_type       INT OUTPUT,   -- As above
  @freq_subday_interval   INT OUTPUT,   -- As above
  @freq_relative_interval INT OUTPUT,   -- As above
  @freq_recurrence_factor INT OUTPUT,   -- As above
  @active_start_date      INT OUTPUT,
  @active_start_time      INT OUTPUT,
  @active_end_date        INT OUTPUT,
  @active_end_time        INT OUTPUT,
  @owner_sid              VARBINARY(85) --Must be a valid sid. Will fail if this is NULL
AS
BEGIN
  DECLARE @return_code             INT
  DECLARE @res_valid_range         NVARCHAR(100)
  DECLARE @reason                  NVARCHAR(200)
  DECLARE @isAdmin                 INT
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name = LTRIM(RTRIM(@name))

  -- Make sure that NULL input/output parameters - if NULL - are initialized to 0
  SELECT @freq_interval          = ISNULL(@freq_interval, 0)
  SELECT @freq_subday_type       = ISNULL(@freq_subday_type, 0)
  SELECT @freq_subday_interval   = ISNULL(@freq_subday_interval, 0)
  SELECT @freq_relative_interval = ISNULL(@freq_relative_interval, 0)
  SELECT @freq_recurrence_factor = ISNULL(@freq_recurrence_factor, 0)
  SELECT @active_start_date      = ISNULL(@active_start_date, 0)
  SELECT @active_start_time      = ISNULL(@active_start_time, 0)
  SELECT @active_end_date        = ISNULL(@active_end_date, 0)
  SELECT @active_end_time        = ISNULL(@active_end_time, 0)


  -- Check owner 
  IF(ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
    SELECT @isAdmin = 1
  ELSE
    SELECT @isAdmin = 0


  -- If a non-sa is [illegally] trying to create a schedule for another user then raise an error
  IF ((@isAdmin <> 1) AND 
      (ISNULL(IS_MEMBER('SQLAgentOperatorRole'),0) <> 1 AND @schedule_id IS NULL) AND
      (@owner_sid <> SUSER_SID()))
  BEGIN
     RAISERROR(14366, -1, -1)
     RETURN(1) -- Failure
  END


  -- Now just check that the login id is valid (ie. it exists and isn't an NT group)
  IF (@owner_sid <> 0x010100000000000512000000) AND -- NT AUTHORITY\SYSTEM sid
     (@owner_sid <> 0x010100000000000514000000)     -- NT AUTHORITY\NETWORK SERVICE sid
  BEGIN
     IF (@owner_sid IS NULL) OR (EXISTS (SELECT *
                                      FROM master.dbo.syslogins
                                      WHERE (sid = @owner_sid)
                                      AND (isntgroup <> 0)))
     BEGIN
       -- NOTE: In the following message we quote @owner_login_name instead of @owner_sid
       --       since this is the parameter the user passed to the calling SP (ie. either
       --       sp_add_schedule, sp_add_job and sp_update_job)
       SELECT @res_valid_range = FORMATMESSAGE(14203)
       RAISERROR(14234, -1, -1, '@owner_login_name', @res_valid_range)
       RETURN(1) -- Failure
     END
  END
  
  -- Verify name (we disallow schedules called 'ALL' since this has special meaning in sp_delete_jobschedules)
  IF (UPPER(@name collate SQL_Latin1_General_CP1_CS_AS) = N'ALL')
  BEGIN
    RAISERROR(14200, -1, -1, '@name')
    RETURN(1) -- Failure
  END

  -- Verify enabled state
  IF (@enabled <> 0) AND (@enabled <> 1)
  BEGIN
    RAISERROR(14266, -1, -1, '@enabled', '0, 1')
    RETURN(1) -- Failure
  END

  -- Verify frequency type
  IF (@freq_type = 0x2) -- OnDemand is no longer supported
  BEGIN
    RAISERROR(14295, -1, -1)
    RETURN(1) -- Failure
  END
  IF (@freq_type NOT IN (0x1, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80))
  BEGIN
    RAISERROR(14266, -1, -1, '@freq_type', '1, 4, 8, 16, 32, 64, 128')
    RETURN(1) -- Failure
  END

  -- Verify frequency sub-day type
  IF (@freq_subday_type <> 0) AND (@freq_subday_type NOT IN (0x1, 0x2, 0x4, 0x8))
  BEGIN
    RAISERROR(14266, -1, -1, '@freq_subday_type', '0x1, 0x2, 0x4, 0x8')
    RETURN(1) -- Failure
  END

  -- Default active start/end date/times (if not supplied, or supplied as NULLs or 0)
  IF (@active_start_date = 0)
    SELECT @active_start_date = DATEPART(yy, GETDATE()) * 10000 +
                                DATEPART(mm, GETDATE()) * 100 +
                                DATEPART(dd, GETDATE()) -- This is an ISO format: "yyyymmdd"
  IF (@active_end_date = 0)
    SELECT @active_end_date = 99991231  -- December 31st 9999
  IF (@active_start_time = 0)
    SELECT @active_start_time = 000000  -- 12:00:00 am
  IF (@active_end_time = 0)
    SELECT @active_end_time = 235959    -- 11:59:59 pm

  -- Verify active start/end dates
  IF (@active_end_date = 0)
    SELECT @active_end_date = 99991231

  EXECUTE @return_code = sp_verify_job_date @active_end_date, '@active_end_date'
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  EXECUTE @return_code = sp_verify_job_date @active_start_date, '@active_start_date'
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  IF (@active_end_date < @active_start_date)
  BEGIN
    RAISERROR(14288, -1, -1, '@active_end_date', '@active_start_date')
    RETURN(1) -- Failure
  END

  EXECUTE @return_code = sp_verify_job_time @active_end_time, '@active_end_time'
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  EXECUTE @return_code = sp_verify_job_time @active_start_time, '@active_start_time'
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  -- NOTE: It's valid for active_end_time to be less than active_start_time since in this
  --       case we assume that the user wants the active time zone to span midnight.
  --       But it's not valid for active_start_date and active_end_date to be the same for recurring sec/hour/minute schedules

  IF (@active_start_time = @active_end_time and (@freq_subday_type in (0x2, 0x4, 0x8)))
  BEGIN
    SELECT @res_valid_range = FORMATMESSAGE(14202)
    RAISERROR(14266, -1, -1, '@active_end_time', @res_valid_range)
    RETURN(1) -- Failure
  END

  -- NOTE: The rest of this procedure is a SQL implementation of VerifySchedule in job.c

  IF ((@freq_type = 0x1) OR  -- FREQTYPE_ONETIME
      (@freq_type = 0x40) OR -- FREQTYPE_AUTOSTART
      (@freq_type = 0x80))   -- FREQTYPE_ONIDLE
  BEGIN
    -- Set standard defaults for non-required parameters
    SELECT @freq_interval          = 0
    SELECT @freq_subday_type       = 0
    SELECT @freq_subday_interval   = 0
    SELECT @freq_relative_interval = 0
    SELECT @freq_recurrence_factor = 0

    -- Check that a one-time schedule isn't already in the past
    -- Bug 442883: let the creation of the one-time schedule succeed but leave a disabled schedule
    /*
    IF (@freq_type = 0x1) -- FREQTYPE_ONETIME
    BEGIN
      DECLARE @current_date INT
      DECLARE @current_time INT

      -- This is an ISO format: "yyyymmdd"
      SELECT @current_date = CONVERT(INT, CONVERT(VARCHAR, GETDATE(), 112))
      SELECT @current_time = (DATEPART(hh, GETDATE()) * 10000) + (DATEPART(mi, GETDATE()) * 100) + DATEPART(ss, GETDATE())
      IF (@active_start_date < @current_date) OR ((@active_start_date = @current_date) AND (@active_start_time <= @current_time))
      BEGIN
        SELECT @res_valid_range = '> ' + CONVERT(VARCHAR, @current_date) + ' / ' + CONVERT(VARCHAR, @current_time)
        SELECT @reason = '@active_start_date = ' + CONVERT(VARCHAR, @active_start_date) + ' / @active_start_time = ' + CONVERT(VARCHAR, @active_start_time)
        RAISERROR(14266, -1, -1, @reason, @res_valid_range)
        RETURN(1) -- Failure
      END
    END
    */

    GOTO ExitProc
  END

  -- Safety net: If the sub-day-type is 0 (and we know that the schedule is not a one-time or
  --             auto-start) then set it to 1 (FREQSUBTYPE_ONCE).  If the user wanted something
  --             other than ONCE then they should have explicitly set @freq_subday_type.
  IF (@freq_subday_type = 0)
    SELECT @freq_subday_type = 0x1 -- FREQSUBTYPE_ONCE

  IF ((@freq_subday_type <> 0x1) AND  -- FREQSUBTYPE_ONCE   (see qsched.h)
      (@freq_subday_type <> 0x2) AND  -- FREQSUBTYPE_SECOND (see qsched.h)
      (@freq_subday_type <> 0x4) AND  -- FREQSUBTYPE_MINUTE (see qsched.h)
      (@freq_subday_type <> 0x8))     -- FREQSUBTYPE_HOUR   (see qsched.h)
  BEGIN
    SELECT @reason = FORMATMESSAGE(14266, '@freq_subday_type', '0x1, 0x2, 0x4, 0x8')
    RAISERROR(14278, -1, -1, @reason)
    RETURN(1) -- Failure
  END

  IF ((@freq_subday_type <> 0x1) AND (@freq_subday_interval < 1)) -- FREQSUBTYPE_ONCE and less than 1 interval
     OR
     ((@freq_subday_type = 0x2) AND (@freq_subday_interval < 10)) -- FREQSUBTYPE_SECOND and less than 10 seconds (see MIN_SCHEDULE_GRANULARITY in SqlAgent source code)
  BEGIN
    SELECT @reason = FORMATMESSAGE(14200, '@freq_subday_interval')
    RAISERROR(14278, -1, -1, @reason)
    RETURN(1) -- Failure
  END

  IF (@freq_type = 0x4)      -- FREQTYPE_DAILY
  BEGIN
    SELECT @freq_recurrence_factor = 0
    IF (@freq_interval < 1)
    BEGIN
      SELECT @reason = FORMATMESSAGE(14572)
      RAISERROR(14278, -1, -1, @reason)
      RETURN(1) -- Failure
    END
  END

  IF (@freq_type = 0x8)      -- FREQTYPE_WEEKLY
  BEGIN
    IF (@freq_interval < 1)   OR
       (@freq_interval > 127) -- (2^7)-1 [freq_interval is a bitmap (Sun=1..Sat=64)]
    BEGIN
      SELECT @reason = FORMATMESSAGE(14573)
      RAISERROR(14278, -1, -1, @reason)
      RETURN(1) -- Failure
    END

  END

  IF (@freq_type = 0x10)    -- FREQTYPE_MONTHLY
  BEGIN
    IF (@freq_interval < 1)  OR
       (@freq_interval > 31)
    BEGIN
      SELECT @reason = FORMATMESSAGE(14574)
      RAISERROR(14278, -1, -1, @reason)
      RETURN(1) -- Failure
    END

  END

  IF (@freq_type = 0x20)     -- FREQTYPE_MONTHLYRELATIVE
  BEGIN
    IF (@freq_relative_interval <> 0x01) AND  -- RELINT_1ST
       (@freq_relative_interval <> 0x02) AND  -- RELINT_2ND
       (@freq_relative_interval <> 0x04) AND  -- RELINT_3RD
       (@freq_relative_interval <> 0x08) AND  -- RELINT_4TH
       (@freq_relative_interval <> 0x10)      -- RELINT_LAST
    BEGIN
      SELECT @reason = FORMATMESSAGE(14575)
      RAISERROR(14278, -1, -1, @reason)
      RETURN(1) -- Failure
    END
  END

  IF (@freq_type = 0x20)     -- FREQTYPE_MONTHLYRELATIVE
  BEGIN
    IF (@freq_interval <> 01) AND -- RELATIVE_SUN
       (@freq_interval <> 02) AND -- RELATIVE_MON
       (@freq_interval <> 03) AND -- RELATIVE_TUE
       (@freq_interval <> 04) AND -- RELATIVE_WED
       (@freq_interval <> 05) AND -- RELATIVE_THU
       (@freq_interval <> 06) AND -- RELATIVE_FRI
       (@freq_interval <> 07) AND -- RELATIVE_SAT
       (@freq_interval <> 08) AND -- RELATIVE_DAY
       (@freq_interval <> 09) AND -- RELATIVE_WEEKDAY
       (@freq_interval <> 10)     -- RELATIVE_WEEKENDDAY
    BEGIN
      SELECT @reason = FORMATMESSAGE(14576)
      RAISERROR(14278, -1, -1, @reason)
      RETURN(1) -- Failure
    END
  END

  IF ((@freq_type = 0x08)  OR   -- FREQTYPE_WEEKLY
      (@freq_type = 0x10)  OR   -- FREQTYPE_MONTHLY
      (@freq_type = 0x20)) AND  -- FREQTYPE_MONTHLYRELATIVE
      (@freq_recurrence_factor < 1)
  BEGIN
    SELECT @reason = FORMATMESSAGE(14577)
    RAISERROR(14278, -1, -1, @reason)
    RETURN(1) -- Failure
  END

ExitProc:
  -- If we made it this far the schedule is good
  RETURN(0) -- Success

END
go



/**************************************************************/
/* SP_ADD_SCHEDULE                                            */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_add_schedule...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_schedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_schedule
go

CREATE PROCEDURE sp_add_schedule
(
  @schedule_name        sysname,
  @enabled              TINYINT         = 1,            -- Name does not have to be unique
  @freq_type            INT             = 0,
  @freq_interval        INT             = 0,
  @freq_subday_type        INT             = 0,
  @freq_subday_interval    INT             = 0,
  @freq_relative_interval  INT             = 0,
  @freq_recurrence_factor  INT             = 0,
  @active_start_date    INT             = NULL,         -- sp_verify_schedule assigns a default
  @active_end_date         INT             = 99991231,     -- December 31st 9999
  @active_start_time    INT             = 000000,       -- 12:00:00 am
  @active_end_time         INT             = 235959,       -- 11:59:59 pm
  @owner_login_name        sysname         = NULL,
  @schedule_uid             UNIQUEIDENTIFIER= NULL  OUTPUT, -- Used by a TSX machine when inserting a schedule
  @schedule_id              INT             = NULL  OUTPUT,
  @originating_server       sysname        = NULL
)   
AS
BEGIN
  DECLARE @retval           INT
  DECLARE @owner_sid        VARBINARY(85)
  DECLARE @orig_server_id   INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @schedule_name         = LTRIM(RTRIM(@schedule_name)),
         @owner_login_name      = LTRIM(RTRIM(@owner_login_name)),
         @originating_server    = UPPER(LTRIM(RTRIM(@originating_server))),
         @schedule_id           = 0
         
         
   -- If the owner isn't supplied make if the current user
  IF(@owner_login_name IS NULL OR @owner_login_name = '')
  BEGIN
    --Get the current users sid
    SELECT @owner_sid = SUSER_SID()
  END
  ELSE
  BEGIN
    -- Get the sid for @owner_login_name SID
    --force case insensitive comparation for NT users
    SELECT @owner_sid = dbo.SQLAGENT_SUSER_SID(@owner_login_name)
    -- Cannot proceed if @owner_login_name doesn't exist
    IF(@owner_sid IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@owner_login_name', @owner_login_name)
      RETURN(1) -- Failure
    END
  END

  -- Check schedule (frequency and owner) parameters
  EXECUTE @retval = sp_verify_schedule NULL,   -- schedule_id does not exist for the new schedule
                                       @name                    = @schedule_name,
                                       @enabled                 = @enabled,
                                       @freq_type               = @freq_type,
                                       @freq_interval           = @freq_interval            OUTPUT,
                                       @freq_subday_type        = @freq_subday_type         OUTPUT,
                                       @freq_subday_interval    = @freq_subday_interval     OUTPUT,
                                       @freq_relative_interval  = @freq_relative_interval   OUTPUT,
                                       @freq_recurrence_factor  = @freq_recurrence_factor   OUTPUT,
                                       @active_start_date       = @active_start_date        OUTPUT,
                                       @active_start_time       = @active_start_time        OUTPUT,
                                       @active_end_date         = @active_end_date          OUTPUT,
                                       @active_end_time         = @active_end_time          OUTPUT,
                                       @owner_sid               = @owner_sid
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- ignore @originating_server unless SQLAgent is calling
  if((@originating_server IS NULL) OR (@originating_server = N'') OR (PROGRAM_NAME() NOT LIKE N'SQLAgent%'))
  BEGIN
    --Get the local originating_server_id
    SELECT @orig_server_id = originating_server_id 
    FROM msdb.dbo.sysoriginatingservers_view 
    WHERE master_server = 0
  END
  ELSE
  BEGIN
    --Get the MSX originating_server_id. If @originating_server isn't the msx server error out
    SELECT @orig_server_id = originating_server_id 
    FROM msdb.dbo.sysoriginatingservers_view 
    WHERE (originating_server = @originating_server)

    IF (@orig_server_id IS NULL)
    BEGIN
      RAISERROR(14370, -1, -1)
      RETURN(1) -- Failure
    END
  END
  
  IF (@schedule_uid IS NULL)
  BEGIN
    -- Assign the GUID
    SELECT @schedule_uid = NEWID()
  END
  ELSE IF (@schedule_uid <> CONVERT(UNIQUEIDENTIFIER, 0x00))
  BEGIN
    --Try and find the schedule if a @schedule_uid is provided. 
    --A TSX server uses the @schedule_uid to identify a schedule downloaded from the MSX
   SELECT @schedule_id = schedule_id
    FROM msdb.dbo.sysschedules
    WHERE schedule_uid = @schedule_uid

   IF((@schedule_id IS NOT NULL) AND (@schedule_id <> 0))
   BEGIN
      --If found update the fields
      UPDATE msdb.dbo.sysschedules
        SET name              = ISNULL(@schedule_name, name),
            enabled              = ISNULL(@enabled, enabled),
         freq_type            = ISNULL(@freq_type, freq_type),
         freq_interval        = ISNULL(@freq_interval, freq_interval),
         freq_subday_type     = ISNULL(@freq_subday_type, freq_subday_type),
         freq_subday_interval = ISNULL(@freq_subday_interval, freq_subday_interval),
         freq_relative_interval  = ISNULL(@freq_relative_interval, freq_relative_interval),
         freq_recurrence_factor  = ISNULL(@freq_recurrence_factor, freq_recurrence_factor),
         active_start_date    = ISNULL(@active_start_date, active_start_date),
         active_end_date         = ISNULL(@active_end_date, active_end_date),
         active_start_time    = ISNULL(@active_start_time, active_start_time),
         active_end_time         = ISNULL(@active_end_time, active_end_time)
      WHERE schedule_uid = @schedule_uid

      RETURN(@@ERROR)
   END
  END
  
  --MSX not found so add a record to sysschedules
  INSERT INTO msdb.dbo.sysschedules
         (schedule_uid,
          originating_server_id,
          name,
          owner_sid,
          enabled,
          freq_type,
          freq_interval,
          freq_subday_type,
          freq_subday_interval,
          freq_relative_interval,
          freq_recurrence_factor,
          active_start_date,
          active_end_date,
          active_start_time,
          active_end_time)
  select @schedule_uid,
         @orig_server_id, 
         @schedule_name,
         @owner_sid,
         @enabled,
         @freq_type,
         @freq_interval,
         @freq_subday_type,
         @freq_subday_interval,
         @freq_relative_interval,
         @freq_recurrence_factor,
         @active_start_date,
         @active_end_date,
         @active_start_time,
         @active_end_time
          
  SELECT @retval = @@ERROR,
         @schedule_id = @@IDENTITY

  RETURN(@retval) -- 0 means success
END
GO


/**************************************************************/
/* SP_ATTACH_SCHEDULE                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_attach_schedule ...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_attach_schedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_attach_schedule
go

CREATE PROCEDURE sp_attach_schedule
(
  @job_id               UNIQUEIDENTIFIER    = NULL,     -- Must provide either this or job_name
  @job_name             sysname             = NULL,     -- Must provide either this or job_id
  @schedule_id          INT                 = NULL,     -- Must provide either this or schedule_name
  @schedule_name        sysname             = NULL,     -- Must provide either this or schedule_id
  @automatic_post       BIT                 = 1         -- If 1 will post notifications to all tsx servers to that run this job
)   
AS
BEGIN
  DECLARE @retval           INT
  DECLARE @sched_owner_sid  VARBINARY(85)
  DECLARE @job_owner_sid    VARBINARY(85)

  
  SET NOCOUNT ON

  -- Check that we can uniquely identify the job
  EXECUTE @retval = msdb.dbo.sp_verify_job_identifiers '@job_name',
                                                       '@job_id',
                                                        @job_name                   OUTPUT,
                                                        @job_id                     OUTPUT,
                                                        @owner_sid = @job_owner_sid OUTPUT
    IF (@retval <> 0)
        RETURN(1) -- Failure

  -- Check authority (only SQLServerAgent can add a schedule to a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)
        
  -- Check that we can uniquely identify the schedule
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @schedule_name    OUTPUT,
                                                            @schedule_id            = @schedule_id      OUTPUT,
                                                            @owner_sid              = @sched_owner_sid  OUTPUT,
                                                            @orig_server_id         = NULL
  IF (@retval <> 0)
      RETURN(1) -- Failure     

  --Schedules can only be attached to a job if the job and schedule have the 
  --same owner or the caller is a sysadmin
  IF ((@sched_owner_sid <> @job_owner_sid) AND 
     ((@sched_owner_sid <> SUSER_SID()) OR (@job_owner_sid <> SUSER_SID())) AND
      (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
     RAISERROR(14377, -1, -1)
     RETURN(1) -- Failure
  END

  -- If the record doesn't already exist create it
  IF( NOT EXISTS(SELECT *  
                 FROM msdb.dbo.sysjobschedules
                 WHERE (schedule_id = @schedule_id)
                   AND (job_id = @job_id)) )
  BEGIN
    INSERT INTO msdb.dbo.sysjobschedules (schedule_id, job_id)
    SELECT @schedule_id, @job_id
    
    SELECT @retval = @@ERROR

    -- Notify SQLServerAgent of the change, but only if we know the job has been cached
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                    AND (server_id = 0)))
    BEGIN
        EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                            @job_id      = @job_id,
                                            @schedule_id = @schedule_id,
                                            @action_type = N'I'
    END
    
    -- For a multi-server job, remind the user that they need to call sp_post_msx_operation
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                    AND (server_id <> 0)))
      -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines 
      IF (@automatic_post = 1)
        EXECUTE sp_post_msx_operation @operation = 'INSERT', @object_type = 'JOB', @job_id = @job_id
      ELSE
        RAISERROR(14547, 0, 1, N'INSERT', N'sp_post_msx_operation')

    -- update this job's subplan to point to this schedule
    UPDATE msdb.dbo.sysmaintplan_subplans
      SET schedule_id = @schedule_id
    WHERE (job_id = @job_id)
      AND (schedule_id IS NULL)
  END
  
  RETURN(@retval) -- 0 means success
END
GO


/**************************************************************/
/* SP_DETACH_SCHEDULE                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_detach_schedule ...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_detach_schedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_detach_schedule
go

CREATE PROCEDURE sp_detach_schedule
(
  @job_id               UNIQUEIDENTIFIER    = NULL,     -- Must provide either this or job_name
  @job_name             sysname             = NULL,     -- Must provide either this or job_id
  @schedule_id          INT                 = NULL,     -- Must provide either this or schedule_name
  @schedule_name        sysname             = NULL,     -- Must provide either this or schedule_id
  @delete_unused_schedule BIT               = 0,        -- Can optionally delete schedule if it isn't referenced.
                                                        -- The default is to keep schedules 
  @automatic_post       BIT                 = 1         -- If 1 will post notifications to all tsx servers to that run this job
)   
AS
BEGIN
  DECLARE @retval   INT
  DECLARE @sched_owner_sid VARBINARY(85)
  DECLARE @job_owner_sid    VARBINARY(85)
  
  SET NOCOUNT ON

  -- Check that we can uniquely identify the job
  EXECUTE @retval = msdb.dbo.sp_verify_job_identifiers '@job_name',
                                                       '@job_id',
                                                        @job_name OUTPUT,
                                                        @job_id   OUTPUT,
                                                        @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check authority (only SQLServerAgent can add a schedule to a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)
        
  -- Check that we can uniquely identify the schedule
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @schedule_name OUTPUT,
                                                            @schedule_id            = @schedule_id   OUTPUT,
                                                            @owner_sid              = @sched_owner_sid OUTPUT,
                                                            @orig_server_id         = NULL,
                                                            @job_id_filter          = @job_id
  IF (@retval <> 0)
      RETURN(1) -- Failure
 
  -- If the record doesn't exist raise an error
  IF( NOT EXISTS(SELECT *  
                 FROM msdb.dbo.sysjobschedules
                 WHERE (schedule_id = @schedule_id)
                   AND (job_id = @job_id)) )
  BEGIN
    RAISERROR(14374, 0, 1, @schedule_name, @job_name)    
    RETURN(1) -- Failure   
  END
  ELSE
  BEGIN
  
    -- Only sysadmin can detach schedules from jobs they do not own
   IF (((@sched_owner_sid <> SUSER_SID()) OR (@job_owner_sid <> SUSER_SID())) AND
        (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
    BEGIN
      RAISERROR(14391, -1, -1)
      RETURN(1) -- Failure
    END

    DELETE FROM msdb.dbo.sysjobschedules
    WHERE (job_id = @job_id)
      AND (schedule_id = @schedule_id)
    
    SELECT @retval = @@ERROR
    
    --delete the schedule if requested and it isn't referenced
    IF(@retval = 0 AND @delete_unused_schedule = 1)
    BEGIN
        IF(NOT EXISTS(SELECT * 
                      FROM msdb.dbo.sysjobschedules
                      WHERE (schedule_id = @schedule_id)))
        BEGIN
            DELETE FROM msdb.dbo.sysschedules
            WHERE (schedule_id = @schedule_id)
        END
    END

    -- Update the job's version/last-modified information
    UPDATE msdb.dbo.sysjobs
    SET version_number = version_number + 1,
        date_modified = GETDATE()
    WHERE (job_id = @job_id)

    -- Notify SQLServerAgent of the change, but only if we know the job has been cached
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                    AND (server_id = 0)))
    BEGIN
        EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                            @job_id      = @job_id,
                                            @schedule_id = @schedule_id,
                                            @action_type = N'D'
    END

    -- For a multi-server job, remind the user that they need to call sp_post_msx_operation
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                    AND (server_id <> 0)))
      -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines 
      IF (@automatic_post = 1)
        EXECUTE sp_post_msx_operation @operation = 'INSERT', @object_type = 'JOB', @job_id = @job_id
      ELSE
        RAISERROR(14547, 0, 1, N'INSERT', N'sp_post_msx_operation')
    
    -- set this job's subplan to the first schedule in sysjobschedules or NULL if there is none 
    UPDATE msdb.dbo.sysmaintplan_subplans
    SET schedule_id = (    SELECT TOP(1) schedule_id
                        FROM msdb.dbo.sysjobschedules
                        WHERE (job_id = @job_id) )
    WHERE (job_id = @job_id)
      AND (schedule_id = @schedule_id)
  END
  
  RETURN(@retval) -- 0 means success
END
GO

/**************************************************************/
/* SP_UPDATE_REPLICATION_JOB_PARAMETER                        */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_replication_job_parameter...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_update_replication_job_parameter')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_replication_job_parameter
go
CREATE PROCEDURE sp_update_replication_job_parameter
  @job_id        UNIQUEIDENTIFIER,
  @old_freq_type INT,
  @new_freq_type INT
AS
BEGIN
  DECLARE @category_id INT
  DECLARE @pattern     NVARCHAR(50)
  DECLARE @patternidx  INT
  DECLARE @cmdline     NVARCHAR(3200)
  DECLARE @step_id     INT

  SET NOCOUNT ON
  SELECT @pattern = N'%[-/][Cc][Oo][Nn][Tt][Ii][Nn][Uu][Oo][Uu][Ss]%'

  -- Make sure that we are dealing with relevant replication jobs
  SELECT @category_id = category_id
  FROM msdb.dbo.sysjobs
  WHERE (@job_id = job_id)

  -- @category_id = 10 (REPL-Distribution), 13 (REPL-LogReader), 14 (REPL-Merge),
  --  19 (REPL-QueueReader)
  IF @category_id IN (10, 13, 14, 19)
  BEGIN
    -- Adding the -Continuous parameter (non auto-start to auto-start)
    IF ((@old_freq_type <> 0x40) AND (@new_freq_type = 0x40))
    BEGIN
      -- Use a cursor to handle multiple replication agent job steps
      DECLARE step_cursor CURSOR LOCAL FOR
      SELECT command, step_id
      FROM msdb.dbo.sysjobsteps
      WHERE (@job_id = job_id)
        AND (UPPER(subsystem collate SQL_Latin1_General_CP1_CS_AS) IN (N'MERGE', N'LOGREADER', N'DISTRIBUTION', N'QUEUEREADER'))
      OPEN step_cursor
      FETCH step_cursor INTO @cmdline, @step_id

      WHILE (@@FETCH_STATUS <> -1)
      BEGIN
        SELECT @patternidx = PATINDEX(@pattern, @cmdline)
        -- Make sure that the -Continuous parameter has not been specified already
        IF (@patternidx = 0)
        BEGIN
          SELECT @cmdline = @cmdline + N' -Continuous'
          UPDATE msdb.dbo.sysjobsteps
          SET command = @cmdline
          WHERE (@job_id = job_id)
            AND (@step_id = step_id)
        END -- IF (@patternidx = 0)
        FETCH NEXT FROM step_cursor into @cmdline, @step_id
      END -- WHILE (@@FETCH_STATUS <> -1)
      CLOSE step_cursor
      DEALLOCATE step_cursor
    END -- IF ((@old_freq_type...
    -- Removing the -Continuous parameter (auto-start to non auto-start)
    ELSE
    IF ((@old_freq_type = 0x40) AND (@new_freq_type <> 0x40))
    BEGIN
      DECLARE step_cursor CURSOR LOCAL FOR
      SELECT command, step_id
      FROM msdb.dbo.sysjobsteps
      WHERE (@job_id = job_id)
        AND (UPPER(subsystem collate SQL_Latin1_General_CP1_CS_AS) IN (N'MERGE', N'LOGREADER', N'DISTRIBUTION', N'QUEUEREADER'))
      OPEN step_cursor
      FETCH step_cursor INTO @cmdline, @step_id

      WHILE (@@FETCH_STATUS <> -1)
      BEGIN
        SELECT @patternidx = PATINDEX(@pattern, @cmdline)
        IF (@patternidx <> 0)
        BEGIN
          -- Handle multiple instances of -Continuous in the commandline
          WHILE (@patternidx <> 0)
          BEGIN
            SELECT @cmdline = STUFF(@cmdline, @patternidx, 11, N'')
            IF (@patternidx > 1)
            BEGIN
              -- Remove the preceding space if -Continuous does not start at the beginning of the commandline
              SELECT @cmdline = stuff(@cmdline, @patternidx - 1, 1, N'')
            END
            SELECT @patternidx = PATINDEX(@pattern, @cmdline)
          END -- WHILE (@patternidx <> 0)
          UPDATE msdb.dbo.sysjobsteps
          SET command = @cmdline
          WHERE (@job_id = job_id)
            AND (@step_id = step_id)
        END -- IF (@patternidx <> -1)
        FETCH NEXT FROM step_cursor INTO @cmdline, @step_id
      END -- WHILE (@@FETCH_STATUS <> -1)
      CLOSE step_cursor
      DEALLOCATE step_cursor
    END -- ELSE IF ((@old_freq_type = 0x40)...
  END -- IF @category_id IN (10, 13, 14)

  RETURN 0
END
go

/**************************************************************/
/* SP_UPDATE_SCHEDULE                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_update_schedule ...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_schedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_schedule
go

CREATE PROCEDURE sp_update_schedule
(
  @schedule_id              INT             = NULL,     -- Must provide either this or schedule_name
  @name                     sysname         = NULL,     -- Must provide either this or schedule_id
  @new_name                 sysname         = NULL,
  @enabled                  TINYINT         = NULL,
  @freq_type                INT             = NULL,
  @freq_interval            INT             = NULL,
  @freq_subday_type         INT             = NULL,
  @freq_subday_interval     INT             = NULL,
  @freq_relative_interval   INT             = NULL,
  @freq_recurrence_factor   INT             = NULL,
  @active_start_date        INT             = NULL, 
  @active_end_date          INT             = NULL,
  @active_start_time        INT             = NULL,
  @active_end_time          INT             = NULL,
  @owner_login_name         sysname         = NULL,
  @automatic_post           BIT             = 1         -- If 1 will post notifications to all tsx servers to 
                                                        -- update all jobs that use this schedule
)
AS
BEGIN
  DECLARE @retval                   INT
  DECLARE @owner_sid                VARBINARY(85)
  DECLARE @cur_owner_sid            VARBINARY(85)
  DECLARE @x_name                   sysname
  DECLARE @enable_only_used         INT

  DECLARE @x_enabled                TINYINT
  DECLARE @x_freq_type              INT
  DECLARE @x_freq_interval          INT
  DECLARE @x_freq_subday_type       INT
  DECLARE @x_freq_subday_interval   INT
  DECLARE @x_freq_relative_interval INT
  DECLARE @x_freq_recurrence_factor INT
  DECLARE @x_active_start_date      INT
  DECLARE @x_active_end_date        INT
  DECLARE @x_active_start_time      INT
  DECLARE @x_active_end_time        INT
  DECLARE @schedule_uid             UNIQUEIDENTIFIER

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name              = LTRIM(RTRIM(@name))
  SELECT @new_name          = LTRIM(RTRIM(@new_name))
  SELECT @owner_login_name  = LTRIM(RTRIM(@owner_login_name))
  -- Turn [nullable] empty string parameters into NULLs
  IF (@new_name = N'') SELECT @new_name = NULL

   -- If the owner is supplied get the sid and check it
  IF(@owner_login_name IS NOT NULL AND @owner_login_name <> '')
  BEGIN
      -- Get the sid for @owner_login_name SID 
      --force case insensitive comparation for NT users
      SELECT @owner_sid = dbo.SQLAGENT_SUSER_SID(@owner_login_name)
    -- Cannot proceed if @owner_login_name doesn't exist
    IF(@owner_sid IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@owner_login_name', @owner_login_name)
      RETURN(1) -- Failure
    END
  END

  -- Check that we can uniquely identify the schedule. This only returns a schedule that is visible to this user
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @name             OUTPUT,
                                                            @schedule_id            = @schedule_id      OUTPUT,
                                                            @owner_sid              = @cur_owner_sid    OUTPUT,
                                                            @orig_server_id         = NULL
  IF (@retval <> 0)
      RETURN(1) -- Failure   

  -- Is @enable the only parameter used beside jobname and jobid?
  IF ((@enabled                   IS NOT NULL) AND
       (@new_name                 IS NULL) AND
      (@freq_type                 IS NULL) AND
      (@freq_interval             IS NULL) AND
      (@freq_subday_type          IS NULL) AND
      (@freq_subday_interval      IS NULL) AND
      (@freq_relative_interval    IS NULL) AND
      (@freq_recurrence_factor    IS NULL) AND
      (@active_start_date         IS NULL) AND
      (@active_end_date           IS NULL) AND
      (@active_start_time         IS NULL) AND
      (@active_end_time           IS NULL) AND
      (@owner_login_name          IS NULL))
    SELECT @enable_only_used = 1
  ELSE
    SELECT @enable_only_used = 0
      
  -- Non-sysadmins can only update jobs schedules they own. 
  -- Members of SQLAgentReaderRole and SQLAgentOperatorRole can view job schedules, 
  -- but they should not be able to delete them
  IF ((@cur_owner_sid <> SUSER_SID())
       AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'),0) <> 1)
      AND (@enable_only_used <> 1 OR ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) <> 1))
  BEGIN
   RAISERROR(14394, -1, -1)
   RETURN(1) -- Failure
  END
  
  -- If the param @owner_login_name is null or doesn't get resolved by SUSER_SID() set it to the current owner of the schedule
  if(@owner_sid IS NULL)
      SELECT @owner_sid = @cur_owner_sid
       
   -- Set the x_ (existing) variables
  SELECT @x_name                   = name,
         @x_enabled                = enabled,
         @x_freq_type              = freq_type,
         @x_freq_interval          = freq_interval,
         @x_freq_subday_type       = freq_subday_type,
         @x_freq_subday_interval   = freq_subday_interval,
         @x_freq_relative_interval = freq_relative_interval,
         @x_freq_recurrence_factor = freq_recurrence_factor,
         @x_active_start_date      = active_start_date,
         @x_active_end_date        = active_end_date,
         @x_active_start_time      = active_start_time,
         @x_active_end_time        = active_end_time
  FROM msdb.dbo.sysschedules
  WHERE (schedule_id = @schedule_id )     
  
  
    -- Fill out the values for all non-supplied parameters from the existing values
  IF (@new_name               IS NULL) SELECT @new_name               = @x_name
  IF (@enabled                IS NULL) SELECT @enabled                = @x_enabled
  IF (@freq_type              IS NULL) SELECT @freq_type              = @x_freq_type
  IF (@freq_interval          IS NULL) SELECT @freq_interval          = @x_freq_interval
  IF (@freq_subday_type       IS NULL) SELECT @freq_subday_type       = @x_freq_subday_type
  IF (@freq_subday_interval   IS NULL) SELECT @freq_subday_interval   = @x_freq_subday_interval
  IF (@freq_relative_interval IS NULL) SELECT @freq_relative_interval = @x_freq_relative_interval
  IF (@freq_recurrence_factor IS NULL) SELECT @freq_recurrence_factor = @x_freq_recurrence_factor
  IF (@active_start_date      IS NULL) SELECT @active_start_date      = @x_active_start_date
  IF (@active_end_date        IS NULL) SELECT @active_end_date        = @x_active_end_date
  IF (@active_start_time      IS NULL) SELECT @active_start_time      = @x_active_start_time
  IF (@active_end_time        IS NULL) SELECT @active_end_time        = @x_active_end_time
      
  -- Check schedule (frequency and owner) parameters
  EXECUTE @retval = sp_verify_schedule @schedule_id             = @schedule_id,
                                       @name                    = @new_name,
                                       @enabled                 = @enabled,
                                       @freq_type               = @freq_type,
                                       @freq_interval           = @freq_interval            OUTPUT,
                                       @freq_subday_type        = @freq_subday_type         OUTPUT,
                                       @freq_subday_interval    = @freq_subday_interval     OUTPUT,
                                       @freq_relative_interval  = @freq_relative_interval   OUTPUT,
                                       @freq_recurrence_factor  = @freq_recurrence_factor   OUTPUT,
                                       @active_start_date       = @active_start_date        OUTPUT,
                                       @active_start_time       = @active_start_time        OUTPUT,
                                       @active_end_date         = @active_end_date          OUTPUT,
                                       @active_end_time         = @active_end_time          OUTPUT,
                                       @owner_sid               = @owner_sid
  IF (@retval <> 0)
    RETURN(1) -- Failure  

  -- Update the sysschedules table
  UPDATE msdb.dbo.sysschedules
  SET name                   = @new_name,
      owner_sid              = @owner_sid,
      enabled                = @enabled,
      freq_type              = @freq_type,
      freq_interval          = @freq_interval,
      freq_subday_type       = @freq_subday_type,
      freq_subday_interval   = @freq_subday_interval,
      freq_relative_interval = @freq_relative_interval,
      freq_recurrence_factor = @freq_recurrence_factor,
      active_start_date      = @active_start_date,
      active_end_date        = @active_end_date,
      active_start_time      = @active_start_time,
      active_end_time        = @active_end_time,
      date_modified          = GETDATE(),
      version_number         = version_number + 1
  WHERE (schedule_id = @schedule_id)

  SELECT @retval = @@error

 -- update any job that has repl steps
  DECLARE @job_id UNIQUEIDENTIFIER
  DECLARE jobsschedule_cursor CURSOR LOCAL FOR
  SELECT job_id
  FROM msdb.dbo.sysjobschedules
  WHERE (schedule_id = @schedule_id)
  
  IF @x_freq_type <> @freq_type
  BEGIN
    OPEN jobsschedule_cursor
    FETCH NEXT FROM jobsschedule_cursor INTO @job_id

    WHILE (@@FETCH_STATUS = 0)
    BEGIN 
      EXEC  sp_update_replication_job_parameter @job_id = @job_id,
                                                @old_freq_type = @x_freq_type,
                                                @new_freq_type = @freq_type
      FETCH NEXT FROM jobsschedule_cursor INTO @job_id
    END
    CLOSE jobsschedule_cursor
  END
  DEALLOCATE jobsschedule_cursor
  
  -- Notify SQLServerAgent of the change if this is attached to a local job
  IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobschedules AS jsched 
              JOIN msdb.dbo.sysjobservers AS jsvr
                    ON jsched.job_id = jsvr.job_id
                WHERE (jsched.schedule_id = @schedule_id)
                  AND (jsvr.server_id = 0)) )
  BEGIN 
      EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                          @schedule_id = @schedule_id,
                                          @action_type = N'U'              
  END


  -- Instruct the tsx servers to pick up the altered schedule
  IF (@automatic_post = 1)
  BEGIN
      SELECT @schedule_uid = schedule_uid 
      FROM sysschedules 
      WHERE schedule_id = @schedule_id

      IF(NOT @schedule_uid IS NULL)
      BEGIN
          -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines 
          EXECUTE @retval = sp_post_msx_operation @operation = 'INSERT', @object_type = 'SCHEDULE', @schedule_uid = @schedule_uid
      END
  END  

  RETURN(@retval) -- 0 means success
END
GO


/**************************************************************/
/* SP_DELETE_SCHEDULE                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_delete_schedule ...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_schedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_schedule
go

CREATE PROCEDURE sp_delete_schedule
(
  @schedule_id          INT                 = NULL,     -- Must provide either this or schedule_name
  @schedule_name        sysname             = NULL,     -- Must provide either this or schedule_id
  @force_delete         bit                 = 0,
  @automatic_post       BIT                 = 1         -- If 1 will post notifications to all tsx servers to that run this schedule
)   
AS
BEGIN
  DECLARE @retval           INT
  DECLARE @owner_sid        VARBINARY(85)
  DECLARE @job_count        INT
  DECLARE @targ_server_id   INT

  SET NOCOUNT ON
  --Get the owners sid       
  SELECT @job_count = 0

  -- Check that we can uniquely identify the schedule. This only returns a schedule that is visible to this user
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @schedule_name    OUTPUT,
                                                            @schedule_id            = @schedule_id      OUTPUT,
                                                            @owner_sid              = @owner_sid        OUTPUT,
                                                            @orig_server_id         = NULL
  IF (@retval <> 0)
    RETURN(1) -- Failure 

  -- Non-sysadmins can only update jobs schedules they own. 
  -- Members of SQLAgentReaderRole and SQLAgentOperatorRole can view job schedules, 
  -- but they should not be able to delete them
  IF ((@owner_sid <> SUSER_SID()) AND
     (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'),0) <> 1))
  BEGIN
   RAISERROR(14394, -1, -1)
   RETURN(1) -- Failure
  END
    
  --check if there are jobs using this schedule
  SELECT @job_count = count(*)
  FROM sysjobschedules 
  WHERE (schedule_id = @schedule_id)   
  
  -- If we aren't force deleting the schedule make sure no jobs are using it
  IF ((@force_delete = 0) AND (@job_count > 0))
  BEGIN 
    RAISERROR(14372, -1, -1)
    RETURN (1) -- Failure 
  END

  -- Get the one of the terget server_id's. 
  -- Getting MIN(jsvr.server_id) works here because we are only interested in this ID
  -- to determine if the schedule ID is for local jobs or MSX jobs. 
  -- Note, an MSX job can't be run on the local server
  SELECT @targ_server_id = MIN(jsvr.server_id)
  FROM msdb.dbo.sysjobschedules AS jsched 
   JOIN msdb.dbo.sysjobservers AS jsvr
      ON jsched.job_id = jsvr.job_id
  WHERE (jsched.schedule_id = @schedule_id)

  --OK to delete the job - schedule link
  DELETE sysjobschedules 
  WHERE schedule_id = @schedule_id

  --OK to delete the schedule 
  DELETE sysschedules 
  WHERE schedule_id = @schedule_id

  -- @targ_server_id would be null if no jobs use this schedule
  IF (@targ_server_id IS NOT NULL)
  BEGIN
   -- Notify SQLServerAgent of the change but only if it the schedule was used by a local job
   IF (@targ_server_id = 0)
   BEGIN 
      -- Only send a notification if the schedule is force deleted. If it isn't force deleted
      -- a notification would have already been sent while detaching the schedule (sp_detach_schedule)
      IF (@force_delete = 1)
      BEGIN
        EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                   @schedule_id = @schedule_id,
                                   @action_type = N'D'
      END                   
   END
   ELSE
   BEGIN
    -- Instruct the tsx servers to pick up the altered schedule
    IF (@automatic_post = 1)
    BEGIN
      DECLARE @schedule_uid UNIQUEIDENTIFIER
      SELECT @schedule_uid = schedule_uid 
      FROM sysschedules 
      WHERE schedule_id = @schedule_id

      IF(NOT @schedule_uid IS NULL)
      BEGIN
        -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines 
        EXECUTE sp_post_msx_operation @operation = 'INSERT', @object_type = 'SCHEDULE', @schedule_uid = @schedule_uid
      END
    END
    ELSE
      RAISERROR(14547, 0, 1, N'INSERT', N'sp_post_msx_operation')
   END
  END
  
  RETURN(@retval) -- 0 means success
END
GO



/**************************************************************/
/* SP_GET_JOBSTEP_DB_USERNAME                                 */
/*                                                            */
/* NOTE: For NT login names this procedure can take several   */
/*       seconds to return as it hits the PDC/BDC.            */
/*       SQLServerAgent calls this at runtime.                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_get_jobstep_db_username...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_get_jobstep_db_username')
              AND (type = 'P')))
  DROP PROCEDURE sp_get_jobstep_db_username
go
CREATE PROCEDURE sp_get_jobstep_db_username
  @database_name        sysname,
  @login_name           sysname = NULL,
  @username_in_targetdb sysname OUTPUT
AS
BEGIN
  DECLARE @suser_sid_clause NVARCHAR(512)

  -- Check the database name
  IF (DB_ID(@database_name) IS NULL)
  BEGIN
    RAISERROR(14262, 16, 1, 'database', @database_name)
    RETURN(1) -- Failure
  END

  -- Initialize return value
  SELECT @username_in_targetdb = NULL

  -- Make sure login name is never NULL
  IF (@login_name IS NULL)
    SELECT @login_name = SUSER_SNAME()
  IF (@login_name IS NULL)
    RETURN(1) -- Failure

  -- Handle an NT login name
  IF (@login_name LIKE N'%\%')
  BEGIN
    -- Special case...
    IF (UPPER(@login_name collate SQL_Latin1_General_CP1_CS_AS) = N'NT AUTHORITY\SYSTEM')
      SELECT @username_in_targetdb = N'dbo'
    ELSE
      SELECT @username_in_targetdb = @login_name

    RETURN(0) -- Success
  END

  -- Handle a SQL login name
  SELECT @suser_sid_clause = N'SUSER_SID(N' + QUOTENAME(@login_name, '''') + N')'
  IF (SUSER_SID(@login_name) IS NULL)
    RETURN(1) -- Failure

  DECLARE @quoted_database_name NVARCHAR(258)
  SELECT @quoted_database_name = QUOTENAME(@database_name, N'[')

  DECLARE @temp_username TABLE (user_name sysname COLLATE database_default NOT NULL, is_aliased BIT)

  -- 1) Look for the user name of the current login in the target database
  INSERT INTO @temp_username
  EXECUTE (N'SET NOCOUNT ON
             SELECT name, isaliased
             FROM '+ @quoted_database_name + N'.[dbo].[sysusers]
             WHERE (sid = ' + @suser_sid_clause + N')
               AND (hasdbaccess = 1)')

  -- 2) Look for the alias user name of the current login in the target database
  IF (EXISTS (SELECT *
              FROM @temp_username
              WHERE (is_aliased = 1)))
  BEGIN
    DELETE FROM @temp_username
    INSERT INTO @temp_username
    EXECUTE (N'SET NOCOUNT ON
               SELECT name, 0
               FROM '+ @quoted_database_name + N'.[dbo].[sysusers]
               WHERE uid = (SELECT altuid
                            FROM ' + @quoted_database_name + N'.[dbo].[sysusers]
                            WHERE (sid = ' + @suser_sid_clause + N'))
                 AND (hasdbaccess = 1)')
  END

  -- 3) Look for the guest user name in the target database
  IF (NOT EXISTS (SELECT *
                  FROM @temp_username))
    INSERT INTO @temp_username
    EXECUTE (N'SET NOCOUNT ON
               SELECT name, 0
               FROM '+ @quoted_database_name + N'.[dbo].[sysusers]
               WHERE (name = N''guest'')
                 AND (hasdbaccess = 1)')

  SELECT @username_in_targetdb = user_name
  FROM @temp_username

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_VERIFY_JOBSTEP                                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_jobstep...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_jobstep')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_jobstep
go
CREATE PROCEDURE sp_verify_jobstep
  @job_id             UNIQUEIDENTIFIER,
  @step_id            INT,
  @step_name          sysname,
  @subsystem          NVARCHAR(40),
  @command            NVARCHAR(max),
  @server             sysname,
  @on_success_action  TINYINT,
  @on_success_step_id INT,
  @on_fail_action     TINYINT,
  @on_fail_step_id    INT,
  @os_run_priority    INT,
  @database_name      sysname OUTPUT,
  @database_user_name sysname OUTPUT,
  @flags              INT,
  @output_file_name   NVARCHAR(200),
  @proxy_id         INT 
AS
BEGIN
  DECLARE @max_step_id             INT
  DECLARE @retval                  INT
  DECLARE @valid_values            VARCHAR(50)
  DECLARE @database_name_temp      NVARCHAR(258)
  DECLARE @database_user_name_temp NVARCHAR(256)
  DECLARE @temp_command            NVARCHAR(max)
  DECLARE @iPos                    INT
  DECLARE @create_count            INT
  DECLARE @destroy_count           INT
  DECLARE @is_olap_subsystem       BIT
  DECLARE @owner_sid               VARBINARY(85)
  DECLARE @owner_name              sysname
  -- Remove any leading/trailing spaces from parameters
  SELECT @subsystem        = LTRIM(RTRIM(@subsystem))
  SELECT @server           = LTRIM(RTRIM(@server))
  SELECT @output_file_name = LTRIM(RTRIM(@output_file_name))

  -- Get current maximum step id
  SELECT @max_step_id = ISNULL(MAX(step_id), 0)
  FROM msdb.dbo.sysjobsteps
  WHERE (job_id = @job_id)

  -- Check step id
  IF (@step_id < 1) OR (@step_id > @max_step_id + 1)
  BEGIN
    SELECT @valid_values = '1..' + CONVERT(VARCHAR, @max_step_id + 1)
    RAISERROR(14266, -1, -1, '@step_id', @valid_values)
    RETURN(1) -- Failure
  END

  -- Check subsystem
  EXECUTE @retval = sp_verify_subsystem @subsystem
  IF (@retval <> 0)
    RETURN(1) -- Failure
  
  --check if proxy is allowed for this subsystem for current user
  IF (@proxy_id IS NOT NULL)
  BEGIN
    --get the job owner
    SELECT @owner_sid = owner_sid FROM sysjobs
    WHERE  job_id = @job_id
    IF @owner_sid = 0xFFFFFFFF
    BEGIN
      --ask to verify for the special account
      EXECUTE @retval = sp_verify_proxy_permissions 
        @subsystem_name = @subsystem, 
        @proxy_id = @proxy_id, 
        @name = NULL, 
        @raise_error = 1, 
        @allow_disable_proxy = 1, 
        @verify_special_account = 1
      IF (@retval <> 0)
        RETURN(1) -- Failure
    END
    ELSE
    BEGIN
      SELECT @owner_name = SUSER_SNAME(@owner_sid)
      EXECUTE @retval = sp_verify_proxy_permissions 
      @subsystem_name = @subsystem, 
      @proxy_id = @proxy_id, 
      @name = @owner_name, 
      @raise_error = 1, 
      @allow_disable_proxy = 1
      IF (@retval <> 0)
        RETURN(1) -- Failure
    END
  END

  --Only sysadmin can specify @output_file_name 
  IF (@output_file_name IS NOT NULL) AND  (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(14582, -1, -1)
    RETURN(1) -- Failure    
  END

  --Determmine if this is a olap subsystem jobstep
  IF ( UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) in (N'ANALYSISQUERY', N'ANALYSISCOMMAND') )
    SELECT @is_olap_subsystem = 1
  ELSE
    SELECT @is_olap_subsystem = 0

  -- Check step name
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobsteps
              WHERE (job_id = @job_id)
                AND (step_name = @step_name)))
  BEGIN
    RAISERROR(14261, -1, -1, '@step_name', @step_name)
    RETURN(1) -- Failure
  END

  -- Check on-success action/step
  IF (@on_success_action <> 1) AND -- Quit Qith Success
     (@on_success_action <> 2) AND -- Quit Qith Failure
     (@on_success_action <> 3) AND -- Goto Next Step
     (@on_success_action <> 4)     -- Goto Step
  BEGIN
    RAISERROR(14266, -1, -1, '@on_success_action', '1, 2, 3, 4')
    RETURN(1) -- Failure
  END
  IF (@on_success_action = 4) AND
     ((@on_success_step_id < 1) OR (@on_success_step_id = @step_id))
  BEGIN
    -- NOTE: We allow forward references to non-existant steps to prevent the user from
    --       having to make a second update pass to fix up the flow
    RAISERROR(14235, -1, -1, '@on_success_step', @step_id)
    RETURN(1) -- Failure
  END

  -- Check on-fail action/step
  IF (@on_fail_action <> 1) AND -- Quit With Success
     (@on_fail_action <> 2) AND -- Quit With Failure
     (@on_fail_action <> 3) AND -- Goto Next Step
     (@on_fail_action <> 4)     -- Goto Step
  BEGIN
    RAISERROR(14266, -1, -1, '@on_failure_action', '1, 2, 3, 4')
    RETURN(1) -- Failure
  END
  IF (@on_fail_action = 4) AND
     ((@on_fail_step_id < 1) OR (@on_fail_step_id = @step_id))
  BEGIN
    -- NOTE: We allow forward references to non-existant steps to prevent the user from
    --       having to make a second update pass to fix up the flow
    RAISERROR(14235, -1, -1, '@on_failure_step', @step_id)
    RETURN(1) -- Failure
  END

  -- Warn the user about forward references
  IF ((@on_success_action = 4) AND (@on_success_step_id > @max_step_id))
    RAISERROR(14236, 0, 1, '@on_success_step_id')
  IF ((@on_fail_action = 4) AND (@on_fail_step_id > @max_step_id))
    RAISERROR(14236, 0, 1, '@on_fail_step_id')

  --Special case the olap subsystem. It can have any server name. 
  --Default it to the local server if @server is null 
  IF(@is_olap_subsystem = 1)
  BEGIN
    IF(@server IS NULL)
    BEGIN
    --TODO: needs error better message ? >> 'Specify the OLAP server name in the %s parameter'
      --Must specify the olap server name
      RAISERROR(14262, -1, -1, '@server', @server)
      RETURN(1) -- Failure    
    END
  END
  ELSE
  BEGIN
  -- @server may contain port number remove it before looking up.
    declare @srv sysname
    declare @index int
    set @index = PATINDEX('%,%', @server)

    IF @index <> 0
      set @srv = substring(@server, 0, @index)
    ELSE
      set @srv = @server

   -- Check server (this is the replication server, NOT the job-target server)
    IF (@server IS NOT NULL) AND (NOT EXISTS (SELECT *
                                              FROM master.dbo.sysservers
                                              WHERE (UPPER(srvname) = UPPER(@srv))))
    BEGIN
      RAISERROR(14234, -1, -1, '@srv', 'sp_helpserver')
      RETURN(1) -- Failure
    END
  END

  -- Check run priority: must be a valid value to pass to SetThreadPriority:
  -- [-15 = IDLE, -1 = BELOW_NORMAL, 0 = NORMAL, 1 = ABOVE_NORMAL, 15 = TIME_CRITICAL]
  IF (@os_run_priority NOT IN (-15, -1, 0, 1, 15))
  BEGIN
    RAISERROR(14266, -1, -1, '@os_run_priority', '-15, -1, 0, 1, 15')
    RETURN(1) -- Failure
  END

  -- Check flags
  IF ((@flags < 0) OR (@flags > 114))
  BEGIN
    RAISERROR(14266, -1, -1, '@flags', '0..114')
    RETURN(1) -- Failure
  END

  IF (((@flags & 64) <> 0) AND (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) NOT IN ('CMDEXEC')))
  BEGIN
    RAISERROR(14545, -1, -1, '@flags', '@subsystem')
    RETURN(1) -- Failure
  END

  -- Check output file
  IF (@output_file_name IS NOT NULL) AND (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) NOT IN ('TSQL', 'CMDEXEC', 'ANALYSISQUERY', 'ANALYSISCOMMAND', 'SSIS', 'POWERSHELL' ))
  BEGIN
    RAISERROR(14545, -1, -1, '@output_file_name', @subsystem)
    RETURN(1) -- Failure
  END

  -- Check writing to table flags
  IF (@flags IS NOT NULL) AND (((@flags & 8) <> 0) OR ((@flags & 16) <> 0)) AND (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) NOT IN ('TSQL', 'CMDEXEC', 'ANALYSISQUERY', 'ANALYSISCOMMAND', 'SSIS', 'POWERSHELL' ))
  BEGIN
    RAISERROR(14545, -1, -1, '@flags', @subsystem)
    RETURN(1) -- Failure
  END

  -- For CmdExec steps database-name and database-user-name should both be null
  IF (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = N'CMDEXEC')
    SELECT @database_name = NULL,
           @database_user_name = NULL

  -- For non-TSQL steps, database-user-name should be null
  IF (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) <> 'TSQL')
    SELECT @database_user_name = NULL

  -- For a TSQL step, get (and check) the username of the caller in the target database.
  IF (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = 'TSQL')
  BEGIN
    SET NOCOUNT ON

    -- But first check if remote server name has been supplied
    IF (@server IS NOT NULL)
      SELECT @server = NULL

    -- Default database to 'master' if not supplied
    IF (LTRIM(RTRIM(@database_name)) IS NULL)
      SELECT @database_name = N'master'

    -- Check the database (although this is no guarantee that @database_user_name can access it)
    IF (DB_ID(@database_name) IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@database_name', @database_name)
      RETURN(1) -- Failure
    END

    SELECT @database_user_name = LTRIM(RTRIM(@database_user_name))

    -- Only if a SysAdmin is creating the job can the database user name be non-NULL [since only
    -- SysAdmin's can call SETUSER].
    -- NOTE: In this case we don't try to validate the user name (it's too costly to do so)
    --       so if it's bad we'll get a runtime error when the job executes.
    IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
    BEGIN
      -- If this is a multi-server job then @database_user_name must be null
      IF (@database_user_name IS NOT NULL)
      BEGIN
        IF (EXISTS (SELECT *
                    FROM msdb.dbo.sysjobs       sj,
                         msdb.dbo.sysjobservers sjs
                    WHERE (sj.job_id = sjs.job_id)
                      AND (sj.job_id = @job_id)
                      AND (sjs.server_id <> 0)))
        BEGIN
          RAISERROR(14542, -1, -1, N'database_user_name')
          RETURN(1) -- Failure
        END
      END

      -- For a SQL-user, check if it exists
      IF (@database_user_name NOT LIKE N'%\%')
      BEGIN
        SELECT @database_user_name_temp = replace(@database_user_name, N'''', N'''''')
        SELECT @database_name_temp = QUOTENAME(@database_name)

        EXECUTE(N'DECLARE @ret INT
                  SELECT @ret = COUNT(*)
                  FROM ' + @database_name_temp + N'.dbo.sysusers
                  WHERE (name = N''' + @database_user_name_temp + N''')
                  HAVING (COUNT(*) > 0)')
        IF (@@ROWCOUNT = 0)
        BEGIN
          RAISERROR(14262, -1, -1, '@database_user_name', @database_user_name)
          RETURN(1) -- Failure
        END
      END
    END
    ELSE
      SELECT @database_user_name = NULL

  END  -- End of TSQL property verification

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_ADD_JOBSTEP_INTERNAL                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_jobstep_internal...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_jobstep_internal')
              AND (type = 'P')))
  DROP PROCEDURE dbo.sp_add_jobstep_internal
go
CREATE PROCEDURE dbo.sp_add_jobstep_internal
  @job_id                UNIQUEIDENTIFIER = NULL,   -- Must provide either this or job_name
  @job_name              sysname          = NULL,   -- Must provide either this or job_id
  @step_id               INT              = NULL,   -- The proc assigns a default
  @step_name             sysname,
  @subsystem             NVARCHAR(40)     = N'TSQL',
  @command               NVARCHAR(max)    = NULL,
  @additional_parameters NVARCHAR(max)    = NULL,
  @cmdexec_success_code  INT              = 0,
  @on_success_action     TINYINT          = 1,      -- 1 = Quit With Success, 2 = Quit With Failure, 3 = Goto Next Step, 4 = Goto Step
  @on_success_step_id    INT              = 0,
  @on_fail_action        TINYINT          = 2,      -- 1 = Quit With Success, 2 = Quit With Failure, 3 = Goto Next Step, 4 = Goto Step
  @on_fail_step_id       INT              = 0,
  @server                sysname          = NULL,
  @database_name         sysname          = NULL,
  @database_user_name    sysname          = NULL,
  @retry_attempts        INT              = 0,      -- No retries
  @retry_interval        INT              = 0,      -- 0 minute interval
  @os_run_priority       INT              = 0,      -- -15 = Idle, -1 = Below Normal, 0 = Normal, 1 = Above Normal, 15 = Time Critical)
  @output_file_name      NVARCHAR(200)    = NULL,
  @flags                 INT              = 0,       --  0 = Normal, 
                                                     --  1 = Encrypted command (read only), 
                                                     --  2 = Append output files (if any), 
                                                     --  4 = Write TSQL step output to step history
                                                     --  8 = Write log to table (overwrite existing history)
                                                     -- 16 = Write log to table (append to existing history)
                                                     -- 32 = Write all output to job history
                                                     -- 64 = Create a Windows event to use as a signal for the Cmd jobstep to abort
  @proxy_id               int               = NULL,
  @proxy_name              sysname           = NULL,
  -- mutual exclusive; must specify only one of above 2 parameters to 
  -- identify the proxy. 
  @step_uid UNIQUEIDENTIFIER              = NULL OUTPUT
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @max_step_id    INT
  DECLARE @job_owner_sid  VARBINARY(85)
  DECLARE @subsystem_id   INT
  DECLARE @auto_proxy_name sysname
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @step_name          = LTRIM(RTRIM(@step_name))
  SELECT @subsystem          = LTRIM(RTRIM(@subsystem))
  SELECT @server             = LTRIM(RTRIM(@server))
  SELECT @database_name      = LTRIM(RTRIM(@database_name))
  SELECT @database_user_name = LTRIM(RTRIM(@database_user_name))
  SELECT @output_file_name   = LTRIM(RTRIM(@output_file_name))
  SELECT @proxy_name         = LTRIM(RTRIM(@proxy_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@server             = N'') SELECT @server             = NULL
  IF (@database_name      = N'') SELECT @database_name      = NULL
  IF (@database_user_name = N'') SELECT @database_user_name = NULL
  IF (@output_file_name   = N'') SELECT @output_file_name   = NULL
  IF (@proxy_name         = N'') SELECT @proxy_name         = NULL

  -- Check authority (only SQLServerAgent can add a step to a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                               @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) AND
      (SUSER_SID() <> @job_owner_sid))
  BEGIN
     RAISERROR(14525, -1, -1)
     RETURN(1) -- Failure
  END
  

  -- check proxy identifiers only if a proxy has been provided
  IF (@proxy_id IS NOT NULL) or (@proxy_name IS NOT NULL)
  BEGIN
    EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                  '@proxy_id',
                                                   @proxy_name OUTPUT,
                                                   @proxy_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
   END

  -- Default step id (if not supplied)
  IF (@step_id IS NULL)
  BEGIN
    SELECT @step_id = ISNULL(MAX(step_id), 0) + 1
    FROM msdb.dbo.sysjobsteps
    WHERE (job_id = @job_id)
  END

  -- Check parameters
  EXECUTE @retval = sp_verify_jobstep @job_id,
                                      @step_id,
                                      @step_name,
                                      @subsystem,
                                      @command,
                                      @server,
                                      @on_success_action,
                                      @on_success_step_id,
                                      @on_fail_action,
                                      @on_fail_step_id,
                                      @os_run_priority,
                                      @database_name      OUTPUT,
                                      @database_user_name OUTPUT,
                                      @flags,
                                      @output_file_name,
                                               @proxy_id

  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Get current maximum step id
  SELECT @max_step_id = ISNULL(MAX(step_id), 0)
  FROM msdb.dbo.sysjobsteps
  WHERE (job_id = @job_id)

  DECLARE @TranCounter INT;
  SET @TranCounter = @@TRANCOUNT;
  IF @TranCounter = 0
  BEGIN
      -- start our own transaction if there is no outer transaction
      BEGIN TRANSACTION;
  END
  
  -- Modify database.
  BEGIN TRY
    -- Update the job's version/last-modified information
    UPDATE msdb.dbo.sysjobs
    SET version_number = version_number + 1,
        date_modified = GETDATE()
    WHERE (job_id = @job_id)

    -- Adjust step id's (unless the new step is being inserted at the 'end')
    -- NOTE: We MUST do this before inserting the step.
    IF (@step_id <= @max_step_id)
    BEGIN
      UPDATE msdb.dbo.sysjobsteps
      SET step_id = step_id + 1
      WHERE (step_id >= @step_id)
        AND (job_id = @job_id)

      -- Clean up OnSuccess/OnFail references
      UPDATE msdb.dbo.sysjobsteps
      SET on_success_step_id = on_success_step_id + 1
      WHERE (on_success_step_id >= @step_id)
        AND (job_id = @job_id)

      UPDATE msdb.dbo.sysjobsteps
      SET on_fail_step_id = on_fail_step_id + 1
      WHERE (on_fail_step_id >= @step_id)
        AND (job_id = @job_id)

      UPDATE msdb.dbo.sysjobsteps
      SET on_success_step_id = 0,
          on_success_action = 1  -- Quit With Success
      WHERE (on_success_step_id = @step_id)
        AND (job_id = @job_id)

      UPDATE msdb.dbo.sysjobsteps
      SET on_fail_step_id = 0,
          on_fail_action = 2     -- Quit With Failure
      WHERE (on_fail_step_id = @step_id)
        AND (job_id = @job_id)
    END

    SELECT @step_uid = NEWID()

    -- Insert the step
    INSERT INTO msdb.dbo.sysjobsteps
           (job_id,
            step_id,
            step_name,
            subsystem,
            command,
            flags,
            additional_parameters,
            cmdexec_success_code,
            on_success_action,
            on_success_step_id,
            on_fail_action,
            on_fail_step_id,
            server,
            database_name,
            database_user_name,
            retry_attempts,
            retry_interval,
            os_run_priority,
            output_file_name,
            last_run_outcome,
            last_run_duration,
            last_run_retries,
            last_run_date,
            last_run_time,
            proxy_id,
         step_uid)
    VALUES (@job_id,
            @step_id,
            @step_name,
            @subsystem,
            @command,
            @flags,
            @additional_parameters,
            @cmdexec_success_code,
            @on_success_action,
            @on_success_step_id,
            @on_fail_action,
            @on_fail_step_id,
            @server,
            @database_name,
            @database_user_name,
            @retry_attempts,
            @retry_interval,
            @os_run_priority,
            @output_file_name,
            0,
            0,
            0,
            0,
            0,
         @proxy_id,
         @step_uid)
         
  IF @TranCounter = 0
  BEGIN
      -- start our own transaction if there is no outer transaction
      COMMIT TRANSACTION;
  END

  END TRY
  BEGIN CATCH

      -- Prepare tp echo error information to the caller.
      DECLARE @ErrorMessage NVARCHAR(400)
      DECLARE @ErrorSeverity INT
      DECLARE @ErrorState INT

      SELECT @ErrorMessage = ERROR_MESSAGE()
      SELECT @ErrorSeverity = ERROR_SEVERITY()
      SELECT @ErrorState = ERROR_STATE()
      
      IF @TranCounter = 0
      BEGIN
          -- Transaction started in procedure.
          -- Roll back complete transaction.
          ROLLBACK TRANSACTION;
      END
      RAISERROR (@ErrorMessage, -- Message text.
                  @ErrorSeverity, -- Severity.
                  @ErrorState -- State.
                  )
      RETURN (1)                  
  END CATCH
  
  -- Make sure that SQLServerAgent refreshes the job if the 'Has Steps' property has changed
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.sysjobsteps
       WHERE (job_id = @job_id)) = 1)
  BEGIN
    -- NOTE: We only notify SQLServerAgent if we know the job has been cached
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                  AND (server_id = 0)))
      EXECUTE msdb.dbo.sp_sqlagent_notify @op_type       = N'J',
                                            @job_id      = @job_id,
                                            @action_type = N'U'
  END

  -- For a multi-server job, push changes to the target servers
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
  BEGIN
    EXECUTE msdb.dbo.sp_post_msx_operation 'INSERT', 'JOB', @job_id
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_ADD_JOBSTEP                                             */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_jobstep...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_jobstep')
              AND (type = 'P')))
  DROP PROCEDURE dbo.sp_add_jobstep
go
CREATE PROCEDURE dbo.sp_add_jobstep
  @job_id                UNIQUEIDENTIFIER = NULL,   -- Must provide either this or job_name
  @job_name              sysname          = NULL,   -- Must provide either this or job_id
  @step_id               INT              = NULL,   -- The proc assigns a default
  @step_name             sysname,
  @subsystem             NVARCHAR(40)     = N'TSQL',
  @command               NVARCHAR(max)   = NULL,   
  @additional_parameters NVARCHAR(max)    = NULL,
  @cmdexec_success_code  INT              = 0,
  @on_success_action     TINYINT          = 1,      -- 1 = Quit With Success, 2 = Quit With Failure, 3 = Goto Next Step, 4 = Goto Step
  @on_success_step_id    INT              = 0,
  @on_fail_action        TINYINT          = 2,      -- 1 = Quit With Success, 2 = Quit With Failure, 3 = Goto Next Step, 4 = Goto Step
  @on_fail_step_id       INT              = 0,
  @server                sysname      = NULL,
  @database_name         sysname          = NULL,
  @database_user_name    sysname          = NULL,
  @retry_attempts        INT              = 0,      -- No retries
  @retry_interval        INT              = 0,      -- 0 minute interval
  @os_run_priority       INT              = 0,      -- -15 = Idle, -1 = Below Normal, 0 = Normal, 1 = Above Normal, 15 = Time Critical)
  @output_file_name      NVARCHAR(200)    = NULL,
  @flags                 INT              = 0,       -- 0 = Normal, 
                                                     -- 1 = Encrypted command (read only), 
                                                     -- 2 = Append output files (if any), 
                                                     -- 4 = Write TSQL step output to step history,                                            
                                                     -- 8 = Write log to table (overwrite existing history), 
                                                     -- 16 = Write log to table (append to existing history)
                                                     -- 32 = Write all output to job history
                                                     -- 64 = Create a Windows event to use as a signal for the Cmd jobstep to abort
  @proxy_id                 INT                = NULL,
  @proxy_name               sysname          = NULL,
  -- mutual exclusive; must specify only one of above 2 parameters to 
  -- identify the proxy. 
  @step_uid UNIQUEIDENTIFIER = NULL OUTPUT
AS
BEGIN
  DECLARE @retval      INT

  SET NOCOUNT ON
  -- Only sysadmin's or db_owner's of msdb can add replication job steps directly
  IF (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) IN
                        (N'DISTRIBUTION',
                         N'SNAPSHOT',
                         N'LOGREADER',
                         N'MERGE',
                         N'QUEUEREADER'))
  BEGIN
    IF NOT ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) OR
            (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1) OR
            (UPPER(USER_NAME() collate SQL_Latin1_General_CP1_CS_AS) = N'DBO'))
    BEGIN
      RAISERROR(14260, -1, -1)
      RETURN(1) -- Failure
    END
  END

  --Only sysadmin can specify @database_user_name
  IF (@database_user_name IS NOT NULL) AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(14583, -1, -1)
    RETURN(1) -- Failure    
  END

  -- Make sure Dts is translated into new subsystem's name SSIS
  IF UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = N'DTS'
  BEGIN
    SET @subsystem = N'SSIS'
  END

  EXECUTE @retval = dbo.sp_add_jobstep_internal @job_id = @job_id,
                                                @job_name = @job_name,
                                                @step_id = @step_id,
                                                @step_name = @step_name,
                                                @subsystem = @subsystem,
                                                @command = @command,
                                                @additional_parameters = @additional_parameters,
                                                @cmdexec_success_code = @cmdexec_success_code,
                                                @on_success_action = @on_success_action,
                                                @on_success_step_id = @on_success_step_id,
                                                @on_fail_action = @on_fail_action,
                                                @on_fail_step_id = @on_fail_step_id,
                                                @server = @server,
                                                @database_name = @database_name,
                                                @database_user_name = @database_user_name,
                                                @retry_attempts = @retry_attempts,
                                                @retry_interval = @retry_interval,
                                                @os_run_priority = @os_run_priority,
                                                @output_file_name = @output_file_name,
                                                @flags = @flags,
                                                            @proxy_id = @proxy_id,
                                                @proxy_name = @proxy_name,
                                                            @step_uid = @step_uid OUTPUT


  RETURN(@retval)
END
GO

/**************************************************************/
/* SP_UPDATE_JOBSTEP                                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_jobstep...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_jobstep')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_jobstep
go
CREATE PROCEDURE sp_update_jobstep
  @job_id                 UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name               sysname          = NULL, -- Not updatable (provided for identification purposes only)
  @step_id                INT,                     -- Not updatable (provided for identification purposes only)
  @step_name              sysname          = NULL,
  @subsystem              NVARCHAR(40)     = NULL,
  @command                NVARCHAR(max)    = NULL,
  @additional_parameters  NVARCHAR(max)    = NULL,
  @cmdexec_success_code   INT              = NULL,
  @on_success_action      TINYINT          = NULL,
  @on_success_step_id     INT              = NULL,
  @on_fail_action         TINYINT          = NULL,
  @on_fail_step_id        INT              = NULL,
  @server                 sysname          = NULL,
  @database_name          sysname          = NULL,
  @database_user_name     sysname          = NULL,
  @retry_attempts         INT              = NULL,
  @retry_interval         INT              = NULL,
  @os_run_priority        INT              = NULL,
  @output_file_name       NVARCHAR(200)    = NULL,
  @flags                  INT              = NULL,
  @proxy_id            int          = NULL,
  @proxy_name          sysname         = NULL
  -- mutual exclusive; must specify only one of above 2 parameters to 
  -- identify the proxy. 
AS
BEGIN
  DECLARE @retval                 INT
  DECLARE @os_run_priority_code   INT
  DECLARE @step_id_as_char        VARCHAR(10)
  DECLARE @new_step_name          sysname
  DECLARE @x_step_name            sysname
  DECLARE @x_subsystem            NVARCHAR(40)
  DECLARE @x_command              NVARCHAR(max)
  DECLARE @x_flags                INT
  DECLARE @x_cmdexec_success_code INT
  DECLARE @x_on_success_action    TINYINT
  DECLARE @x_on_success_step_id   INT
  DECLARE @x_on_fail_action       TINYINT
  DECLARE @x_on_fail_step_id      INT
  DECLARE @x_server               sysname
  DECLARE @x_database_name        sysname
  DECLARE @x_database_user_name   sysname
  DECLARE @x_retry_attempts       INT
  DECLARE @x_retry_interval       INT
  DECLARE @x_os_run_priority      INT
  DECLARE @x_output_file_name     NVARCHAR(200)
  DECLARE @x_proxy_id             INT         
  DECLARE @x_last_run_outcome     TINYINT      -- Not updatable (but may be in future)
  DECLARE @x_last_run_duration    INT          -- Not updatable (but may be in future)
  DECLARE @x_last_run_retries     INT          -- Not updatable (but may be in future)
  DECLARE @x_last_run_date        INT          -- Not updatable (but may be in future)
  DECLARE @x_last_run_time        INT          -- Not updatable (but may be in future)

  DECLARE @new_proxy_id           INT
  DECLARE @subsystem_id           INT
  DECLARE @auto_proxy_name        sysname
  DECLARE @job_owner_sid        VARBINARY(85)
  
  SET NOCOUNT ON

  SELECT @new_proxy_id = NULL

  -- Remove any leading/trailing spaces from parameters
  SELECT @step_name          = LTRIM(RTRIM(@step_name))
  SELECT @subsystem          = LTRIM(RTRIM(@subsystem))
  SELECT @command            = LTRIM(RTRIM(@command))
  SELECT @server             = LTRIM(RTRIM(@server))
  SELECT @database_name      = LTRIM(RTRIM(@database_name))
  SELECT @database_user_name = LTRIM(RTRIM(@database_user_name))
  SELECT @output_file_name   = LTRIM(RTRIM(@output_file_name))
  SELECT @proxy_name         = LTRIM(RTRIM(@proxy_name))

  -- Make sure Dts is translated into new subsystem's name SSIS
  IF (@subsystem IS NOT NULL AND UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = N'DTS')
  BEGIN
    SET @subsystem = N'SSIS'
  END

  -- Only sysadmin's or db_owner's of msdb can directly change
  -- an existing job step to use one of the replication
  -- subsystems
  IF (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) IN
                        (N'DISTRIBUTION',
                         N'SNAPSHOT',
                         N'LOGREADER',
                         N'MERGE',
                         N'QUEUEREADER'))
  BEGIN
    IF NOT ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) OR
            (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1) OR
            (UPPER(USER_NAME() collate SQL_Latin1_General_CP1_CS_AS) = N'DBO'))
    BEGIN
      RAISERROR(14260, -1, -1)
      RETURN(1) -- Failure
    END
  END

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                               @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check permissions beyond what's checked by the sysjobs_view
  -- SQLAgentReader and SQLAgentOperator roles that can see all jobs
  -- cannot modify jobs they do not own
  IF (@job_owner_sid <> SUSER_SID()                   -- does not own the job
     AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))   -- is not sysadmin
  BEGIN
   RAISERROR(14525, -1, -1);
   RETURN(1) -- Failure
  END

  -- Check that the step exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysjobsteps
                  WHERE (job_id = @job_id)
                    AND (step_id = @step_id)))
  BEGIN
    SELECT @step_id_as_char = CONVERT(VARCHAR(10), @step_id)
    RAISERROR(14262, -1, -1, '@step_id', @step_id_as_char)
    RETURN(1) -- Failure
  END

  -- check proxy identifiers only if a proxy has been provided
  -- @proxy_name = N'' is a special case to allow change of an existing proxy with NULL
  IF (@proxy_id IS NOT NULL) OR (@proxy_name IS NOT NULL AND @proxy_name <> N'') 
  BEGIN
    EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                  '@proxy_id',
                                                   @proxy_name OUTPUT,
                                                   @proxy_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

     SELECT @new_proxy_id  = @proxy_id

  END

  -- Check authority (only SQLServerAgent can modify a step of a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Set the x_ (existing) variables
  SELECT @x_step_name            = step_name,
         @x_subsystem            = subsystem,
         @x_command              = command,
         @x_flags                = flags,
         @x_cmdexec_success_code = cmdexec_success_code,
         @x_on_success_action    = on_success_action,
         @x_on_success_step_id   = on_success_step_id,
         @x_on_fail_action       = on_fail_action,
         @x_on_fail_step_id      = on_fail_step_id,
         @x_server               = server,
         @x_database_name        = database_name,
         @x_database_user_name   = database_user_name,
         @x_retry_attempts       = retry_attempts,
         @x_retry_interval       = retry_interval,
         @x_os_run_priority      = os_run_priority,
         @x_output_file_name     = output_file_name,
         @x_proxy_id             = proxy_id,
         @x_last_run_outcome     = last_run_outcome,
         @x_last_run_duration    = last_run_duration,
         @x_last_run_retries     = last_run_retries,
         @x_last_run_date        = last_run_date,
         @x_last_run_time        = last_run_time
  FROM msdb.dbo.sysjobsteps
  WHERE (job_id = @job_id)
    AND (step_id = @step_id)

  IF ((@step_name IS NOT NULL) AND (@step_name <> @x_step_name))
    SELECT @new_step_name = @step_name

  -- Fill out the values for all non-supplied parameters from the existing values
  IF (@step_name            IS NULL) SELECT @step_name            = @x_step_name
  IF (@subsystem            IS NULL) SELECT @subsystem            = @x_subsystem
  IF (@command              IS NULL) SELECT @command              = @x_command
  IF (@flags                IS NULL) SELECT @flags                = @x_flags
  IF (@cmdexec_success_code IS NULL) SELECT @cmdexec_success_code = @x_cmdexec_success_code
  IF (@on_success_action    IS NULL) SELECT @on_success_action    = @x_on_success_action
  IF (@on_success_step_id   IS NULL) SELECT @on_success_step_id   = @x_on_success_step_id
  IF (@on_fail_action       IS NULL) SELECT @on_fail_action       = @x_on_fail_action
  IF (@on_fail_step_id      IS NULL) SELECT @on_fail_step_id      = @x_on_fail_step_id
  IF (@server               IS NULL) SELECT @server               = @x_server
  IF (@database_name        IS NULL) SELECT @database_name        = @x_database_name
  IF (@database_user_name   IS NULL) SELECT @database_user_name   = @x_database_user_name
  IF (@retry_attempts       IS NULL) SELECT @retry_attempts       = @x_retry_attempts
  IF (@retry_interval       IS NULL) SELECT @retry_interval       = @x_retry_interval
  IF (@os_run_priority      IS NULL) SELECT @os_run_priority      = @x_os_run_priority
  IF (@output_file_name     IS NULL) SELECT @output_file_name     = @x_output_file_name
  IF (@proxy_id             IS NULL) SELECT @new_proxy_id         = @x_proxy_id

  --if an empty proxy_name is supplied the proxy is removed
  IF @proxy_name = N'' SELECT @new_proxy_id = NULL
  -- Turn [nullable] empty string parameters into NULLs
  IF (@command            = N'') SELECT @command            = NULL
  IF (@server             = N'') SELECT @server             = NULL
  IF (@database_name      = N'') SELECT @database_name      = NULL
  IF (@database_user_name = N'') SELECT @database_user_name = NULL
  IF (@output_file_name   = N'') SELECT @output_file_name   = NULL


  -- Check new values
  EXECUTE @retval = sp_verify_jobstep @job_id,
                                      @step_id,
                                      @new_step_name,
                                      @subsystem,
                                      @command,
                                      @server,
                                      @on_success_action,
                                      @on_success_step_id,
                                      @on_fail_action,
                                      @on_fail_step_id,
                                      @os_run_priority,
                                      @database_name      OUTPUT,
                                      @database_user_name OUTPUT,
                                      @flags,
                                      @output_file_name,
                                               @new_proxy_id
  IF (@retval <> 0)
    RETURN(1) -- Failure

  BEGIN TRANSACTION

    -- Update the job's version/last-modified information
    UPDATE msdb.dbo.sysjobs
    SET version_number = version_number + 1,
        date_modified = GETDATE()
    WHERE (job_id = @job_id)

    -- Update the step
    UPDATE msdb.dbo.sysjobsteps
    SET step_name             = @step_name,
        subsystem             = @subsystem,
        command               = @command,
        flags                 = @flags,
        additional_parameters = @additional_parameters,
        cmdexec_success_code  = @cmdexec_success_code,
        on_success_action     = @on_success_action,
        on_success_step_id    = @on_success_step_id,
        on_fail_action        = @on_fail_action,
        on_fail_step_id       = @on_fail_step_id,
        server                = @server,
        database_name         = @database_name,
        database_user_name    = @database_user_name,
        retry_attempts        = @retry_attempts,
        retry_interval        = @retry_interval,
        os_run_priority       = @os_run_priority,
        output_file_name      = @output_file_name,
        last_run_outcome      = @x_last_run_outcome,
        last_run_duration     = @x_last_run_duration,
        last_run_retries      = @x_last_run_retries,
        last_run_date         = @x_last_run_date,
        last_run_time         = @x_last_run_time,
          proxy_id                 = @new_proxy_id
    WHERE (job_id = @job_id)
      AND (step_id = @step_id)


  COMMIT TRANSACTION

  -- For a multi-server job, push changes to the target servers
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
  BEGIN
    EXECUTE msdb.dbo.sp_post_msx_operation 'INSERT', 'JOB', @job_id
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_DELETE_JOBSTEP                                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_jobstep...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_jobstep')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_jobstep
go
CREATE PROCEDURE sp_delete_jobstep
  @job_id   UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name sysname          = NULL, -- Must provide either this or job_id
  @step_id  INT
AS
BEGIN
  DECLARE @retval      INT
  DECLARE @max_step_id INT
  DECLARE @valid_range VARCHAR(50)
  DECLARE @job_owner_sid VARBINARY(85)

  SET NOCOUNT ON

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                               @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check authority (only SQLServerAgent can delete a step of a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = 'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)
    
  -- SQLAgentReader and SQLAgentOperator roles that can see all jobs
  -- cannot modify jobs they do not own
  IF (@job_owner_sid <> SUSER_SID()                   -- does not own the job
     AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))   -- is not sysadmin
  BEGIN
   RAISERROR(14525, -1, -1);
   RETURN(1) -- Failure
  END

  -- Get current maximum step id
  SELECT @max_step_id = ISNULL(MAX(step_id), 0)
  FROM msdb.dbo.sysjobsteps
  WHERE (job_id = @job_id)

  -- Check step id
  IF (@step_id < 0) OR (@step_id > @max_step_id)
  BEGIN
    SELECT @valid_range = FORMATMESSAGE(14201) + CONVERT(VARCHAR, @max_step_id)
    RAISERROR(14266, -1, -1, '@step_id', @valid_range)
    RETURN(1) -- Failure
  END

  BEGIN TRANSACTION

    -- Delete either the specified step or ALL the steps (if step id is 0)
    IF (@step_id = 0)
    BEGIN
      DELETE FROM msdb.dbo.sysjobsteps
      WHERE (job_id = @job_id)
     END
      ELSE
     BEGIN
      DELETE FROM msdb.dbo.sysjobsteps
      WHERE (job_id = @job_id)
        AND (step_id = @step_id)
    END

    IF (@step_id <> 0)
    BEGIN
      -- Adjust step id's
      UPDATE msdb.dbo.sysjobsteps
      SET step_id = step_id - 1
      WHERE (step_id > @step_id)
        AND (job_id = @job_id)

      -- Clean up OnSuccess/OnFail references
      UPDATE msdb.dbo.sysjobsteps
      SET on_success_step_id = on_success_step_id - 1
      WHERE (on_success_step_id > @step_id)
        AND (job_id = @job_id)

      UPDATE msdb.dbo.sysjobsteps
      SET on_fail_step_id = on_fail_step_id - 1
      WHERE (on_fail_step_id > @step_id)
        AND (job_id = @job_id)

      UPDATE msdb.dbo.sysjobsteps
      SET on_success_step_id = 0,
          on_success_action = 1   -- Quit With Success
      WHERE (on_success_step_id = @step_id)
        AND (job_id = @job_id)

      UPDATE msdb.dbo.sysjobsteps
      SET on_fail_step_id = 0,
          on_fail_action = 2   -- Quit With Failure
      WHERE (on_fail_step_id = @step_id)
        AND (job_id = @job_id)
        
    END

    
    -- Update the job's version/last-modified information
    UPDATE msdb.dbo.sysjobs
    SET version_number = version_number + 1,
        date_modified = GETDATE()
    WHERE (job_id = @job_id)

  COMMIT TRANSACTION

  -- Make sure that SQLServerAgent refreshes the job if the 'Has Steps' property has changed
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.sysjobsteps
       WHERE (job_id = @job_id)) = 0)
  BEGIN
    -- NOTE: We only notify SQLServerAgent if we know the job has been cached
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                  AND (server_id = 0)))
      EXECUTE msdb.dbo.sp_sqlagent_notify @op_type       = N'J',
                                            @job_id      = @job_id,
                                            @action_type = N'U'
  END

  -- For a multi-server job, push changes to the target servers
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
  BEGIN
    EXECUTE msdb.dbo.sp_post_msx_operation 'INSERT', 'JOB', @job_id
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_HELP_JOBSTEP                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_jobstep...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobstep')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobstep
go
CREATE PROCEDURE sp_help_jobstep
  @job_id    UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name  sysname          = NULL, -- Must provide either this or job_id
  @step_id   INT              = NULL,
  @step_name sysname          = NULL,
  @suffix    BIT              = 0     -- A flag to control how the result set is formatted
AS
BEGIN
  DECLARE @retval      INT
  DECLARE @max_step_id INT
  DECLARE @valid_range VARCHAR(50)

  SET NOCOUNT ON

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                              'NO_TEST'
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- The suffix flag must be either 0 (ie. no suffix) or 1 (ie. add suffix). 0 is the default.
  IF (@suffix <> 0)
    SELECT @suffix = 1

  -- Check step id (if supplied)
  IF (@step_id IS NOT NULL)
  BEGIN
    -- Get current maximum step id
    SELECT @max_step_id = ISNULL(MAX(step_id), 0)
    FROM msdb.dbo.sysjobsteps
    WHERE job_id = @job_id
   IF @max_step_id = 0
   BEGIN
      RAISERROR(14528, -1, -1, @job_name)
      RETURN(1) -- Failure 
   END
    ELSE IF (@step_id < 1) OR (@step_id > @max_step_id)
    BEGIN
      SELECT @valid_range = '1..' + CONVERT(VARCHAR, @max_step_id)
      RAISERROR(14266, -1, -1, '@step_id', @valid_range)
      RETURN(1) -- Failure
    END
  END

  -- Check step name (if supplied)
  -- NOTE: A supplied step id overrides a supplied step name
  IF ((@step_id IS NULL) AND (@step_name IS NOT NULL))
  BEGIN
    SELECT @step_id = step_id
    FROM msdb.dbo.sysjobsteps
    WHERE (step_name = @step_name)
      AND (job_id = @job_id)

    IF (@step_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@step_name', @step_name)
      RETURN(1) -- Failure
    END
  END

  -- Return the job steps for this job (or just return the specific step)
  IF (@suffix = 0)
  BEGIN
    SELECT step_id,
           step_name,
           subsystem,
           command,
           flags,
           cmdexec_success_code,
           on_success_action,
           on_success_step_id,
           on_fail_action,
           on_fail_step_id,
           server,
           database_name,
           database_user_name,
           retry_attempts,
           retry_interval,
           os_run_priority,
           output_file_name,
           last_run_outcome,
           last_run_duration,
           last_run_retries,
           last_run_date,
           last_run_time,
         proxy_id
    FROM msdb.dbo.sysjobsteps
    WHERE (job_id = @job_id)
      AND ((@step_id IS NULL) OR (step_id = @step_id))
    ORDER BY job_id, step_id
  END
  ELSE
  BEGIN
    SELECT step_id,
           step_name,
           subsystem,
           command,
          'flags' = CONVERT(NVARCHAR, flags) + N' (' +
                    ISNULL(CASE WHEN (flags = 0)     THEN FORMATMESSAGE(14561) END, '') +
                    ISNULL(CASE WHEN (flags & 1) = 1 THEN FORMATMESSAGE(14558) + ISNULL(CASE WHEN (flags > 1) THEN N', ' END, '') END, '') +
                    ISNULL(CASE WHEN (flags & 2) = 2 THEN FORMATMESSAGE(14559) + ISNULL(CASE WHEN (flags > 3) THEN N', ' END, '') END, '') +
                    ISNULL(CASE WHEN (flags & 4) = 4 THEN FORMATMESSAGE(14560) END, '') + N')',
           cmdexec_success_code,
          'on_success_action' = CASE on_success_action
                                  WHEN 1 THEN CONVERT(NVARCHAR, on_success_action) + N' ' + FORMATMESSAGE(14562)
                                  WHEN 2 THEN CONVERT(NVARCHAR, on_success_action) + N' ' + FORMATMESSAGE(14563)
                                  WHEN 3 THEN CONVERT(NVARCHAR, on_success_action) + N' ' + FORMATMESSAGE(14564)
                                  WHEN 4 THEN CONVERT(NVARCHAR, on_success_action) + N' ' + FORMATMESSAGE(14565)
                                  ELSE        CONVERT(NVARCHAR, on_success_action) + N' ' + FORMATMESSAGE(14205)
                                END,
           on_success_step_id,
          'on_fail_action' = CASE on_fail_action
                               WHEN 1 THEN CONVERT(NVARCHAR, on_fail_action) + N' ' + FORMATMESSAGE(14562)
                               WHEN 2 THEN CONVERT(NVARCHAR, on_fail_action) + N' ' + FORMATMESSAGE(14563)
                               WHEN 3 THEN CONVERT(NVARCHAR, on_fail_action) + N' ' + FORMATMESSAGE(14564)
                               WHEN 4 THEN CONVERT(NVARCHAR, on_fail_action) + N' ' + FORMATMESSAGE(14565)
                               ELSE        CONVERT(NVARCHAR, on_fail_action) + N' ' + FORMATMESSAGE(14205)
                             END,
           on_fail_step_id,
           server,
           database_name,
           database_user_name,
           retry_attempts,
           retry_interval,
          'os_run_priority' = CASE os_run_priority
                                WHEN -15 THEN CONVERT(NVARCHAR, os_run_priority) + N' ' + FORMATMESSAGE(14566)
                                WHEN -1  THEN CONVERT(NVARCHAR, os_run_priority) + N' ' + FORMATMESSAGE(14567)
                                WHEN  0  THEN CONVERT(NVARCHAR, os_run_priority) + N' ' + FORMATMESSAGE(14561)
                                WHEN  1  THEN CONVERT(NVARCHAR, os_run_priority) + N' ' + FORMATMESSAGE(14568)
                                WHEN  15 THEN CONVERT(NVARCHAR, os_run_priority) + N' ' + FORMATMESSAGE(14569)
                                ELSE          CONVERT(NVARCHAR, os_run_priority) + N' ' + FORMATMESSAGE(14205)
                              END,
           output_file_name,
           last_run_outcome,
           last_run_duration,
           last_run_retries,
           last_run_date,
           last_run_time,
         proxy_id
    FROM msdb.dbo.sysjobsteps
    WHERE (job_id = @job_id)
      AND ((@step_id IS NULL) OR (step_id = @step_id))
    ORDER BY job_id, step_id
  END

  RETURN(@@error) -- 0 means success

END
go


/**************************************************************/
/* SP_WRITE_SYSJOBSTEP_LOG                                    */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_write_sysjobstep_log...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_write_sysjobstep_log')
              AND (type = 'P')))
  DROP PROCEDURE sp_write_sysjobstep_log
go
CREATE PROCEDURE sp_write_sysjobstep_log
  @job_id    UNIQUEIDENTIFIER, 
  @step_id   INT,
  @log_text  NVARCHAR(MAX),
  @append_to_last INT = 0
AS
BEGIN
  DECLARE @step_uid UNIQUEIDENTIFIER
  DECLARE @log_already_exists int
  SET @log_already_exists = 0

  SET @step_uid = ( SELECT step_uid FROM  msdb.dbo.sysjobsteps
      WHERE (job_id = @job_id)
        AND (step_id = @step_id) )
  

  IF(EXISTS(SELECT * FROM msdb.dbo.sysjobstepslogs
                      WHERE step_uid = @step_uid ))
  BEGIN
     SET @log_already_exists = 1
  END

  --Need create log if "overwrite is selected or log does not exists. 
  IF (@append_to_last = 0) OR (@log_already_exists = 0)
  BEGIN
     -- flag is overwrite
     
     --if overwrite and log exists, delete it
     IF (@append_to_last = 0 AND @log_already_exists = 1)
     BEGIN
        -- remove previous logs entries 
        EXEC sp_delete_jobsteplog @job_id, NULL, @step_id, NULL   
     END
   
     INSERT INTO msdb.dbo.sysjobstepslogs
      (
         log,
         log_size,
         step_uid
      )
      VALUES
      (
         @log_text,
         DATALENGTH(@log_text),
         @step_uid
      )
  END
  ELSE
  BEGIN
     DECLARE @log_id   INT
     --Selecting TOP is just a safety net - there is only one log entry row per step.
     SET @log_id = ( SELECT TOP 1 log_id FROM msdb.dbo.sysjobstepslogs
         WHERE (step_uid = @step_uid)
           ORDER BY log_id DESC ) 

      -- Append @log_text to the existing log record. Note that if this
      -- action would make the value of the log column longer than
      -- nvarchar(max), then the engine will raise error 599.
      UPDATE msdb.dbo.sysjobstepslogs
        SET 
             log .WRITE(@log_text,NULL,0),
             log_size = DATALENGTH(log) + DATALENGTH(@log_text) ,
             date_modified = getdate()
      WHERE log_id = @log_id
  END

  RETURN(@@error) -- 0 means success

END
go

/**************************************************************/
/* SP_HELP_JOBSTEPLOG                                    */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_help_jobsteplog...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobsteplog')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobsteplog
go
CREATE PROCEDURE sp_help_jobsteplog
  @job_id    UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name  sysname          = NULL, -- Must provide either this or job_id
  @step_id   INT              = NULL,
  @step_name sysname          = NULL
AS
BEGIN
  DECLARE @retval      INT
  DECLARE @max_step_id INT
  DECLARE @valid_range VARCHAR(50)

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                              'NO_TEST'
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check step id (if supplied)
  IF (@step_id IS NOT NULL)
  BEGIN
    -- Get current maximum step id
    SELECT @max_step_id = ISNULL(MAX(step_id), 0)
    FROM msdb.dbo.sysjobsteps
    WHERE job_id = @job_id
   IF @max_step_id = 0
   BEGIN
      RAISERROR(14528, -1, -1, @job_name)
      RETURN(1) -- Failure 
   END
    ELSE IF (@step_id < 1) OR (@step_id > @max_step_id)
    BEGIN
      SELECT @valid_range = '1..' + CONVERT(VARCHAR, @max_step_id)
      RAISERROR(14266, -1, -1, '@step_id', @valid_range)
      RETURN(1) -- Failure
    END
  END

  -- Check step name (if supplied)
  -- NOTE: A supplied step id overrides a supplied step name
  IF ((@step_id IS NULL) AND (@step_name IS NOT NULL))
  BEGIN
    SELECT @step_id = step_id
    FROM msdb.dbo.sysjobsteps
    WHERE (step_name = @step_name)
      AND (job_id = @job_id)

    IF (@step_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@step_name', @step_name)
      RETURN(1) -- Failure
    END
  END


    SELECT sjv.job_id,
           @job_name as job_name,
           steps.step_id,
           steps.step_name,
           steps.step_uid,
           logs.date_created,
           logs.date_modified,
           logs.log_size,
           logs.log
    FROM msdb.dbo.sysjobs_view sjv, msdb.dbo.sysjobsteps as steps, msdb.dbo.sysjobstepslogs as logs 
    WHERE (sjv.job_id = @job_id)
      AND (steps.job_id = @job_id)
      AND ((@step_id IS NULL) OR (step_id = @step_id))
      AND (steps.step_uid = logs.step_uid)
   
  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_JOBSTEPLOG                                    */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_delete_jobsteplog...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_jobsteplog')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_jobsteplog
go
CREATE PROCEDURE sp_delete_jobsteplog
  @job_id      UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name    sysname          = NULL, -- Must provide either this or job_id
  @step_id     INT              = NULL,
  @step_name   sysname          = NULL,
  @older_than  datetime         = NULL,
  @larger_than int      = NULL   -- (in megabytes)
AS
BEGIN
  DECLARE @retval      INT
  DECLARE @max_step_id INT
  DECLARE @valid_range VARCHAR(50)

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                              'NO_TEST'
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check step id (if supplied)
  IF (@step_id IS NOT NULL)
  BEGIN
    -- Get current maximum step id
    SELECT @max_step_id = ISNULL(MAX(step_id), 0)
    FROM msdb.dbo.sysjobsteps
    WHERE job_id = @job_id
   IF @max_step_id = 0
   BEGIN
      RAISERROR(14528, -1, -1, @job_name)
      RETURN(1) -- Failure 
   END
    ELSE IF (@step_id < 1) OR (@step_id > @max_step_id)
    BEGIN
      SELECT @valid_range = '1..' + CONVERT(VARCHAR, @max_step_id)
      RAISERROR(14266, -1, -1, '@step_id', @valid_range)
      RETURN(1) -- Failure
    END
  END

  -- Check step name (if supplied)
  -- NOTE: A supplied step id overrides a supplied step name
  IF ((@step_id IS NULL) AND (@step_name IS NOT NULL))
  BEGIN
    SELECT @step_id = step_id
    FROM msdb.dbo.sysjobsteps
    WHERE (step_name = @step_name)
      AND (job_id = @job_id)

    IF (@step_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@step_name', @step_name)
      RETURN(1) -- Failure
    END
  END


   -- Delete either the specified step or ALL the steps (if step id is NULL)
   
   DELETE FROM msdb.dbo.sysjobstepslogs
   WHERE (step_uid IN (SELECT DISTINCT step_uid 
                        FROM   msdb.dbo.sysjobsteps js, msdb.dbo.sysjobs_view jv
                        WHERE (  @job_id = jv.job_id )
                          AND (js.job_id = jv.job_id )
                          AND ((@step_id IS NULL) OR (@step_id = step_id)))) 
    AND ((@older_than IS NULL) OR (date_modified < @older_than))
    AND ((@larger_than IS NULL) OR (log_size > @larger_than))

  RETURN(@retval) -- 0 means success

END
go


/**************************************************************/
/* SP_GET_SCHEDULE_DESCRIPTION                                */
/*                                                            */
/* NOTE: This SP only returns an English description of the   */
/*       schedule due to the piecemeal nature of the          */
/*       description's construction.                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_get_schedule_description...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_get_schedule_description')
              AND (type = 'P')))
  DROP PROCEDURE sp_get_schedule_description
go
CREATE PROCEDURE sp_get_schedule_description
  @freq_type              INT          = NULL,
  @freq_interval          INT          = NULL,
  @freq_subday_type       INT          = NULL,
  @freq_subday_interval   INT          = NULL,
  @freq_relative_interval INT          = NULL,
  @freq_recurrence_factor INT          = NULL,
  @active_start_date      INT          = NULL,
  @active_end_date        INT          = NULL,
  @active_start_time      INT          = NULL,
  @active_end_time        INT          = NULL,
  @schedule_description   NVARCHAR(255) OUTPUT
AS
BEGIN
  DECLARE @loop              INT
  DECLARE @idle_cpu_percent  INT
  DECLARE @idle_cpu_duration INT

  SET NOCOUNT ON

  IF (@freq_type = 0x1) -- OneTime
  BEGIN
    SELECT @schedule_description = N'Once on ' + CONVERT(NVARCHAR, @active_start_date) + N' at ' + CONVERT(NVARCHAR, @active_start_time)
    RETURN
  END

  IF (@freq_type = 0x4) -- Daily
  BEGIN
    SELECT @schedule_description = N'Every day '
  END

  IF (@freq_type = 0x8) -- Weekly
  BEGIN
    SELECT @schedule_description = N'Every ' + CONVERT(NVARCHAR, @freq_recurrence_factor) + N' week(s) on '
    SELECT @loop = 1
    WHILE (@loop <= 7)
    BEGIN
      IF (@freq_interval & POWER(2, @loop - 1) = POWER(2, @loop - 1))
        SELECT @schedule_description = @schedule_description + DATENAME(dw, N'1996120' + CONVERT(NVARCHAR, @loop)) + N', '
      SELECT @loop = @loop + 1
    END
    IF (RIGHT(@schedule_description, 2) = N', ')
      SELECT @schedule_description = SUBSTRING(@schedule_description, 1, (DATALENGTH(@schedule_description) / 2) - 2) + N' '
  END

  IF (@freq_type = 0x10) -- Monthly
  BEGIN
    SELECT @schedule_description = N'Every ' + CONVERT(NVARCHAR, @freq_recurrence_factor) + N' months(s) on day ' + CONVERT(NVARCHAR, @freq_interval) + N' of that month '
  END

  IF (@freq_type = 0x20) -- Monthly Relative
  BEGIN
    SELECT @schedule_description = N'Every ' + CONVERT(NVARCHAR, @freq_recurrence_factor) + N' months(s) on the '
    SELECT @schedule_description = @schedule_description +
      CASE @freq_relative_interval
        WHEN 0x01 THEN N'first '
        WHEN 0x02 THEN N'second '
        WHEN 0x04 THEN N'third '
        WHEN 0x08 THEN N'fourth '
        WHEN 0x10 THEN N'last '
      END +
      CASE
        WHEN (@freq_interval > 00)
         AND (@freq_interval < 08) THEN DATENAME(dw, N'1996120' + CONVERT(NVARCHAR, @freq_interval))
        WHEN (@freq_interval = 08) THEN N'day'
        WHEN (@freq_interval = 09) THEN N'week day'
        WHEN (@freq_interval = 10) THEN N'weekend day'
      END + N' of that month '
  END

  IF (@freq_type = 0x40) -- AutoStart
  BEGIN
    SELECT @schedule_description = FORMATMESSAGE(14579)
    RETURN
  END

  IF (@freq_type = 0x80) -- OnIdle
  BEGIN
    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'IdleCPUPercent',
                                           @idle_cpu_percent OUTPUT,
                                           N'no_output'
    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'IdleCPUDuration',
                                           @idle_cpu_duration OUTPUT,
                                           N'no_output'
    SELECT @schedule_description = FORMATMESSAGE(14578, ISNULL(@idle_cpu_percent, 10), ISNULL(@idle_cpu_duration, 600))
    RETURN
  END

  -- Subday stuff
  SELECT @schedule_description = @schedule_description +
    CASE @freq_subday_type
      WHEN 0x1 THEN N'at ' + CONVERT(NVARCHAR, @active_start_time)
      WHEN 0x2 THEN N'every ' + CONVERT(NVARCHAR, @freq_subday_interval) + N' second(s)'
      WHEN 0x4 THEN N'every ' + CONVERT(NVARCHAR, @freq_subday_interval) + N' minute(s)'
      WHEN 0x8 THEN N'every ' + CONVERT(NVARCHAR, @freq_subday_interval) + N' hour(s)'
    END
  IF (@freq_subday_type IN (0x2, 0x4, 0x8))
    SELECT @schedule_description = @schedule_description + N' between ' +
           CONVERT(NVARCHAR, @active_start_time) + N' and ' + CONVERT(NVARCHAR, @active_end_time)
END
go

CHECKPOINT
go


/**************************************************************/
/* SP_ADD_JOBSCHEDULE                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_jobschedule...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_jobschedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_jobschedule
go
CREATE PROCEDURE sp_add_jobschedule                 
  @job_id                 UNIQUEIDENTIFIER = NULL,
  @job_name               sysname          = NULL,
  @name                   sysname,
  @enabled                TINYINT          = 1,
  @freq_type              INT              = 1,
  @freq_interval          INT              = 0,
  @freq_subday_type       INT              = 0,
  @freq_subday_interval   INT              = 0,
  @freq_relative_interval INT              = 0,
  @freq_recurrence_factor INT              = 0,
  @active_start_date      INT              = NULL,     -- sp_verify_schedule assigns a default
  @active_end_date        INT              = 99991231, -- December 31st 9999
  @active_start_time      INT              = 000000,   -- 12:00:00 am
  @active_end_time        INT              = 235959,    -- 11:59:59 pm
  @schedule_id            INT              = NULL  OUTPUT,
  @automatic_post         BIT              = 1,         -- If 1 will post notifications to all tsx servers to that run this job
  @schedule_uid           UNIQUEIDENTIFIER = NULL OUTPUT
AS
BEGIN
  DECLARE @retval           INT
  DECLARE @owner_login_name sysname

  SET NOCOUNT ON

  -- Check authority (only SQLServerAgent can add a schedule to a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Check that we can uniquely identify the job
  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Get the owner of the job. Prior to resusable schedules the job owner also owned the schedule
  SELECT @owner_login_name = dbo.SQLAGENT_SUSER_SNAME(owner_sid)
  FROM   sysjobs
  WHERE  (job_id = @job_id) 

  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) AND
      (SUSER_SNAME() <> @owner_login_name))
  BEGIN
   RAISERROR(14525, -1, -1)
   RETURN(1) -- Failure
  END

  -- Check authority (only SQLServerAgent can add a schedule to a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Add the schedule first
  EXECUTE @retval = msdb.dbo.sp_add_schedule @schedule_name          = @name,
                                             @enabled                = @enabled,
                                             @freq_type              = @freq_type,
                                             @freq_interval          = @freq_interval,
                                             @freq_subday_type       = @freq_subday_type,
                                             @freq_subday_interval   = @freq_subday_interval,
                                             @freq_relative_interval = @freq_relative_interval,
                                             @freq_recurrence_factor = @freq_recurrence_factor,
                                             @active_start_date      = @active_start_date,
                                             @active_end_date        = @active_end_date,
                                             @active_start_time      = @active_start_time,
                                             @active_end_time        = @active_end_time,
                                             @owner_login_name       = @owner_login_name,
                                             @schedule_uid           = @schedule_uid OUTPUT,
                                             @schedule_id            = @schedule_id  OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure
 
 
  EXECUTE @retval = msdb.dbo.sp_attach_schedule @job_id           = @job_id, 
                                                @job_name         = NULL,
                                                @schedule_id      = @schedule_id,
                                                @schedule_name    = NULL,
                                                @automatic_post   = @automatic_post
  IF (@retval <> 0)
    RETURN(1) -- Failure
    
    

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_UPDATE_JOBSCHEDULE                                      */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_jobschedule...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_jobschedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_jobschedule
go
CREATE PROCEDURE sp_update_jobschedule              -- This SP is deprecated by sp_update_schedule.
  @job_id                 UNIQUEIDENTIFIER = NULL,
  @job_name               sysname          = NULL,
  @name                   sysname,
  @new_name               sysname          = NULL,
  @enabled                TINYINT          = NULL,
  @freq_type              INT              = NULL,
  @freq_interval          INT              = NULL,
  @freq_subday_type       INT              = NULL,
  @freq_subday_interval   INT              = NULL,
  @freq_relative_interval INT              = NULL,
  @freq_recurrence_factor INT              = NULL,
  @active_start_date      INT              = NULL,
  @active_end_date        INT              = NULL,
  @active_start_time      INT              = NULL,
  @active_end_time        INT              = NULL,
  @automatic_post         BIT              = 1         -- If 1 will post notifications to all tsx servers to that run this job
AS
BEGIN
  DECLARE @retval                   INT
  DECLARE @sched_count              INT
  DECLARE @schedule_id              INT
  DECLARE @job_owner_sid            VARBINARY(85)
  DECLARE @enable_only_used         INT

  DECLARE @x_name                   sysname
  DECLARE @x_enabled                TINYINT
  DECLARE @x_freq_type              INT
  DECLARE @x_freq_interval          INT
  DECLARE @x_freq_subday_type       INT
  DECLARE @x_freq_subday_interval   INT
  DECLARE @x_freq_relative_interval INT
  DECLARE @x_freq_recurrence_factor INT
  DECLARE @x_active_start_date      INT
  DECLARE @x_active_end_date        INT
  DECLARE @x_active_start_time      INT
  DECLARE @x_active_end_time        INT
  DECLARE @owner_sid                VARBINARY(85)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name     = LTRIM(RTRIM(@name))
  SELECT @new_name = LTRIM(RTRIM(@new_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@new_name = N'') SELECT @new_name = NULL

  -- Check authority (only SQLServerAgent can modify a schedule of a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = 'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Check that we can uniquely identify the job
  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                               @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure
    
  -- Is @enable the only parameter used beside jobname and jobid?
  IF ((@enabled                   IS NOT NULL) AND
     (@name                 IS NULL) AND
     (@new_name                IS NULL) AND
      (@freq_type              IS NULL) AND
      (@freq_interval             IS NULL) AND
      (@freq_subday_type          IS NULL) AND
      (@freq_subday_interval      IS NULL) AND
      (@freq_relative_interval       IS NULL) AND
      (@freq_recurrence_factor       IS NULL) AND
      (@active_start_date         IS NULL) AND
      (@active_end_date           IS NULL) AND
      (@active_start_time         IS NULL) AND
      (@active_end_time           IS NULL))
    SELECT @enable_only_used = 1
  ELSE
    SELECT @enable_only_used = 0
    

  IF ((SUSER_SID() <> @job_owner_sid)
     AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
      AND (@enable_only_used <> 1 OR ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) <> 1))
  BEGIN
   RAISERROR(14525, -1, -1)
   RETURN(1) -- Failure
  END

  -- Make sure the schedule_id can be uniquely identified and that it exists
  -- Note: It's safe use the values returned by the MIN() function because the SP errors out if more than 1 record exists
  SELECT @sched_count = COUNT(*),
         @schedule_id = MIN(sched.schedule_id),
         @owner_sid   = MIN(sched.owner_sid)
  FROM msdb.dbo.sysjobschedules as jsched
    JOIN msdb.dbo.sysschedules_localserver_view as sched
      ON jsched.schedule_id = sched.schedule_id
  WHERE (jsched.job_id = @job_id)
    AND (sched.name = @name)

  -- Need to use sp_update_schedule to update this ambiguous schedule name
  IF(@sched_count > 1)
  BEGIN
    RAISERROR(14375, -1, -1, @name, @job_name)
    RETURN(1) -- Failure
  END

  IF (@schedule_id IS NULL)
  BEGIN
   --raise an explicit message if the schedule does exist but isn't attached to this job
   IF EXISTS(SELECT * 
           FROM sysschedules_localserver_view
              WHERE (name = @name))
   BEGIN
      RAISERROR(14374, -1, -1, @name, @job_name)
   END
   ELSE
    BEGIN
      --If the schedule is from an MSX and a sysadmin is calling report a specific 'MSX' message
      IF(PROGRAM_NAME() NOT LIKE N'SQLAgent%' AND
         ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1 AND
         EXISTS(SELECT * 
                FROM msdb.dbo.sysschedules as sched
                  JOIN msdb.dbo.sysoriginatingservers_view as svr
                    ON sched.originating_server_id = svr.originating_server_id
                  JOIN msdb.dbo.sysjobschedules as js 
                    ON sched.schedule_id = js.schedule_id
                WHERE (svr.master_server = 1) AND
                      (sched.name = @name) AND
                      (js.job_id = @job_id)))
     BEGIN
       RAISERROR(14274, -1, -1)
     END
      ELSE
      BEGIN
        --Generic message that the schedule doesn't exist
        RAISERROR(14262, -1, -1, 'Schedule Name', @name)
      END
   END

   RETURN(1) -- Failure
  END

  -- Set the x_ (existing) variables
  SELECT @x_name                   = name,
         @x_enabled                = enabled,
         @x_freq_type              = freq_type,
         @x_freq_interval          = freq_interval,
         @x_freq_subday_type       = freq_subday_type,
         @x_freq_subday_interval   = freq_subday_interval,
         @x_freq_relative_interval = freq_relative_interval,
         @x_freq_recurrence_factor = freq_recurrence_factor,
         @x_active_start_date      = active_start_date,
         @x_active_end_date        = active_end_date,
         @x_active_start_time      = active_start_time,
         @x_active_end_time        = active_end_time
  FROM msdb.dbo.sysschedules_localserver_view
  WHERE (schedule_id = @schedule_id )

  
  -- Fill out the values for all non-supplied parameters from the existing values
  IF (@new_name               IS NULL) SELECT @new_name               = @x_name
  IF (@enabled                IS NULL) SELECT @enabled                = @x_enabled
  IF (@freq_type              IS NULL) SELECT @freq_type              = @x_freq_type
  IF (@freq_interval          IS NULL) SELECT @freq_interval          = @x_freq_interval
  IF (@freq_subday_type       IS NULL) SELECT @freq_subday_type       = @x_freq_subday_type
  IF (@freq_subday_interval   IS NULL) SELECT @freq_subday_interval   = @x_freq_subday_interval
  IF (@freq_relative_interval IS NULL) SELECT @freq_relative_interval = @x_freq_relative_interval
  IF (@freq_recurrence_factor IS NULL) SELECT @freq_recurrence_factor = @x_freq_recurrence_factor
  IF (@active_start_date      IS NULL) SELECT @active_start_date      = @x_active_start_date
  IF (@active_end_date        IS NULL) SELECT @active_end_date        = @x_active_end_date
  IF (@active_start_time      IS NULL) SELECT @active_start_time      = @x_active_start_time
  IF (@active_end_time        IS NULL) SELECT @active_end_time        = @x_active_end_time

  -- Check schedule (frequency and owner) parameters
  EXECUTE @retval = sp_verify_schedule @schedule_id             = @schedule_id,
                                       @name                    = @new_name,
                                       @enabled                 = @enabled,
                                       @freq_type               = @freq_type,
                                       @freq_interval           = @freq_interval            OUTPUT,
                                       @freq_subday_type        = @freq_subday_type         OUTPUT,
                                       @freq_subday_interval    = @freq_subday_interval     OUTPUT,
                                       @freq_relative_interval  = @freq_relative_interval   OUTPUT,
                                       @freq_recurrence_factor  = @freq_recurrence_factor   OUTPUT,
                                       @active_start_date       = @active_start_date        OUTPUT,
                                       @active_start_time       = @active_start_time        OUTPUT,
                                       @active_end_date         = @active_end_date          OUTPUT,
                                       @active_end_time         = @active_end_time          OUTPUT,
                                       @owner_sid               = @owner_sid
  IF (@retval <> 0)
    RETURN(1) -- Failure


  -- Update the JobSchedule
  UPDATE msdb.dbo.sysschedules
  SET name                   = @new_name,
      enabled                = @enabled,
      freq_type              = @freq_type,
      freq_interval          = @freq_interval,
      freq_subday_type       = @freq_subday_type,
      freq_subday_interval   = @freq_subday_interval,
      freq_relative_interval = @freq_relative_interval,
      freq_recurrence_factor = @freq_recurrence_factor,
      active_start_date      = @active_start_date,
      active_end_date        = @active_end_date,
      active_start_time      = @active_start_time,
      active_end_time        = @active_end_time,
      date_modified          = GETDATE(),
      version_number         = version_number + 1
  WHERE (schedule_id = @schedule_id)

  SELECT @retval = @@error

  -- Update the job's version/last-modified information
  UPDATE msdb.dbo.sysjobs
  SET version_number = version_number + 1,
      date_modified = GETDATE()
  WHERE (job_id = @job_id)

  -- Notify SQLServerAgent of the change, but only if we know the job has been cached
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id = 0)))
  BEGIN
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                        @job_id      = @job_id,
                                        @schedule_id = @schedule_id,
                                        @action_type = N'U'
  END

  -- For a multi-server job, remind the user that they need to call sp_post_msx_operation
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
    -- Instruct the tsx servers to pick up the altered schedule
    IF (@automatic_post = 1)
    BEGIN
      DECLARE @schedule_uid UNIQUEIDENTIFIER
      SELECT @schedule_uid = schedule_uid 
      FROM sysschedules 
      WHERE schedule_id = @schedule_id

      IF(NOT @schedule_uid IS NULL)
      BEGIN
        -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines 
        EXECUTE sp_post_msx_operation @operation = 'INSERT', @object_type = 'SCHEDULE', @schedule_uid = @schedule_uid
      END
    END
    ELSE
      RAISERROR(14547, 0, 1, N'INSERT', N'sp_post_msx_operation')

  -- Automatic addition and removal of -Continous parameter for replication agent
  EXECUTE sp_update_replication_job_parameter @job_id = @job_id,
                                              @old_freq_type = @x_freq_type,
                                              @new_freq_type = @freq_type

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_JOBSCHEDULE                                      */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_jobschedule...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_delete_jobschedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_jobschedule
go
CREATE PROCEDURE sp_delete_jobschedule           -- This SP is deprecated. Use sp_detach_schedule and sp_delete_schedule.
  @job_id           UNIQUEIDENTIFIER = NULL,
  @job_name         sysname          = NULL,
  @name             sysname,
  @keep_schedule    int              = 0,
  @automatic_post       BIT          = 1         -- If 1 will post notifications to all tsx servers to that run this schedule
AS
BEGIN
  DECLARE @retval           INT
  DECLARE @sched_count      INT
  DECLARE @schedule_id      INT
  DECLARE @job_owner_sid    VARBINARY(85)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name = LTRIM(RTRIM(@name))

  -- Check authority (only SQLServerAgent can delete a schedule of a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Check that we can uniquely identify the job
  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                               @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) AND
      (SUSER_SID() <> @job_owner_sid))
  BEGIN
   RAISERROR(14525, -1, -1)
   RETURN(1) -- Failure
  END


  IF (UPPER(@name collate SQL_Latin1_General_CP1_CS_AS) = N'ALL')
  BEGIN
    SELECT @schedule_id = -1  -- We use this in the call to sp_sqlagent_notify
    
    --Delete the schedule(s) if it isn't being used by other jobs
    DECLARE @temp_schedules_to_delete TABLE (schedule_id INT NOT NULL)


    --If user requests that the schedules be removed (the legacy behavoir)
    --make sure it isnt being used by other jobs
    IF (@keep_schedule = 0)
    BEGIN
        --Get the list of schedules to delete
        INSERT INTO @temp_schedules_to_delete
        SELECT DISTINCT schedule_id 
        FROM   msdb.dbo.sysschedules
        WHERE (schedule_id IN 
                (SELECT schedule_id
                FROM msdb.dbo.sysjobschedules
                WHERE (job_id = @job_id)))
            
        --make sure no other jobs use these schedules
        IF( EXISTS(SELECT *
                    FROM msdb.dbo.sysjobschedules 
                    WHERE (job_id <> @job_id)
                    AND (schedule_id in ( SELECT schedule_id 
                                            FROM @temp_schedules_to_delete ))))
        BEGIN
        RAISERROR(14367, -1, -1)   
        RETURN(1) -- Failure
        END
    END

    --OK to delete the jobschedule
    DELETE FROM msdb.dbo.sysjobschedules
    WHERE (job_id = @job_id)
    
    --OK to delete the schedule - temp_schedules_to_delete is empty if @keep_schedule <> 0
    DELETE FROM msdb.dbo.sysschedules
    WHERE schedule_id IN 
    (SELECT schedule_id from @temp_schedules_to_delete)
  END
  ELSE
  BEGIN

    -- Make sure the schedule_id can be uniquely identified and that it exists
    -- Note: It's safe use the values returned by the MIN() function because the SP errors out if more than 1 record exists
    SELECT @sched_count = COUNT(*),
           @schedule_id = MIN(sched.schedule_id)
    FROM msdb.dbo.sysjobschedules as jsched
      JOIN msdb.dbo.sysschedules_localserver_view as sched
        ON jsched.schedule_id = sched.schedule_id
    WHERE (jsched.job_id = @job_id)
      AND (sched.name = @name)
  
    -- Need to use sp_detach_schedule to remove this ambiguous schedule name
    IF(@sched_count > 1)
    BEGIN
      RAISERROR(14376, -1, -1, @name, @job_name)
      RETURN(1) -- Failure
    END

    IF (@schedule_id IS NULL)
    BEGIN    
     --raise an explicit message if the schedule does exist but isn't attached to this job
     IF EXISTS(SELECT * 
             FROM sysschedules_localserver_view
                WHERE (name = @name))
     BEGIN
      RAISERROR(14374, -1, -1, @name, @job_name)
     END
     ELSE
      BEGIN
        --If the schedule is from an MSX and a sysadmin is calling report a specific 'MSX' message
        IF(PROGRAM_NAME() NOT LIKE N'SQLAgent%' AND
           ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1 AND
           EXISTS(SELECT * 
                  FROM msdb.dbo.sysschedules as sched
                    JOIN msdb.dbo.sysoriginatingservers_view as svr
                      ON sched.originating_server_id = svr.originating_server_id
                    JOIN msdb.dbo.sysjobschedules as js 
                      ON sched.schedule_id = js.schedule_id
                  WHERE (svr.master_server = 1) AND
                        (sched.name = @name) AND
                        (js.job_id = @job_id)))
       BEGIN
         RAISERROR(14274, -1, -1)
       END
        ELSE
        BEGIN
          --Generic message that the schedule doesn't exist
          RAISERROR(14262, -1, -1, '@name', @name)
        END
     END

      RETURN(1) -- Failure
    END

    --If user requests that the schedule be removed (the legacy behavoir)
    --make sure it isnt being used by another job
    IF (@keep_schedule = 0)
    BEGIN
      IF( EXISTS(SELECT * 
                 FROM msdb.dbo.sysjobschedules
                 WHERE (schedule_id = @schedule_id)
                   AND (job_id <> @job_id) ))
      BEGIN
        RAISERROR(14368, -1, -1, @name)
        RETURN(1) -- Failure
      END
    END

    --Delete the job schedule link first
    DELETE FROM msdb.dbo.sysjobschedules
    WHERE (job_id = @job_id)
    AND (schedule_id = @schedule_id)
    --Delete schedule if required
    IF (@keep_schedule = 0)
    BEGIN
      --Now delete the schedule if required
      DELETE FROM msdb.dbo.sysschedules
      WHERE (schedule_id = @schedule_id)   
    END

  END


  -- Update the job's version/last-modified information
  UPDATE msdb.dbo.sysjobs
  SET version_number = version_number + 1,
      date_modified = GETDATE()
  WHERE (job_id = @job_id)

  -- Notify SQLServerAgent of the change, but only if we know the job has been cached
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id = 0)))
  BEGIN
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                        @job_id      = @job_id,
                                        @schedule_id = @schedule_id,
                                        @action_type = N'D'
  END

  -- For a multi-server job, remind the user that they need to call sp_post_msx_operation
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
    -- Instruct the tsx servers to pick up the altered schedule
    IF (@automatic_post = 1)
    BEGIN
      DECLARE @schedule_uid UNIQUEIDENTIFIER
      SELECT @schedule_uid = schedule_uid 
      FROM sysschedules 
      WHERE schedule_id = @schedule_id

      IF(NOT @schedule_uid IS NULL)
      BEGIN
        -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines 
        EXECUTE sp_post_msx_operation @operation = 'INSERT', @object_type = 'SCHEDULE', @schedule_uid = @schedule_uid
      END
    END
    ELSE
      RAISERROR(14547, 0, 1, N'INSERT', N'sp_post_msx_operation')

  RETURN(@retval) -- 0 means success
END
go


/**************************************************************/
/* SP_HELP_SCHEDULE                                           */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_schedule...'
go

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_schedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_schedule
go

CREATE PROCEDURE sp_help_schedule
  @schedule_id              INT     = NULL, -- If both @schedule_id and @schedule_name are NULL retreive all schedules 
  @schedule_name            sysname = NULL,
  @attached_schedules_only  BIT     = 0,    -- If 1 only retreive all schedules that are attached to jobs
  @include_description      BIT     = 0     -- 1 if a schedule description is required (NOTE: It's expensive to generate the description)
AS
BEGIN
  DECLARE @retval                 INT
  DECLARE @schedule_description   NVARCHAR(255)
  DECLARE @name                   sysname
  DECLARE @freq_type              INT
  DECLARE @freq_interval          INT
  DECLARE @freq_subday_type       INT
  DECLARE @freq_subday_interval   INT
  DECLARE @freq_relative_interval INT
  DECLARE @freq_recurrence_factor INT
  DECLARE @active_start_date      INT
  DECLARE @active_end_date        INT
  DECLARE @active_start_time      INT
  DECLARE @active_end_time        INT
  DECLARE @schedule_id_as_char    VARCHAR(10)
  
  SET NOCOUNT ON
    
  -- If both @schedule_id and @schedule_name are NULL retreive all schedules (depending on @attached_schedules_only)
  -- otherwise verify the schedule exists
  IF (@schedule_id IS NOT NULL) OR (@schedule_name IS NOT NULL)  
  BEGIN    
    -- Check that we can uniquely identify the schedule
    EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                              @name_of_id_parameter   = '@schedule_id',
                                                              @schedule_name          = @schedule_name OUTPUT,
                                                              @schedule_id            = @schedule_id   OUTPUT,
                                                              @owner_sid              = NULL,
                                                              @orig_server_id         = NULL
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END


  -- Get the schedule(s) that are attached to a job (or all schs if @attached_schedules_only = 0) into a temporary table
  SELECT schedule_id,
         schedule_uid,
        'schedule_name' = name,
         enabled,
         freq_type,
         freq_interval,
         freq_subday_type,
         freq_subday_interval,
         freq_relative_interval,
         freq_recurrence_factor,
         active_start_date,
         active_end_date,
         active_start_time,
         active_end_time,
         date_created,
        'schedule_description' = FORMATMESSAGE(14549)
  INTO #temp_jobschedule
  FROM msdb.dbo.sysschedules_localserver_view as sch
  WHERE ( (@attached_schedules_only = 0) 
         OR (EXISTS(SELECT * FROM sysjobschedules as jobsch WHERE sch.schedule_id = jobsch.schedule_id)) )
    AND((@schedule_id IS NULL) OR (schedule_id = @schedule_id))

  IF (@include_description = 1)
  BEGIN
    -- For each schedule, generate the textual schedule description and update the temporary
    -- table with it
    IF (EXISTS (SELECT *
                FROM #temp_jobschedule))
    BEGIN
      WHILE (EXISTS (SELECT *
                     FROM #temp_jobschedule
                     WHERE schedule_description = FORMATMESSAGE(14549)))
      BEGIN
        SET ROWCOUNT 1
        SELECT @name                   = schedule_name,
               @freq_type              = freq_type,
               @freq_interval          = freq_interval,
               @freq_subday_type       = freq_subday_type,
               @freq_subday_interval   = freq_subday_interval,
               @freq_relative_interval = freq_relative_interval,
               @freq_recurrence_factor = freq_recurrence_factor,
               @active_start_date      = active_start_date,
               @active_end_date        = active_end_date,
               @active_start_time      = active_start_time,
               @active_end_time        = active_end_time
        FROM #temp_jobschedule
        WHERE (schedule_description = FORMATMESSAGE(14549))
        SET ROWCOUNT 0

        EXECUTE sp_get_schedule_description
          @freq_type,
          @freq_interval,
          @freq_subday_type,
          @freq_subday_interval,
          @freq_relative_interval,
          @freq_recurrence_factor,
          @active_start_date,
          @active_end_date,
          @active_start_time,
          @active_end_time,
          @schedule_description OUTPUT

        UPDATE #temp_jobschedule
        SET schedule_description = ISNULL(LTRIM(RTRIM(@schedule_description)), FORMATMESSAGE(14205))
        WHERE (schedule_name = @name)
      END -- While
    END
  END

  -- Return the result set, adding job count
  SELECT *, (SELECT COUNT(*) FROM sysjobschedules WHERE sysjobschedules.schedule_id = #temp_jobschedule.schedule_id) as 'job_count'
  FROM #temp_jobschedule
  ORDER BY schedule_id

  RETURN(@@error) -- 0 means success
END
go


/**************************************************************/
/* SP_HELP_JOBSCHEDULE                                        */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_jobschedule...'

go


IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobschedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobschedule

go

CREATE PROCEDURE sp_help_jobschedule
  @job_id              UNIQUEIDENTIFIER = NULL,
  @job_name            sysname          = NULL,
  @schedule_name       sysname          = NULL,
  @schedule_id         INT              = NULL,
  @include_description BIT              = 0 -- 1 if a schedule description is required (NOTE: It's expensive to generate the description)
AS
BEGIN
  DECLARE @retval                 INT
  DECLARE @schedule_description   NVARCHAR(255)
  DECLARE @name                   sysname
  DECLARE @freq_type              INT
  DECLARE @freq_interval          INT
  DECLARE @freq_subday_type       INT
  DECLARE @freq_subday_interval   INT
  DECLARE @freq_relative_interval INT
  DECLARE @freq_recurrence_factor INT
  DECLARE @active_start_date      INT
  DECLARE @active_end_date        INT
  DECLARE @active_start_time      INT
  DECLARE @active_end_time        INT
  DECLARE @schedule_id_as_char    VARCHAR(10)
  DECLARE @job_count               INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @schedule_name = LTRIM(RTRIM(@schedule_name))
  SELECT @job_count = 0

  -- Turn [nullable] empty string parameters into NULLs
  IF (@schedule_name = N'') SELECT @schedule_name = NULL

  -- The user must provide either:
  -- 1) job_id (or job_name) and (optionally) a schedule name
  -- or...
  -- 2) just schedule_id
  IF (@schedule_id IS NULL) AND
     (@job_id      IS NULL) AND
     (@job_name    IS NULL)
  BEGIN
    RAISERROR(14273, -1, -1)
    RETURN(1) -- Failure
  END

  IF (@schedule_id IS NOT NULL) AND ((@job_id        IS NOT NULL) OR
                                     (@job_name      IS NOT NULL) OR
                                     (@schedule_name IS NOT NULL))
  BEGIN
    RAISERROR(14273, -1, -1)
    RETURN(1) -- Failure
  END

  -- Check that the schedule (by ID) exists and it is only used by one job. 
  -- Allowing this for backward compatibility with versions prior to V9
  IF (@schedule_id IS NOT NULL) AND 
     (@job_id      IS NULL) AND
     (@job_name    IS NULL)
  BEGIN
  
    SELECT @job_count = COUNT(*)
    FROM msdb.dbo.sysjobschedules
    WHERE (schedule_id = @schedule_id) 
    
    if(@job_count > 1)
    BEGIN
      SELECT @schedule_id_as_char = CONVERT(VARCHAR, @schedule_id)
      RAISERROR(14369, -1, -1, @schedule_id_as_char)
      RETURN(1) -- Failure
    END
  
    SELECT @job_id = job_id
    FROM msdb.dbo.sysjobschedules
    WHERE (schedule_id = @schedule_id)
    IF (@job_id IS NULL)
    BEGIN
      SELECT @schedule_id_as_char = CONVERT(VARCHAR, @schedule_id)
      RAISERROR(14262, -1, -1, '@schedule_id', @schedule_id_as_char)
      RETURN(1) -- Failure
    END
  END

  -- Check that we can uniquely identify the job
  IF (@job_id IS NOT NULL) OR (@job_name IS NOT NULL)
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT,
                                                'NO_TEST'
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  IF (@schedule_id IS NOT NULL OR @schedule_name IS NOT NULL)
  BEGIN
    -- Check that we can uniquely identify the schedule
    EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                              @name_of_id_parameter   = '@schedule_id',
                                                              @schedule_name          = @schedule_name OUTPUT,
                                                              @schedule_id            = @schedule_id   OUTPUT,
                                                              @owner_sid              = NULL,
                                                              @orig_server_id         = NULL,
                                                              @job_id_filter          = @job_id
    IF (@retval <> 0)
      RETURN(1) -- Failure
  
  END

  -- Check that the schedule (by name) exists
  IF (@schedule_name IS NOT NULL)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysjobschedules AS js
                      JOIN msdb.dbo.sysschedules AS s
                        ON js.schedule_id = s.schedule_id
                    WHERE (js.job_id = @job_id)
                      AND (s.name = @schedule_name)))
    BEGIN
      RAISERROR(14262, -1, -1, '@schedule_name', @schedule_name)
      RETURN(1) -- Failure
    END
  END

  -- Get the schedule(s) into a temporary table
  SELECT s.schedule_id,
        'schedule_name' = name,
         enabled,
         freq_type,
         freq_interval,
         freq_subday_type,
         freq_subday_interval,
         freq_relative_interval,
         freq_recurrence_factor,
         active_start_date,
         active_end_date,
         active_start_time,
         active_end_time,
         date_created,
        'schedule_description' = FORMATMESSAGE(14549),
         js.next_run_date,
         js.next_run_time,
         s.schedule_uid
  INTO #temp_jobschedule
  FROM msdb.dbo.sysjobschedules AS js
    JOIN msdb.dbo.sysschedules AS s
      ON js.schedule_id = s.schedule_id
  WHERE ((@job_id IS NULL) OR (js.job_id = @job_id))
    AND ((@schedule_name IS NULL) OR (s.name = @schedule_name))
    AND ((@schedule_id IS NULL) OR (s.schedule_id = @schedule_id))

  IF (@include_description = 1)
  BEGIN
    -- For each schedule, generate the textual schedule description and update the temporary
    -- table with it
    IF (EXISTS (SELECT *
                FROM #temp_jobschedule))
    BEGIN
      WHILE (EXISTS (SELECT *
                     FROM #temp_jobschedule
                     WHERE schedule_description = FORMATMESSAGE(14549)))
      BEGIN
        SET ROWCOUNT 1
        SELECT @name                   = schedule_name,
               @freq_type              = freq_type,
               @freq_interval          = freq_interval,
               @freq_subday_type       = freq_subday_type,
               @freq_subday_interval   = freq_subday_interval,
               @freq_relative_interval = freq_relative_interval,
               @freq_recurrence_factor = freq_recurrence_factor,
               @active_start_date      = active_start_date,
               @active_end_date        = active_end_date,
               @active_start_time      = active_start_time,
               @active_end_time        = active_end_time
        FROM #temp_jobschedule
        WHERE (schedule_description = FORMATMESSAGE(14549))
        SET ROWCOUNT 0

        EXECUTE sp_get_schedule_description
          @freq_type,
          @freq_interval,
          @freq_subday_type,
          @freq_subday_interval,
          @freq_relative_interval,
          @freq_recurrence_factor,
          @active_start_date,
          @active_end_date,
          @active_start_time,
          @active_end_time,
          @schedule_description OUTPUT

        UPDATE #temp_jobschedule
        SET schedule_description = ISNULL(LTRIM(RTRIM(@schedule_description)), FORMATMESSAGE(14205))
        WHERE (schedule_name = @name)
      END -- While
    END
  END

  -- Return the result set, adding job count to it
  SELECT *, (SELECT COUNT(*) FROM sysjobschedules WHERE sysjobschedules.schedule_id = #temp_jobschedule.schedule_id) as 'job_count'
  FROM #temp_jobschedule
  ORDER BY schedule_id

  RETURN(@@error) -- 0 means success
END

go

CHECKPOINT
go

/**************************************************************/
/* SP_VERIFY_JOB                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_job
go
CREATE PROCEDURE sp_verify_job
  @job_id                       UNIQUEIDENTIFIER,
  @name                         sysname,
  @enabled                      TINYINT,
  @start_step_id                INT,
  @category_name                sysname,
  @owner_sid                    VARBINARY(85) OUTPUT, -- Output since we may modify it
  @notify_level_eventlog        INT,
  @notify_level_email           INT           OUTPUT, -- Output since we may reset it to 0
  @notify_level_netsend         INT           OUTPUT, -- Output since we may reset it to 0
  @notify_level_page            INT           OUTPUT, -- Output since we may reset it to 0
  @notify_email_operator_name   sysname,
  @notify_netsend_operator_name sysname,
  @notify_page_operator_name    sysname,
  @delete_level                 INT,
  @category_id                  INT           OUTPUT, -- The ID corresponding to the name
  @notify_email_operator_id     INT           OUTPUT, -- The ID corresponding to the name
  @notify_netsend_operator_id   INT           OUTPUT, -- The ID corresponding to the name
  @notify_page_operator_id      INT           OUTPUT, -- The ID corresponding to the name
  @originating_server           sysname       OUTPUT  -- Output since we may modify it
AS
BEGIN
  DECLARE @job_type           INT
  DECLARE @retval             INT
  DECLARE @current_date       INT
  DECLARE @res_valid_range    NVARCHAR(200)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name                       = LTRIM(RTRIM(@name))
  SELECT @category_name              = LTRIM(RTRIM(@category_name))
  SELECT @originating_server         = UPPER(LTRIM(RTRIM(@originating_server)))

  SELECT @originating_server = ISNULL(@originating_server, UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))))

  -- Check originating server (only the SQLServerAgent can add jobs that originate from a remote server)
  IF (@originating_server <> UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))) AND
     (PROGRAM_NAME() NOT LIKE N'SQLAgent%')
  BEGIN
    RAISERROR(14275, -1, -1)
    RETURN(1) -- Failure
  END

  -- NOTE: We allow jobs with the same name (since job_id is always unique) but only if
  --       they originate from different servers.  Thus jobs can flow from an MSX to a TSX
  --       without having to worry about naming conflicts.
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobs as job
                JOIN msdb.dbo.sysoriginatingservers_view as svr 
                  ON (svr.originating_server_id = job.originating_server_id)  
              WHERE (name = @name)
                AND (svr.originating_server = @originating_server)
                AND (job_id <> ISNULL(@job_id, 0x911)))) -- When adding a new job @job_id is NULL
  BEGIN
    RAISERROR(14261, -1, -1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Check enabled state
  IF (@enabled <> 0) AND (@enabled <> 1)
  BEGIN
    RAISERROR(14266, -1, -1, '@enabled', '0, 1')
    RETURN(1) -- Failure
  END

  -- Check start step
  IF (@job_id IS NULL)
  BEGIN
    -- New job
    -- NOTE: For [new] MSX jobs we allow the start step to be other than 1 since
    --       the start step was validated when the job was created at the MSX
    IF (@start_step_id <> 1) AND (@originating_server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))))
    BEGIN
      RAISERROR(14266, -1, -1, '@start_step_id', '1')
      RETURN(1) -- Failure
    END
  END
  ELSE
  BEGIN
    -- Existing job
    DECLARE @max_step_id INT
    DECLARE @valid_range VARCHAR(50)

    -- Get current maximum step id
    SELECT @max_step_id = ISNULL(MAX(step_id), 0)
    FROM msdb.dbo.sysjobsteps
    WHERE (job_id = @job_id)

    IF (@start_step_id < 1) OR (@start_step_id > @max_step_id + 1)
    BEGIN
      SELECT @valid_range = '1..' + CONVERT(VARCHAR, @max_step_id + 1)
      RAISERROR(14266, -1, -1, '@start_step_id', @valid_range)
      RETURN(1) -- Failure
    END
  END

  -- Check category
  SELECT @job_type = NULL

  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id = 0)))
    SELECT @job_type = 1 -- LOCAL

  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
    SELECT @job_type = 2 -- MULTI-SERVER

  -- A local job cannot be added to a multi-server job_category
  IF (@job_type = 1) AND (EXISTS (SELECT *
                                  FROM msdb.dbo.syscategories
                                  WHERE (category_class = 1) -- Job
                                    AND (category_type = 2) -- Multi-Server
                                    AND (name = @category_name)))
  BEGIN
    RAISERROR(14285, -1, -1)
    RETURN(1) -- Failure
  END

  -- A multi-server job cannot be added to a local job_category
  IF (@job_type = 2) AND (EXISTS (SELECT *
                                  FROM msdb.dbo.syscategories
                                  WHERE (category_class = 1) -- Job
                                    AND (category_type = 1) -- Local
                                    AND (name = @category_name)))
  BEGIN
    RAISERROR(14286, -1, -1)
    RETURN(1) -- Failure
  END

  -- Get the category_id, handling any special-cases as appropriate
  SELECT @category_id = NULL
  IF (@category_name = N'[DEFAULT]') -- User wants to revert to the default job category
  BEGIN
    SELECT @category_id = CASE ISNULL(@job_type, 1)
                            WHEN 1 THEN 0 -- [Uncategorized (Local)]
                            WHEN 2 THEN 2 -- [Uncategorized (Multi-Server)]
                          END
  END
  ELSE
  IF (@category_name IS NULL) -- The sp_add_job default
  BEGIN
    SELECT @category_id = 0
  END
  ELSE
  BEGIN
    SELECT @category_id = category_id
    FROM msdb.dbo.syscategories
    WHERE (category_class = 1) -- Job
      AND (name = @category_name)
  END

  IF (@category_id IS NULL)
  BEGIN
    RAISERROR(14234, -1, -1, '@category_name', 'sp_help_category')
    RETURN(1) -- Failure
  END

  -- Only SQLServerAgent may add jobs to the 'Jobs From MSX' category
  IF (@category_id = 1) AND
     (PROGRAM_NAME() NOT LIKE N'SQLAgent%')
  BEGIN
    RAISERROR(14267, -1, -1, @category_name)
    RETURN(1) -- Failure
  END

  -- Check owner
  -- Default the owner to be the calling user if:
  -- caller is not a sysadmin
  -- caller is not SQLAgentOperator and job_id is NULL, meaning new job 
  IF (((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0) AND
      ((ISNULL(IS_MEMBER('SQLAgentOperatorRole'), 0) = 0) AND @job_id IS NULL)) AND
      (@owner_sid <> SUSER_SID()))
  BEGIN
    SELECT @owner_sid = SUSER_SID()
  END

  -- Now just check that the login id is valid (ie. it exists and isn't an NT group)
  IF (@owner_sid <> 0x010100000000000512000000) AND -- NT AUTHORITY\SYSTEM sid
     (@owner_sid <> 0x010100000000000514000000)     -- NT AUTHORITY\NETWORK SERVICE sid
  BEGIN
     IF (@owner_sid IS NULL) OR (EXISTS (SELECT *
                                      FROM master.dbo.syslogins
                                      WHERE (sid = @owner_sid)
                                        AND (isntgroup <> 0)))
     BEGIN
       -- NOTE: In the following message we quote @owner_login_name instead of @owner_sid
       --       since this is the parameter the user passed to the calling SP (ie. either
       --       sp_add_job or sp_update_job)
       SELECT @res_valid_range = FORMATMESSAGE(14203)
       RAISERROR(14234, -1, -1, '@owner_login_name', @res_valid_range)
       RETURN(1) -- Failure
     END
  END
  
  -- Check notification levels (must be 0, 1, 2 or 3)
  IF (@notify_level_eventlog & 0x3 <> @notify_level_eventlog)
  BEGIN
    RAISERROR(14266, -1, -1, '@notify_level_eventlog', '0, 1, 2, 3')
    RETURN(1) -- Failure
  END
  IF (@notify_level_email & 0x3 <> @notify_level_email)
  BEGIN
    RAISERROR(14266, -1, -1, '@notify_level_email', '0, 1, 2, 3')
    RETURN(1) -- Failure
  END
  IF (@notify_level_netsend & 0x3 <> @notify_level_netsend)
  BEGIN
    RAISERROR(14266, -1, -1, '@notify_level_netsend', '0, 1, 2, 3')
    RETURN(1) -- Failure
  END
  IF (@notify_level_page & 0x3 <> @notify_level_page)
  BEGIN
    RAISERROR(14266, -1, -1, '@notify_level_page', '0, 1, 2, 3')
    RETURN(1) -- Failure
  END

  -- If we're at a TSX, only SQLServerAgent may add jobs that notify 'MSXOperator'
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.systargetservers)) AND
     ((@notify_email_operator_name = N'MSXOperator') OR
      (@notify_page_operator_name = N'MSXOperator') OR
      (@notify_netsend_operator_name = N'MSXOperator')) AND
     (PROGRAM_NAME() NOT LIKE N'SQLAgent%')
  BEGIN
    RAISERROR(14251, -1, -1, 'MSXOperator')
    RETURN(1) -- Failure
  END

  -- Check operator to notify (via email)
  IF (@notify_email_operator_name IS NOT NULL)
  BEGIN
    SELECT @notify_email_operator_id = id
    FROM msdb.dbo.sysoperators
    WHERE (name = @notify_email_operator_name)

    IF (@notify_email_operator_id IS NULL)
    BEGIN
      RAISERROR(14234, -1, -1, '@notify_email_operator_name', 'sp_help_operator')
      RETURN(1) -- Failure
    END
    -- If a valid operator is specified the level must be non-zero
    IF (@notify_level_email = 0)
    BEGIN
      RAISERROR(14266, -1, -1, '@notify_level_email', '1, 2, 3')
      RETURN(1) -- Failure
    END
  END
  ELSE
  BEGIN
    SELECT @notify_email_operator_id = 0
    SELECT @notify_level_email = 0
  END

  -- Check operator to notify (via netsend)
  IF (@notify_netsend_operator_name IS NOT NULL)
  BEGIN
    SELECT @notify_netsend_operator_id = id
    FROM msdb.dbo.sysoperators
    WHERE (name = @notify_netsend_operator_name)

    IF (@notify_netsend_operator_id IS NULL)
    BEGIN
      RAISERROR(14234, -1, -1, '@notify_netsend_operator_name', 'sp_help_operator')
      RETURN(1) -- Failure
    END
    -- If a valid operator is specified the level must be non-zero
    IF (@notify_level_netsend = 0)
    BEGIN
      RAISERROR(14266, -1, -1, '@notify_level_netsend', '1, 2, 3')
      RETURN(1) -- Failure
    END
  END
  ELSE
  BEGIN
    SELECT @notify_netsend_operator_id = 0
    SELECT @notify_level_netsend = 0
  END

  -- Check operator to notify (via page)
  IF (@notify_page_operator_name IS NOT NULL)
  BEGIN
    SELECT @notify_page_operator_id = id
    FROM msdb.dbo.sysoperators
    WHERE (name = @notify_page_operator_name)

    IF (@notify_page_operator_id IS NULL)
    BEGIN
      RAISERROR(14234, -1, -1, '@notify_page_operator_name', 'sp_help_operator')
      RETURN(1) -- Failure
    END
    -- If a valid operator is specified the level must be non-zero
    IF (@notify_level_page = 0)
    BEGIN
      RAISERROR(14266, -1, -1, '@notify_level_page', '1, 2, 3')
      RETURN(1) -- Failure
    END
  END
  ELSE
  BEGIN
    SELECT @notify_page_operator_id = 0
    SELECT @notify_level_page = 0
  END

  -- Check delete level (must be 0, 1, 2 or 3)
  IF (@delete_level & 0x3 <> @delete_level)
  BEGIN
    RAISERROR(14266, -1, -1, '@delete_level', '0, 1, 2, 3')
    RETURN(1) -- Failure
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_ADD_JOB                                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_job
go
CREATE PROCEDURE sp_add_job
  @job_name                     sysname,
  @enabled                      TINYINT          = 1,        -- 0 = Disabled, 1 = Enabled
  @description                  NVARCHAR(512)    = NULL,
  @start_step_id                INT              = 1,
  @category_name                sysname          = NULL,
  @category_id                  INT              = NULL,     -- A language-independent way to specify which category to use
  @owner_login_name             sysname          = NULL,     -- The procedure assigns a default
  @notify_level_eventlog        INT              = 2,        -- 0 = Never, 1 = On Success, 2 = On Failure, 3 = Always
  @notify_level_email           INT              = 0,        -- 0 = Never, 1 = On Success, 2 = On Failure, 3 = Always
  @notify_level_netsend         INT              = 0,        -- 0 = Never, 1 = On Success, 2 = On Failure, 3 = Always
  @notify_level_page            INT              = 0,        -- 0 = Never, 1 = On Success, 2 = On Failure, 3 = Always
  @notify_email_operator_name   sysname          = NULL,
  @notify_netsend_operator_name sysname          = NULL,
  @notify_page_operator_name    sysname          = NULL,
  @delete_level                 INT              = 0,        -- 0 = Never, 1 = On Success, 2 = On Failure, 3 = Always
  @job_id                       UNIQUEIDENTIFIER = NULL OUTPUT,
  @originating_server           sysname           = NULL      -- For SQLAgent use only
AS
BEGIN
  DECLARE @retval                     INT
  DECLARE @notify_email_operator_id   INT
  DECLARE @notify_netsend_operator_id INT
  DECLARE @notify_page_operator_id    INT
  DECLARE @owner_sid                  VARBINARY(85)
  DECLARE @originating_server_id      INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters (except @owner_login_name)
  SELECT @originating_server           = UPPER(LTRIM(RTRIM(@originating_server)))
  SELECT @job_name                     = LTRIM(RTRIM(@job_name))
  SELECT @description                  = LTRIM(RTRIM(@description))
  SELECT @category_name                = LTRIM(RTRIM(@category_name))
  SELECT @notify_email_operator_name   = LTRIM(RTRIM(@notify_email_operator_name))
  SELECT @notify_netsend_operator_name = LTRIM(RTRIM(@notify_netsend_operator_name))
  SELECT @notify_page_operator_name    = LTRIM(RTRIM(@notify_page_operator_name))
  SELECT @originating_server_id        = NULL

  -- Turn [nullable] empty string parameters into NULLs
  IF (@originating_server           = N'') SELECT @originating_server           = NULL
  IF (@description                  = N'') SELECT @description                  = NULL
  IF (@category_name                = N'') SELECT @category_name                = NULL
  IF (@notify_email_operator_name   = N'') SELECT @notify_email_operator_name   = NULL
  IF (@notify_netsend_operator_name = N'') SELECT @notify_netsend_operator_name = NULL
  IF (@notify_page_operator_name    = N'') SELECT @notify_page_operator_name    = NULL

  IF (@originating_server IS NULL) OR (@originating_server = '(LOCAL)')
    SELECT @originating_server= UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

  --only members of sysadmins role can set the owner
  IF (@owner_login_name IS NOT NULL AND ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0) AND (@owner_login_name <> SUSER_SNAME())
  BEGIN
    RAISERROR(14515, -1, -1)
    RETURN(1) -- Failure
  END
    
  -- Default the owner (if not supplied or if a non-sa is [illegally] trying to create a job for another user)
  -- allow special account only when caller is sysadmin
  IF (@owner_login_name = N'$(SQLAgentAccount)')  AND 
     (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
  BEGIN
    SELECT @owner_sid = 0xFFFFFFFF   
  END
  ELSE 
  IF (@owner_login_name IS NULL) OR ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0) AND (@owner_login_name <> SUSER_SNAME()))
  BEGIN
    SELECT @owner_sid = SUSER_SID()
  END
  ELSE
  BEGIN
    --force case insensitive comparation for NT users
    SELECT @owner_sid = SUSER_SID(@owner_login_name, 0) -- If @owner_login_name is invalid then SUSER_SID() will return NULL
  END

  -- Default the description (if not supplied)
  IF (@description IS NULL)
    SELECT @description = FORMATMESSAGE(14571)

  -- If a category ID is provided this overrides any supplied category name
  EXECUTE @retval = sp_verify_category_identifiers '@category_name',
                                                   '@category_id',
                                                    @category_name OUTPUT,
                                                    @category_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check parameters
  EXECUTE @retval = sp_verify_job NULL,  --  The job id is null since this is a new job
                                  @job_name,
                                  @enabled,
                                  @start_step_id,
                                  @category_name,
                                  @owner_sid                  OUTPUT,
                                  @notify_level_eventlog,
                                  @notify_level_email         OUTPUT,
                                  @notify_level_netsend       OUTPUT,
                                  @notify_level_page          OUTPUT,
                                  @notify_email_operator_name,
                                  @notify_netsend_operator_name,
                                  @notify_page_operator_name,
                                  @delete_level,
                                  @category_id                OUTPUT,
                                  @notify_email_operator_id   OUTPUT,
                                  @notify_netsend_operator_id OUTPUT,
                                  @notify_page_operator_id    OUTPUT,
                                  @originating_server         OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure
    
    
  SELECT @originating_server_id = originating_server_id 
  FROM msdb.dbo.sysoriginatingservers_view 
  WHERE (originating_server = @originating_server)
  IF (@originating_server_id IS NULL)
  BEGIN
    RAISERROR(14370, -1, -1)
    RETURN(1) -- Failure
  END
    

  IF (@job_id IS NULL)
  BEGIN
    -- Assign the GUID
    SELECT @job_id = NEWID()
  END
  ELSE
  BEGIN
    -- A job ID has been provided, so check that the caller is SQLServerAgent (inserting an MSX job)
    IF (PROGRAM_NAME() NOT LIKE N'SQLAgent%')
    BEGIN
      RAISERROR(14274, -1, -1)
      RETURN(1) -- Failure
    END
  END

  INSERT INTO msdb.dbo.sysjobs
         (job_id,
          originating_server_id,
          name,
          enabled,
          description,
          start_step_id,
          category_id,
          owner_sid,
          notify_level_eventlog,
          notify_level_email,
          notify_level_netsend,
          notify_level_page,
          notify_email_operator_id,
          notify_netsend_operator_id,
          notify_page_operator_id,
          delete_level,
          date_created,
          date_modified,
          version_number)
  VALUES  (@job_id,
          @originating_server_id,
          @job_name,
          @enabled,
          @description,
          @start_step_id,
          @category_id,
          @owner_sid,
          @notify_level_eventlog,
          @notify_level_email,
          @notify_level_netsend,
          @notify_level_page,
          @notify_email_operator_id,
          @notify_netsend_operator_id,
          @notify_page_operator_id,
          @delete_level,
          GETDATE(),
          GETDATE(),
          1) -- Version number 1
  SELECT @retval = @@error

  -- NOTE: We don't notify SQLServerAgent to update it's cache (we'll do this in sp_add_jobserver)

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_UPDATE_JOB                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_job
go
CREATE PROCEDURE sp_update_job
  @job_id                       UNIQUEIDENTIFIER = NULL, -- Must provide this or current_name
  @job_name                     sysname          = NULL, -- Must provide this or job_id
  @new_name                     sysname          = NULL,
  @enabled                      TINYINT          = NULL,
  @description                  NVARCHAR(512)    = NULL,
  @start_step_id                INT              = NULL,
  @category_name                sysname          = NULL,
  @owner_login_name             sysname          = NULL,
  @notify_level_eventlog        INT              = NULL,
  @notify_level_email           INT              = NULL,
  @notify_level_netsend         INT              = NULL,
  @notify_level_page            INT              = NULL,
  @notify_email_operator_name   sysname          = NULL,
  @notify_netsend_operator_name sysname          = NULL,
  @notify_page_operator_name    sysname          = NULL,
  @delete_level                 INT              = NULL,
  @automatic_post               BIT              = 1     -- Flag for SEM use only
AS
BEGIN
  DECLARE @retval                        INT
  DECLARE @category_id                   INT
  DECLARE @notify_email_operator_id      INT
  DECLARE @notify_netsend_operator_id    INT
  DECLARE @notify_page_operator_id       INT
  DECLARE @owner_sid                     VARBINARY(85)
  DECLARE @alert_id                      INT
  DECLARE @cached_attribute_modified     INT
  DECLARE @is_sysadmin                   INT
  DECLARE @current_owner                 sysname
  DECLARE @enable_only_used              INT

  DECLARE @x_new_name                    sysname
  DECLARE @x_enabled                     TINYINT
  DECLARE @x_description                 NVARCHAR(512)
  DECLARE @x_start_step_id               INT
  DECLARE @x_category_name               sysname
  DECLARE @x_category_id                 INT
  DECLARE @x_owner_sid                   VARBINARY(85)
  DECLARE @x_notify_level_eventlog       INT
  DECLARE @x_notify_level_email          INT
  DECLARE @x_notify_level_netsend        INT
  DECLARE @x_notify_level_page           INT
  DECLARE @x_notify_email_operator_name  sysname
  DECLARE @x_notify_netsnd_operator_name sysname
  DECLARE @x_notify_page_operator_name   sysname
  DECLARE @x_delete_level                INT
  DECLARE @x_originating_server_id       INT -- Not updatable
  DECLARE @x_master_server               BIT

  -- Remove any leading/trailing spaces from parameters (except @owner_login_name)
  SELECT @job_name                     = LTRIM(RTRIM(@job_name))
  SELECT @new_name                     = LTRIM(RTRIM(@new_name))
  SELECT @description                  = LTRIM(RTRIM(@description))
  SELECT @category_name                = LTRIM(RTRIM(@category_name))
  SELECT @notify_email_operator_name   = LTRIM(RTRIM(@notify_email_operator_name))
  SELECT @notify_netsend_operator_name = LTRIM(RTRIM(@notify_netsend_operator_name))
  SELECT @notify_page_operator_name    = LTRIM(RTRIM(@notify_page_operator_name))

  SET NOCOUNT ON

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Are we modifying an attribute which SQLServerAgent caches?
  IF ((@new_name                     IS NOT NULL) OR
      (@enabled                      IS NOT NULL) OR
      (@start_step_id                IS NOT NULL) OR
      (@owner_login_name             IS NOT NULL) OR
      (@notify_level_eventlog        IS NOT NULL) OR
      (@notify_level_email           IS NOT NULL) OR
      (@notify_level_netsend         IS NOT NULL) OR
      (@notify_level_page            IS NOT NULL) OR
      (@notify_email_operator_name   IS NOT NULL) OR
      (@notify_netsend_operator_name IS NOT NULL) OR
      (@notify_page_operator_name    IS NOT NULL) OR
      (@delete_level                 IS NOT NULL))
    SELECT @cached_attribute_modified = 1
  ELSE
    SELECT @cached_attribute_modified = 0
    
  -- Is @enable the only parameter used beside jobname and jobid?
  IF ((@enabled                   IS NOT NULL) AND
     (@new_name                IS NULL) AND
     (@description                  IS NULL) AND
     (@start_step_id                IS NULL) AND
     (@category_name                IS NULL) AND
     (@owner_login_name             IS NULL) AND
     (@notify_level_eventlog        IS NULL) AND
     (@notify_level_email           IS NULL) AND
     (@notify_level_netsend         IS NULL) AND
     (@notify_level_page            IS NULL) AND
     (@notify_email_operator_name   IS NULL) AND
     (@notify_netsend_operator_name IS NULL) AND
     (@notify_page_operator_name    IS NULL) AND
     (@delete_level                 IS NULL))
    SELECT @enable_only_used = 1
  ELSE
    SELECT @enable_only_used = 0

  -- Set the x_ (existing) variables
  SELECT @x_new_name                    = sjv.name,
         @x_enabled                     = sjv.enabled,
         @x_description                 = sjv.description,
         @x_start_step_id               = sjv.start_step_id,
         @x_category_name               = sc.name,                  -- From syscategories
         @x_category_id                 = sc.category_id,           -- From syscategories
         @x_owner_sid                   = sjv.owner_sid,
         @x_notify_level_eventlog       = sjv.notify_level_eventlog,
         @x_notify_level_email          = sjv.notify_level_email,
         @x_notify_level_netsend        = sjv.notify_level_netsend,
         @x_notify_level_page           = sjv.notify_level_page,
         @x_notify_email_operator_name  = so1.name,                   -- From sysoperators
         @x_notify_netsnd_operator_name = so2.name,                   -- From sysoperators
         @x_notify_page_operator_name   = so3.name,                   -- From sysoperators
         @x_delete_level                = sjv.delete_level,
         @x_originating_server_id       = sjv.originating_server_id,
         @x_master_server               = master_server
  FROM msdb.dbo.sysjobs_view                 sjv
       LEFT OUTER JOIN msdb.dbo.sysoperators so1 ON (sjv.notify_email_operator_id = so1.id)
       LEFT OUTER JOIN msdb.dbo.sysoperators so2 ON (sjv.notify_netsend_operator_id = so2.id)
       LEFT OUTER JOIN msdb.dbo.sysoperators so3 ON (sjv.notify_page_operator_id = so3.id),
       msdb.dbo.syscategories                sc
  WHERE (sjv.job_id = @job_id)
    AND (sjv.category_id = sc.category_id)

  -- Check authority (only SQLServerAgent can modify a non-local job)
  IF ((@x_master_server = 1) AND (PROGRAM_NAME() NOT LIKE N'SQLAgent%') )
  BEGIN
    RAISERROR(14274, -1, -1)
    RETURN(1) -- Failure
  END
  
  -- Check permissions beyond what's checked by the sysjobs_view
  -- SQLAgentReader and SQLAgentOperator roles that can see all jobs
  -- cannot modify jobs they do not own
  IF ( (@x_owner_sid <> SUSER_SID())                  -- does not own the job
      AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)   -- is not sysadmin
      AND (@enable_only_used <> 1 OR ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) <> 1))
  BEGIN
   RAISERROR(14525, -1, -1);
   RETURN(1) -- Failure
  END

  -- Check job category, only sysadmin can modify mutli-server jobs        
  IF (EXISTS (SELECT * FROM msdb.dbo.syscategories WHERE (category_class = 1) -- Job
                                                     AND (category_type = 2) -- Multi-Server
                                                     AND (category_id = @x_category_id)
                                                     AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))) -- is not sysadmin
  BEGIN
     RAISERROR(14396, -1, -1);
     RETURN(1) -- Failure
  END          
            
  IF (@new_name = N'') SELECT @new_name = NULL

  -- Fill out the values for all non-supplied parameters from the existing values
  IF (@new_name                     IS NULL) SELECT @new_name                     = @x_new_name
  IF (@enabled                      IS NULL) SELECT @enabled                      = @x_enabled
  IF (@description                  IS NULL) SELECT @description                  = @x_description
  IF (@start_step_id                IS NULL) SELECT @start_step_id                = @x_start_step_id
  IF (@category_name                IS NULL) SELECT @category_name                = @x_category_name
  IF (@owner_sid                    IS NULL) SELECT @owner_sid                    = @x_owner_sid
  IF (@notify_level_eventlog        IS NULL) SELECT @notify_level_eventlog        = @x_notify_level_eventlog
  IF (@notify_level_email           IS NULL) SELECT @notify_level_email           = @x_notify_level_email
  IF (@notify_level_netsend         IS NULL) SELECT @notify_level_netsend         = @x_notify_level_netsend
  IF (@notify_level_page            IS NULL) SELECT @notify_level_page            = @x_notify_level_page
  IF (@notify_email_operator_name   IS NULL) SELECT @notify_email_operator_name   = @x_notify_email_operator_name
  IF (@notify_netsend_operator_name IS NULL) SELECT @notify_netsend_operator_name = @x_notify_netsnd_operator_name
  IF (@notify_page_operator_name    IS NULL) SELECT @notify_page_operator_name    = @x_notify_page_operator_name
  IF (@delete_level                 IS NULL) SELECT @delete_level                 = @x_delete_level

  -- If the SA is attempting to assign ownership of the job to someone else, then convert
  -- the login name to an ID
  IF (@owner_login_name = N'$(SQLAgentAccount)')  AND 
     (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
  BEGIN
    SELECT @owner_sid = 0xFFFFFFFF   
  END
  ELSE IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) AND (@owner_login_name IS NOT NULL))
  BEGIN
    --force case insensitive comparation for NT users
    SELECT @owner_sid = SUSER_SID(@owner_login_name, 0) -- If @owner_login_name is invalid then SUSER_SID() will return NULL
  END

  -- Only the SA can re-assign jobs
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) AND (@owner_login_name IS NOT NULL))
    RAISERROR(14242, -1, -1)

  -- Ownership of a multi-server job cannot be assigned to a non-sysadmin
  IF (@owner_login_name IS NOT NULL) AND
     (EXISTS (SELECT *
              FROM msdb.dbo.sysjobs       sj,
                   msdb.dbo.sysjobservers sjs
              WHERE (sj.job_id = sjs.job_id)
                AND (sj.job_id = @job_id)
                AND (sjs.server_id <> 0)))
  BEGIN
    IF (@owner_login_name = N'$(SQLAgentAccount)') -- allow distributed jobs to be assigned to special account
    BEGIN
      SELECT @is_sysadmin = 1    
    END
    ELSE
    BEGIN
      SELECT @is_sysadmin = 0
      EXECUTE msdb.dbo.sp_sqlagent_has_server_access @login_name = @owner_login_name, @is_sysadmin_member = @is_sysadmin OUTPUT
    END

    IF (@is_sysadmin = 0)
    BEGIN
      SELECT @current_owner = dbo.SQLAGENT_SUSER_SNAME(@x_owner_sid)
      RAISERROR(14543, -1, -1, @current_owner, N'sysadmin')
      RETURN(1) -- Failure
    END
  END

  -- Turn [nullable] empty string parameters into NULLs
  IF (@description                  = N'') SELECT @description                  = NULL
  IF (@category_name                = N'') SELECT @category_name                = NULL
  IF (@notify_email_operator_name   = N'') SELECT @notify_email_operator_name   = NULL
  IF (@notify_netsend_operator_name = N'') SELECT @notify_netsend_operator_name = NULL
  IF (@notify_page_operator_name    = N'') SELECT @notify_page_operator_name    = NULL

  -- Check new values
  EXECUTE @retval = sp_verify_job @job_id,
                                  @new_name,
                                  @enabled,
                                  @start_step_id,
                                  @category_name,
                                  @owner_sid                  OUTPUT,
                                  @notify_level_eventlog,
                                  @notify_level_email         OUTPUT,
                                  @notify_level_netsend       OUTPUT,
                                  @notify_level_page          OUTPUT,
                                  @notify_email_operator_name,
                                  @notify_netsend_operator_name,
                                  @notify_page_operator_name,
                                  @delete_level,
                                  @category_id                OUTPUT,
                                  @notify_email_operator_id   OUTPUT,
                                  @notify_netsend_operator_id OUTPUT,
                                  @notify_page_operator_id    OUTPUT,
                                  NULL  
  IF (@retval <> 0)
    RETURN(1) -- Failure

  BEGIN TRANSACTION

  -- If the job is being re-assigned, modify sysjobsteps.database_user_name as necessary
  IF (@owner_login_name IS NOT NULL)
  BEGIN
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobsteps
                WHERE (job_id = @job_id)
                  AND (subsystem = N'TSQL')))
    BEGIN
      IF (EXISTS (SELECT *
                  FROM master.dbo.syslogins
                  WHERE (sid = @owner_sid)
                    AND (sysadmin <> 1)))
      BEGIN
        -- The job is being re-assigned to an non-SA
        UPDATE msdb.dbo.sysjobsteps
        SET database_user_name = NULL
        WHERE (job_id = @job_id)
          AND (subsystem = N'TSQL')
      END
    END
  END

  UPDATE msdb.dbo.sysjobs
  SET name                       = @new_name,
      enabled                    = @enabled,
      description                = @description,
      start_step_id              = @start_step_id,
      category_id                = @category_id,              -- Returned from sp_verify_job
      owner_sid                  = @owner_sid,
      notify_level_eventlog      = @notify_level_eventlog,
      notify_level_email         = @notify_level_email,
      notify_level_netsend       = @notify_level_netsend,
      notify_level_page          = @notify_level_page,
      notify_email_operator_id   = @notify_email_operator_id,   -- Returned from sp_verify_job
      notify_netsend_operator_id = @notify_netsend_operator_id, -- Returned from sp_verify_job
      notify_page_operator_id    = @notify_page_operator_id,    -- Returned from sp_verify_job
      delete_level               = @delete_level,
      version_number             = version_number + 1,  -- Update the job's version
      date_modified              = GETDATE()            -- Update the job's last-modified information
  WHERE (job_id = @job_id)
  SELECT @retval = @@error

  COMMIT TRANSACTION

  -- Always re-post the job if it's an auto-delete job (or if we're updating an auto-delete job
  -- to be non-auto-delete)
  IF (((SELECT delete_level
        FROM msdb.dbo.sysjobs
        WHERE (job_id = @job_id)) <> 0) OR
      ((@x_delete_level = 1) AND (@delete_level = 0)))
    EXECUTE msdb.dbo.sp_post_msx_operation 'INSERT', 'JOB', @job_id
  ELSE
  BEGIN
    -- Post the update to target servers
    IF (@automatic_post = 1)
      EXECUTE msdb.dbo.sp_post_msx_operation 'UPDATE', 'JOB', @job_id
  END

  -- Keep SQLServerAgent's cache in-sync
  -- NOTE: We only notify SQLServerAgent if we know the job has been cached and if
  --       attributes other than description or category have been changed (since
  --       SQLServerAgent doesn't cache these two)
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id = 0)
                AND (@cached_attribute_modified = 1)))
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                        @job_id      = @job_id,
                                        @action_type = N'U'

  -- If the name was changed, make SQLServerAgent re-cache any alerts that reference the job
  -- since the alert cache contains the job name
  IF ((@job_name <> @new_name) AND (EXISTS (SELECT *
                                            FROM msdb.dbo.sysalerts
                                            WHERE (job_id = @job_id))))
  BEGIN
    DECLARE sysalerts_cache_update CURSOR LOCAL
    FOR
    SELECT id
    FROM msdb.dbo.sysalerts
    WHERE (job_id = @job_id)

    OPEN sysalerts_cache_update
    FETCH NEXT FROM sysalerts_cache_update INTO @alert_id

    WHILE (@@fetch_status = 0)
    BEGIN
      EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'A',
                                          @alert_id    = @alert_id,
                                          @action_type = N'U'
      FETCH NEXT FROM sysalerts_cache_update INTO @alert_id
    END
    DEALLOCATE sysalerts_cache_update
  END

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_JOB                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_job
go
CREATE PROCEDURE sp_delete_job
  @job_id               UNIQUEIDENTIFIER = NULL, -- If provided should NOT also provide job_name
  @job_name             sysname          = NULL, -- If provided should NOT also provide job_id
  @originating_server      sysname         = NULL, -- Reserved (used by SQLAgent)
  @delete_history       BIT              = 1,    -- Reserved (used by SQLAgent)
  @delete_unused_schedule   BIT              = 1     -- For backward compatibility schedules are deleted by default if they are not 
                                        -- being used by another job. With the introduction of reusable schedules in V9 
                                        -- callers should set this to 0 so the schedule will be preserved for reuse.
AS
BEGIN
  DECLARE @current_msx_server sysname
  DECLARE @bMSX_job           BIT
  DECLARE @retval             INT
  DECLARE @local_machine_name sysname
  DECLARE @category_id        INT
  DECLARE @job_owner_sid      VARBINARY(85)
  
  SET NOCOUNT ON
  -- Remove any leading/trailing spaces from parameters
  SELECT @originating_server = UPPER(LTRIM(RTRIM(@originating_server)))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@originating_server = N'') SELECT @originating_server = NULL

  -- Change server name to always reflect real servername or servername\instancename
  IF (@originating_server IS NOT NULL AND @originating_server = '(LOCAL)')
    SELECT @originating_server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT,
                                                 @owner_sid = @job_owner_sid OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

  END

  -- We need either a job name or a server name, not both
  IF ((@job_name IS NULL)     AND (@originating_server IS NULL)) OR
     ((@job_name IS NOT NULL) AND (@originating_server IS NOT NULL))
  BEGIN
    RAISERROR(14279, -1, -1)
    RETURN(1) -- Failure
  END

  -- Get category to see if it is a misc. replication agent. @category_id will be
  -- NULL if there is no @job_id.
  select @category_id = category_id from msdb.dbo.sysjobs where job_id = @job_id

  -- If job name was given, determine if the job is from an MSX
  IF (@job_id IS NOT NULL)
  BEGIN
    SELECT @bMSX_job = CASE UPPER(originating_server)
                         WHEN UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))) THEN 0
                         ELSE 1
                       END
    FROM msdb.dbo.sysjobs_view
    WHERE (job_id = @job_id)
  END

  -- If server name was given, warn user if different from current MSX
  IF (@originating_server IS NOT NULL)
  BEGIN
    EXECUTE @retval = master.dbo.xp_getnetname @local_machine_name OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

    IF ((@originating_server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))) OR (@originating_server = UPPER(@local_machine_name)))
      SELECT @originating_server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'MSXServerName',
                                           @current_msx_server OUTPUT,
                                           N'no_output'

    SELECT @current_msx_server = UPPER(@current_msx_server)
    -- If server name was given but it's not the current MSX, print a warning
    SELECT @current_msx_server = LTRIM(RTRIM(@current_msx_server))
    IF ((@current_msx_server IS NOT NULL) AND (@current_msx_server <> N'') AND (@originating_server <> @current_msx_server))
      RAISERROR(14224, 0, 1, @current_msx_server)
  END

  -- Check authority (only SQLServerAgent can delete a non-local job)
  IF (((@originating_server IS NOT NULL) AND (@originating_server <> UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))))) OR (@bMSX_job = 1)) AND
     (PROGRAM_NAME() NOT LIKE N'SQLAgent%')
  BEGIN
    RAISERROR(14274, -1, -1)
    RETURN(1) -- Failure
  END
  
  -- Check permissions beyond what's checked by the sysjobs_view
  -- SQLAgentReader and SQLAgentOperator roles that can see all jobs
  -- cannot delete jobs they do not own
  IF (@job_id IS NOT NULL)
  BEGIN
   IF (@job_owner_sid <> SUSER_SID()                     -- does not own the job
       AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)) -- is not sysadmin
   BEGIN
     RAISERROR(14525, -1, -1);
     RETURN(1) -- Failure
    END
  END

  -- Do the delete (for a specific job)
  IF (@job_id IS NOT NULL)
  BEGIN
    -- Note: This temp table is referenced by msdb.dbo.sp_delete_job_references,
    -- so it cannot be declared as a local table.
    CREATE TABLE #temp_jobs_to_delete (job_id UNIQUEIDENTIFIER NOT NULL PRIMARY KEY CLUSTERED,
                                       job_is_cached INT NOT NULL)

    DECLARE @temp_schedules_to_delete TABLE (schedule_id INT NOT NULL)  

    INSERT INTO #temp_jobs_to_delete
    SELECT job_id, (SELECT COUNT(*)
                    FROM msdb.dbo.sysjobservers
                    WHERE (job_id = @job_id)
                      AND (server_id = 0))
    FROM msdb.dbo.sysjobs_view
    WHERE (job_id = @job_id)

    -- Check if we have any work to do
    IF (NOT EXISTS (SELECT *
                    FROM #temp_jobs_to_delete))
    BEGIN
      DROP TABLE #temp_jobs_to_delete
      RETURN(0) -- Success
    END

    -- Post the delete to any target servers (need to do this BEFORE
    -- deleting the job itself, but AFTER clearing all all pending
    -- download instructions).  Note that if the job is NOT a
    -- multi-server job then sp_post_msx_operation will catch this and
    -- will do nothing. Since it will do nothing that is why we need
    -- to NOT delete any pending delete requests, because that delete
    -- request might have been for the last target server and thus
    -- this job isn't a multi-server job anymore so posting the global
    -- delete would do nothing.
    DELETE FROM msdb.dbo.sysdownloadlist
    WHERE (object_id = @job_id)
      and (operation_code != 3) -- Delete
    EXECUTE msdb.dbo.sp_post_msx_operation 'DELETE', 'JOB', @job_id


    -- Must do this before deleting the job itself since sp_sqlagent_notify does a lookup on sysjobs_view
    -- Note: Don't notify agent in this call. It is done after the transaction is committed
    --       just in case this job is in the process of deleting itself
    EXECUTE msdb.dbo.sp_delete_job_references @notify_sqlagent = 0

    -- Delete all traces of the job
    BEGIN TRANSACTION

    DECLARE @err int

   --Get the schedules to delete before deleting records from sysjobschedules
    IF(@delete_unused_schedule = 1)
    BEGIN
        --Get the list of schedules to delete
        INSERT INTO @temp_schedules_to_delete
        SELECT DISTINCT schedule_id 
        FROM   msdb.dbo.sysschedules
        WHERE (schedule_id IN 
                (SELECT schedule_id
                FROM msdb.dbo.sysjobschedules
                WHERE (job_id = @job_id)))
    END


    DELETE FROM msdb.dbo.sysjobschedules
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)
    
    DELETE FROM msdb.dbo.sysjobservers
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

    DELETE FROM msdb.dbo.sysjobsteps
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

    DELETE FROM msdb.dbo.sysjobs
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)
    SELECT @err = @@ERROR
 
    IF @err <> 0
    BEGIN
    ROLLBACK TRANSACTION
    RETURN @err
    END

    
    --Delete the schedule(s) if requested to and it isn't being used by other jobs
    IF(@delete_unused_schedule = 1)
    BEGIN
      --Now OK to delete the schedule
      DELETE FROM msdb.dbo.sysschedules
      WHERE schedule_id IN 
        (SELECT schedule_id
         FROM @temp_schedules_to_delete as sdel
         WHERE NOT EXISTS(SELECT * 
                          FROM msdb.dbo.sysjobschedules AS js
                          WHERE (js.schedule_id = sdel.schedule_id)))
    END


    -- Delete the job history if requested    
    IF (@delete_history = 1)
    BEGIN
      DELETE FROM msdb.dbo.sysjobhistory
      WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)
    END
    -- All done
    COMMIT TRANSACTION

    -- Now notify agent to delete the job.
    IF(EXISTS(SELECT * FROM #temp_jobs_to_delete WHERE job_is_cached > 0))
    BEGIN
      DECLARE @nt_user_name   NVARCHAR(100)
      SELECT @nt_user_name = ISNULL(NT_CLIENT(), ISNULL(SUSER_SNAME(), FORMATMESSAGE(14205)))
      --Call the xp directly. sp_sqlagent_notify checks sysjobs_view and the record has already been deleted
      EXEC master.dbo.xp_sqlagent_notify N'J', @job_id, 0, 0, N'D', @nt_user_name, 1, @@trancount, NULL, NULL
    END

  END
  ELSE
  -- Do the delete (for all jobs originating from the specific server)
  IF (@originating_server IS NOT NULL)
  BEGIN
    EXECUTE msdb.dbo.sp_delete_all_msx_jobs @msx_server = @originating_server

    -- NOTE: In this case there is no need to propagate the delete via sp_post_msx_operation
    --       since this type of delete is only ever performed on a TSX.
  END

  IF (OBJECT_ID(N'tempdb.dbo.#temp_jobs_to_delete', 'U') IS NOT NULL)    
    DROP TABLE #temp_jobs_to_delete

  RETURN(0) -- 0 means success
END
go


/**************************************************************/
/* SP_GET_COMPOSITE_JOB_INFO                                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_get_composite_job_info...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_get_composite_job_info')
              AND (type = 'P')))
  DROP PROCEDURE sp_get_composite_job_info
go
CREATE PROCEDURE sp_get_composite_job_info
  @job_id             UNIQUEIDENTIFIER = NULL,
  @job_type           VARCHAR(12)      = NULL,  -- LOCAL or MULTI-SERVER
  @owner_login_name   sysname          = NULL,
  @subsystem          NVARCHAR(40)     = NULL,
  @category_id        INT              = NULL,
  @enabled            TINYINT          = NULL,
  @execution_status   INT              = NULL,  -- 0 = Not idle or suspended, 1 = Executing, 2 = Waiting For Thread, 3 = Between Retries, 4 = Idle, 5 = Suspended, [6 = WaitingForStepToFinish], 7 = PerformingCompletionActions
  @date_comparator    CHAR(1)          = NULL,  -- >, < or =
  @date_created       DATETIME         = NULL,
  @date_last_modified DATETIME         = NULL,
  @description        NVARCHAR(512)    = NULL,  -- We do a LIKE on this so it can include wildcards
  @schedule_id        INT              = NULL   -- if supplied only return the jobs that use this schedule
AS
BEGIN
  DECLARE @can_see_all_running_jobs INT
  DECLARE @job_owner   sysname

  SET NOCOUNT ON

  -- By 'composite' we mean a combination of sysjobs and xp_sqlagent_enum_jobs data.
  -- This proc should only ever be called by sp_help_job, so we don't verify the
  -- parameters (sp_help_job has already done this).

  -- Step 1: Create intermediate work tables
  DECLARE @job_execution_state TABLE (job_id                  UNIQUEIDENTIFIER NOT NULL,
                                     date_started            INT              NOT NULL,
                                     time_started            INT              NOT NULL,
                                     execution_job_status    INT              NOT NULL,
                                     execution_step_id       INT              NULL,
                                     execution_step_name     sysname          COLLATE database_default NULL,
                                     execution_retry_attempt INT              NOT NULL,
                                     next_run_date           INT              NOT NULL,
                                     next_run_time           INT              NOT NULL,
                                     next_run_schedule_id    INT              NOT NULL)
  DECLARE @filtered_jobs TABLE (job_id                   UNIQUEIDENTIFIER NOT NULL,
                               date_created             DATETIME         NOT NULL,
                               date_last_modified       DATETIME         NOT NULL,
                               current_execution_status INT              NULL,
                               current_execution_step   sysname          COLLATE database_default NULL,
                               current_retry_attempt    INT              NULL,
                               last_run_date            INT              NOT NULL,
                               last_run_time            INT              NOT NULL,
                               last_run_outcome         INT              NOT NULL,
                               next_run_date            INT              NULL,
                               next_run_time            INT              NULL,
                               next_run_schedule_id     INT              NULL,
                               type                     INT              NOT NULL)
  DECLARE @xp_results TABLE (job_id                UNIQUEIDENTIFIER NOT NULL,
                            last_run_date         INT              NOT NULL,
                            last_run_time         INT              NOT NULL,
                            next_run_date         INT              NOT NULL,
                            next_run_time         INT              NOT NULL,
                            next_run_schedule_id  INT              NOT NULL,
                            requested_to_run      INT              NOT NULL, -- BOOL
                            request_source        INT              NOT NULL,
                            request_source_id     sysname          COLLATE database_default NULL,
                            running               INT              NOT NULL, -- BOOL
                            current_step          INT              NOT NULL,
                            current_retry_attempt INT              NOT NULL,
                            job_state             INT              NOT NULL)

  -- Step 2: Capture job execution information (for local jobs only since that's all SQLServerAgent caches)
  SELECT @can_see_all_running_jobs = ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0)
  IF (@can_see_all_running_jobs = 0)
  BEGIN
    SELECT @can_see_all_running_jobs = ISNULL(IS_MEMBER(N'SQLAgentReaderRole'), 0)
  END
  SELECT @job_owner = SUSER_SNAME()

  IF ((@@microsoftversion / 0x01000000) >= 8) -- SQL Server 8.0 or greater
    INSERT INTO @xp_results
    EXECUTE master.dbo.xp_sqlagent_enum_jobs @can_see_all_running_jobs, @job_owner, @job_id
  ELSE
    INSERT INTO @xp_results
    EXECUTE master.dbo.xp_sqlagent_enum_jobs @can_see_all_running_jobs, @job_owner

  INSERT INTO @job_execution_state
  SELECT xpr.job_id,
         xpr.last_run_date,
         xpr.last_run_time,
         xpr.job_state,
         sjs.step_id,
         sjs.step_name,
         xpr.current_retry_attempt,
         xpr.next_run_date,
         xpr.next_run_time,
         xpr.next_run_schedule_id
  FROM @xp_results                          xpr
       LEFT OUTER JOIN msdb.dbo.sysjobsteps sjs ON ((xpr.job_id = sjs.job_id) AND (xpr.current_step = sjs.step_id)),
       msdb.dbo.sysjobs_view                sjv
  WHERE (sjv.job_id = xpr.job_id)

  -- Step 3: Filter on everything but dates and job_type
  IF ((@subsystem        IS NULL) AND
      (@owner_login_name IS NULL) AND
      (@enabled          IS NULL) AND
      (@category_id      IS NULL) AND
      (@execution_status IS NULL) AND
      (@description      IS NULL) AND
      (@job_id           IS NULL))
  BEGIN
    -- Optimize for the frequently used case...
    INSERT INTO @filtered_jobs
    SELECT sjv.job_id,
           sjv.date_created,
           sjv.date_modified,
           ISNULL(jes.execution_job_status, 4), -- Will be NULL if the job is non-local or is not in @job_execution_state (NOTE: 4 = STATE_IDLE)
           CASE ISNULL(jes.execution_step_id, 0)
             WHEN 0 THEN NULL                   -- Will be NULL if the job is non-local or is not in @job_execution_state
             ELSE CONVERT(NVARCHAR, jes.execution_step_id) + N' (' + jes.execution_step_name + N')'
           END,
           jes.execution_retry_attempt,         -- Will be NULL if the job is non-local or is not in @job_execution_state
           0,  -- last_run_date placeholder    (we'll fix it up in step 3.3)
           0,  -- last_run_time placeholder    (we'll fix it up in step 3.3)
           5,  -- last_run_outcome placeholder (we'll fix it up in step 3.3 - NOTE: We use 5 just in case there are no jobservers for the job)
           jes.next_run_date,                   -- Will be NULL if the job is non-local or is not in @job_execution_state
           jes.next_run_time,                   -- Will be NULL if the job is non-local or is not in @job_execution_state
           jes.next_run_schedule_id,            -- Will be NULL if the job is non-local or is not in @job_execution_state
           0   -- type placeholder             (we'll fix it up in step 3.4)
    FROM msdb.dbo.sysjobs_view                sjv
         LEFT OUTER JOIN @job_execution_state jes ON (sjv.job_id = jes.job_id)
    WHERE ((@schedule_id IS NULL)
      OR   (EXISTS(SELECT * 
                 FROM sysjobschedules as js
                 WHERE (sjv.job_id = js.job_id)
                   AND (js.schedule_id = @schedule_id))))
  END
  ELSE
  BEGIN
    INSERT INTO @filtered_jobs
    SELECT DISTINCT
           sjv.job_id,
           sjv.date_created,
           sjv.date_modified,
           ISNULL(jes.execution_job_status, 4), -- Will be NULL if the job is non-local or is not in @job_execution_state (NOTE: 4 = STATE_IDLE)
           CASE ISNULL(jes.execution_step_id, 0)
             WHEN 0 THEN NULL                   -- Will be NULL if the job is non-local or is not in @job_execution_state
             ELSE CONVERT(NVARCHAR, jes.execution_step_id) + N' (' + jes.execution_step_name + N')'
           END,
           jes.execution_retry_attempt,         -- Will be NULL if the job is non-local or is not in @job_execution_state
           0,  -- last_run_date placeholder    (we'll fix it up in step 3.3)
           0,  -- last_run_time placeholder    (we'll fix it up in step 3.3)
           5,  -- last_run_outcome placeholder (we'll fix it up in step 3.3 - NOTE: We use 5 just in case there are no jobservers for the job)
           jes.next_run_date,                   -- Will be NULL if the job is non-local or is not in @job_execution_state
           jes.next_run_time,                   -- Will be NULL if the job is non-local or is not in @job_execution_state
           jes.next_run_schedule_id,            -- Will be NULL if the job is non-local or is not in @job_execution_state
           0   -- type placeholder             (we'll fix it up in step 3.4)
    FROM msdb.dbo.sysjobs_view                sjv
         LEFT OUTER JOIN @job_execution_state jes ON (sjv.job_id = jes.job_id)
         LEFT OUTER JOIN msdb.dbo.sysjobsteps sjs ON (sjv.job_id = sjs.job_id)
    WHERE ((@subsystem        IS NULL) OR (sjs.subsystem            = @subsystem))
      AND ((@owner_login_name IS NULL) 
          OR (sjv.owner_sid            = dbo.SQLAGENT_SUSER_SID(@owner_login_name)))--force case insensitive comparation for NT users
      AND ((@enabled          IS NULL) OR (sjv.enabled              = @enabled))
      AND ((@category_id      IS NULL) OR (sjv.category_id          = @category_id))
      AND ((@execution_status IS NULL) OR ((@execution_status > 0) AND (jes.execution_job_status = @execution_status))
                                       OR ((@execution_status = 0) AND (jes.execution_job_status <> 4) AND (jes.execution_job_status <> 5)))
      AND ((@description      IS NULL) OR (sjv.description       LIKE @description))
      AND ((@job_id           IS NULL) OR (sjv.job_id               = @job_id))
      AND ((@schedule_id IS NULL)
        OR (EXISTS(SELECT * 
                 FROM sysjobschedules as js
                 WHERE (sjv.job_id = js.job_id)
                   AND (js.schedule_id = @schedule_id))))
  END

  -- Step 3.1: Change the execution status of non-local jobs from 'Idle' to 'Unknown'
  UPDATE @filtered_jobs
  SET current_execution_status = NULL
  WHERE (current_execution_status = 4)
    AND (job_id IN (SELECT job_id
                    FROM msdb.dbo.sysjobservers
                    WHERE (server_id <> 0)))

  -- Step 3.2: Check that if the user asked to see idle jobs that we still have some.
  --           If we don't have any then the query should return no rows.
  IF (@execution_status = 4) AND
     (NOT EXISTS (SELECT *
                  FROM @filtered_jobs
                  WHERE (current_execution_status = 4)))
  BEGIN
    DELETE FROM @filtered_jobs
  END

  -- Step 3.3: Populate the last run date/time/outcome [this is a little tricky since for
  --           multi-server jobs there are multiple last run details in sysjobservers, so
  --           we simply choose the most recent].
  IF (EXISTS (SELECT *
              FROM msdb.dbo.systargetservers))
  BEGIN
    UPDATE @filtered_jobs
    SET last_run_date = sjs.last_run_date,
        last_run_time = sjs.last_run_time,
        last_run_outcome = sjs.last_run_outcome
    FROM @filtered_jobs         fj,
         msdb.dbo.sysjobservers sjs
    WHERE (CONVERT(FLOAT, sjs.last_run_date) * 1000000) + sjs.last_run_time =
           (SELECT MAX((CONVERT(FLOAT, last_run_date) * 1000000) + last_run_time)
            FROM msdb.dbo.sysjobservers
            WHERE (job_id = sjs.job_id))
      AND (fj.job_id = sjs.job_id)
  END
  ELSE
  BEGIN
    UPDATE @filtered_jobs
    SET last_run_date = sjs.last_run_date,
        last_run_time = sjs.last_run_time,
        last_run_outcome = sjs.last_run_outcome
    FROM @filtered_jobs         fj,
         msdb.dbo.sysjobservers sjs
    WHERE (fj.job_id = sjs.job_id)
  END

  -- Step 3.4 : Set the type of the job to local (1) or multi-server (2)
  --            NOTE: If the job has no jobservers then it wil have a type of 0 meaning
  --                  unknown.  This is marginally inconsistent with the behaviour of
  --                  defaulting the category of a new job to [Uncategorized (Local)], but
  --                  prevents incompletely defined jobs from erroneously showing up as valid
  --                  local jobs.
  UPDATE @filtered_jobs
  SET type = 1 -- LOCAL
  FROM @filtered_jobs         fj,
       msdb.dbo.sysjobservers sjs
  WHERE (fj.job_id = sjs.job_id)
    AND (server_id = 0)
  UPDATE @filtered_jobs
  SET type = 2 -- MULTI-SERVER
  FROM @filtered_jobs         fj,
       msdb.dbo.sysjobservers sjs
  WHERE (fj.job_id = sjs.job_id)
    AND (server_id <> 0)

  -- Step 4: Filter on job_type
  IF (@job_type IS NOT NULL)
  BEGIN
    IF (UPPER(@job_type collate SQL_Latin1_General_CP1_CS_AS) = 'LOCAL')
      DELETE FROM @filtered_jobs
      WHERE (type <> 1) -- IE. Delete all the non-local jobs
    IF (UPPER(@job_type collate SQL_Latin1_General_CP1_CS_AS) = 'MULTI-SERVER')
      DELETE FROM @filtered_jobs
      WHERE (type <> 2) -- IE. Delete all the non-multi-server jobs
  END

  -- Step 5: Filter on dates
  IF (@date_comparator IS NOT NULL)
  BEGIN
    IF (@date_created IS NOT NULL)
    BEGIN
      IF (@date_comparator = '=')
        DELETE FROM @filtered_jobs WHERE (date_created <> @date_created)
      IF (@date_comparator = '>')
        DELETE FROM @filtered_jobs WHERE (date_created <= @date_created)
      IF (@date_comparator = '<')
        DELETE FROM @filtered_jobs WHERE (date_created >= @date_created)
    END
    IF (@date_last_modified IS NOT NULL)
    BEGIN
      IF (@date_comparator = '=')
        DELETE FROM @filtered_jobs WHERE (date_last_modified <> @date_last_modified)
      IF (@date_comparator = '>')
        DELETE FROM @filtered_jobs WHERE (date_last_modified <= @date_last_modified)
      IF (@date_comparator = '<')
        DELETE FROM @filtered_jobs WHERE (date_last_modified >= @date_last_modified)
    END
  END

  -- Return the result set (NOTE: No filtering occurs here)
  SELECT sjv.job_id,
         originating_server, 
         sjv.name,
         sjv.enabled,
         sjv.description,
         sjv.start_step_id,
         category = ISNULL(sc.name, FORMATMESSAGE(14205)),
         owner = dbo.SQLAGENT_SUSER_SNAME(sjv.owner_sid),
         sjv.notify_level_eventlog,
         sjv.notify_level_email,
         sjv.notify_level_netsend,
         sjv.notify_level_page,
         notify_email_operator   = ISNULL(so1.name, FORMATMESSAGE(14205)),
         notify_netsend_operator = ISNULL(so2.name, FORMATMESSAGE(14205)),
         notify_page_operator    = ISNULL(so3.name, FORMATMESSAGE(14205)),
         sjv.delete_level,
         sjv.date_created,
         sjv.date_modified,
         sjv.version_number,
         fj.last_run_date,
         fj.last_run_time,
         fj.last_run_outcome,
         next_run_date = ISNULL(fj.next_run_date, 0),                                 -- This column will be NULL if the job is non-local
         next_run_time = ISNULL(fj.next_run_time, 0),                                 -- This column will be NULL if the job is non-local
         next_run_schedule_id = ISNULL(fj.next_run_schedule_id, 0),                   -- This column will be NULL if the job is non-local
         current_execution_status = ISNULL(fj.current_execution_status, 0),           -- This column will be NULL if the job is non-local
         current_execution_step = ISNULL(fj.current_execution_step, N'0 ' + FORMATMESSAGE(14205)), -- This column will be NULL if the job is non-local
         current_retry_attempt = ISNULL(fj.current_retry_attempt, 0),                 -- This column will be NULL if the job is non-local
         has_step = (SELECT COUNT(*)
                     FROM msdb.dbo.sysjobsteps sjst
                     WHERE (sjst.job_id = sjv.job_id)),
         has_schedule = (SELECT COUNT(*)
                         FROM msdb.dbo.sysjobschedules sjsch
                         WHERE (sjsch.job_id = sjv.job_id)),
         has_target = (SELECT COUNT(*)
                       FROM msdb.dbo.sysjobservers sjs
                       WHERE (sjs.job_id = sjv.job_id)),
         type = fj.type
  FROM @filtered_jobs                         fj
       LEFT OUTER JOIN msdb.dbo.sysjobs_view  sjv ON (fj.job_id = sjv.job_id)
       LEFT OUTER JOIN msdb.dbo.sysoperators  so1 ON (sjv.notify_email_operator_id = so1.id)
       LEFT OUTER JOIN msdb.dbo.sysoperators  so2 ON (sjv.notify_netsend_operator_id = so2.id)
       LEFT OUTER JOIN msdb.dbo.sysoperators  so3 ON (sjv.notify_page_operator_id = so3.id)
       LEFT OUTER JOIN msdb.dbo.syscategories sc  ON (sjv.category_id = sc.category_id)
  ORDER BY sjv.job_id

END
go


/**************************************************************/
/* SP_HELP_JOB                                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_job
go
CREATE PROCEDURE sp_help_job
  -- Individual job parameters
  @job_id                     UNIQUEIDENTIFIER = NULL,  -- If provided should NOT also provide job_name
  @job_name                   sysname          = NULL,  -- If provided should NOT also provide job_id
  @job_aspect                 VARCHAR(9)       = NULL,  -- JOB, STEPS, SCHEDULES, TARGETS or ALL
  -- Job set parameters
  @job_type                   VARCHAR(12)      = NULL,  -- LOCAL or MULTI-SERVER
  @owner_login_name           sysname          = NULL,
  @subsystem                  NVARCHAR(40)     = NULL,
  @category_name              sysname          = NULL,
  @enabled                    TINYINT          = NULL,
  @execution_status           INT              = NULL,  -- 1 = Executing, 2 = Waiting For Thread, 3 = Between Retries, 4 = Idle, 5 = Suspended, 6 = [obsolete], 7 = PerformingCompletionActions
  @date_comparator            CHAR(1)          = NULL,  -- >, < or =
  @date_created               DATETIME         = NULL,
  @date_last_modified         DATETIME         = NULL,
  @description                NVARCHAR(512)    = NULL   -- We do a LIKE on this so it can include wildcards
AS
BEGIN
  DECLARE @retval          INT
  DECLARE @category_id     INT
  DECLARE @job_id_as_char  VARCHAR(36)
  DECLARE @res_valid_range NVARCHAR(200)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters (except @owner_login_name)
  SELECT @job_name         = LTRIM(RTRIM(@job_name))
  SELECT @job_aspect       = LTRIM(RTRIM(@job_aspect))
  SELECT @job_type         = LTRIM(RTRIM(@job_type))
  SELECT @subsystem        = LTRIM(RTRIM(@subsystem))
  SELECT @category_name    = LTRIM(RTRIM(@category_name))
  SELECT @description      = LTRIM(RTRIM(@description))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@job_name         = N'') SELECT @job_name = NULL
  IF (@job_aspect       = '')  SELECT @job_aspect = NULL
  IF (@job_type         = '')  SELECT @job_type = NULL
  IF (@owner_login_name = N'') SELECT @owner_login_name = NULL
  IF (@subsystem        = N'') SELECT @subsystem = NULL
  IF (@category_name    = N'') SELECT @category_name = NULL
  IF (@description      = N'') SELECT @description = NULL

  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)

  -- If the user provided a job name or id but no aspect, default to ALL
  IF ((@job_name IS NOT NULL) OR (@job_id IS NOT NULL)) AND (@job_aspect IS NULL)
    SELECT @job_aspect = 'ALL'

  -- The caller must supply EITHER job name (or job id) and aspect OR one-or-more of the set
  -- parameters OR no parameters at all
  IF (((@job_name IS NOT NULL) OR (@job_id IS NOT NULL))
      AND ((@job_aspect          IS NULL)     OR
           (@job_type            IS NOT NULL) OR
           (@owner_login_name    IS NOT NULL) OR
           (@subsystem           IS NOT NULL) OR
           (@category_name       IS NOT NULL) OR
           (@enabled             IS NOT NULL) OR
           (@date_comparator     IS NOT NULL) OR
           (@date_created        IS NOT NULL) OR
           (@date_last_modified  IS NOT NULL)))
     OR
     ((@job_name IS NULL) AND (@job_id IS NULL) AND (@job_aspect IS NOT NULL))
  BEGIN
    RAISERROR(14280, -1, -1)
    RETURN(1) -- Failure
  END

  IF (@job_id IS NOT NULL)
  BEGIN
    -- Individual job...

    -- Check job aspect
    SELECT @job_aspect = UPPER(@job_aspect collate SQL_Latin1_General_CP1_CS_AS)
    IF (@job_aspect NOT IN ('JOB', 'STEPS', 'SCHEDULES', 'TARGETS', 'ALL'))
    BEGIN
      RAISERROR(14266, -1, -1, '@job_aspect', 'JOB, STEPS, SCHEDULES, TARGETS, ALL')
      RETURN(1) -- Failure
    END

    -- Generate results set...

    IF (@job_aspect IN ('JOB', 'ALL'))
    BEGIN
      IF (@job_aspect = 'ALL')
      BEGIN
        RAISERROR(14213, 0, 1)
        PRINT REPLICATE('=', DATALENGTH(FORMATMESSAGE(14213)) / 2)
      END
      EXECUTE sp_get_composite_job_info @job_id,
                                        @job_type,
                                        @owner_login_name,
                                        @subsystem,
                                        @category_id,
                                        @enabled,
                                        @execution_status,
                                        @date_comparator,
                                        @date_created,
                                        @date_last_modified,
                                        @description
    END

    IF (@job_aspect IN ('STEPS', 'ALL'))
    BEGIN
      IF (@job_aspect = 'ALL')
      BEGIN
        PRINT ''
        RAISERROR(14214, 0, 1)
        PRINT REPLICATE('=', DATALENGTH(FORMATMESSAGE(14214)) / 2)
      END
      EXECUTE ('EXECUTE sp_help_jobstep @job_id = ''' + @job_id_as_char + ''', @suffix = 1')
    END

    IF (@job_aspect IN ('SCHEDULES', 'ALL'))
    BEGIN
      IF (@job_aspect = 'ALL')
      BEGIN
        PRINT ''
        RAISERROR(14215, 0, 1)
        PRINT REPLICATE('=', DATALENGTH(FORMATMESSAGE(14215)) / 2)
      END
      EXECUTE ('EXECUTE sp_help_jobschedule @job_id = ''' + @job_id_as_char + '''')
    END

    IF (@job_aspect IN ('TARGETS', 'ALL'))
    BEGIN
      IF (@job_aspect = 'ALL')
      BEGIN
        PRINT ''
        RAISERROR(14216, 0, 1)
        PRINT REPLICATE('=', DATALENGTH(FORMATMESSAGE(14216)) / 2)
      END
      EXECUTE ('EXECUTE sp_help_jobserver @job_id = ''' + @job_id_as_char + ''', @show_last_run_details = 1')
    END
  END
  ELSE
  BEGIN
    -- Set of jobs...

    -- Check job type
    IF (@job_type IS NOT NULL)
    BEGIN
      SELECT @job_type = UPPER(@job_type collate SQL_Latin1_General_CP1_CS_AS)
      IF (@job_type NOT IN ('LOCAL', 'MULTI-SERVER'))
      BEGIN
        RAISERROR(14266, -1, -1, '@job_type', 'LOCAL, MULTI-SERVER')
        RETURN(1) -- Failure
      END
    END

    -- Check owner
    IF (@owner_login_name IS NOT NULL)
    BEGIN
      IF (SUSER_SID(@owner_login_name, 0) IS NULL)--force case insensitive comparation for NT users
      BEGIN
        RAISERROR(14262, -1, -1, '@owner_login_name', @owner_login_name)
        RETURN(1) -- Failure
      END
    END

    -- Check subsystem
    IF (@subsystem IS NOT NULL)
    BEGIN
      EXECUTE @retval = sp_verify_subsystem @subsystem
      IF (@retval <> 0)
        RETURN(1) -- Failure
    END

    -- Check job category
    IF (@category_name IS NOT NULL)
    BEGIN
      SELECT @category_id = category_id
      FROM msdb.dbo.syscategories
      WHERE (category_class = 1) -- Job
        AND (name = @category_name)
      IF (@category_id IS NULL)
      BEGIN
        RAISERROR(14262, -1, -1, '@category_name', @category_name)
        RETURN(1) -- Failure
      END
    END

    -- Check enabled state
    IF (@enabled IS NOT NULL) AND (@enabled NOT IN (0, 1))
    BEGIN
      RAISERROR(14266, -1, -1, '@enabled', '0, 1')
      RETURN(1) -- Failure
    END

    -- Check current execution status
    IF (@execution_status IS NOT NULL)
    BEGIN
      IF (@execution_status NOT IN (0, 1, 2, 3, 4, 5, 7))
      BEGIN
        SELECT @res_valid_range = FORMATMESSAGE(14204)
        RAISERROR(14266, -1, -1, '@execution_status', @res_valid_range)
        RETURN(1) -- Failure
      END
    END

    -- If a date comparator is supplied, we must have either a date-created or date-last-modified
    IF ((@date_comparator IS NOT NULL) AND (@date_created IS NOT NULL) AND (@date_last_modified IS NOT NULL)) OR
       ((@date_comparator IS NULL)     AND ((@date_created IS NOT NULL) OR (@date_last_modified IS NOT NULL)))
    BEGIN
      RAISERROR(14282, -1, -1)
      RETURN(1) -- Failure
    END

    -- Check dates / comparator
    IF (@date_comparator IS NOT NULL) AND (@date_comparator NOT IN ('=', '<', '>'))
    BEGIN
      RAISERROR(14266, -1, -1, '@date_comparator', '=, >, <')
      RETURN(1) -- Failure
    END
    IF (@date_created IS NOT NULL) AND
       ((@date_created < '19900101') OR (@date_created > '99991231 23:59'))
    BEGIN
      RAISERROR(14266, -1, -1, '@date_created', '1990-01-01 12:00am .. 9999-12-31 11:59pm')
      RETURN(1) -- Failure
    END
    IF (@date_last_modified IS NOT NULL) AND
       ((@date_last_modified < '19900101') OR (@date_last_modified > '99991231 23:59'))
    BEGIN
      RAISERROR(14266, -1, -1, '@date_last_modified', '1990-01-01 12:00am .. 9999-12-31 11:59pm')
      RETURN(1) -- Failure
    END

    -- Generate results set...
    EXECUTE sp_get_composite_job_info @job_id,
                                      @job_type,
                                      @owner_login_name,
                                      @subsystem,
                                      @category_id,
                                      @enabled,
                                      @execution_status,
                                      @date_comparator,
                                      @date_created,
                                      @date_last_modified,
                                      @description
  END

  RETURN(0) -- Success
END
go

CHECKPOINT
go


/**************************************************************/
/* sp_help_jobcount                                           */
/*      At least one parameter must be specified              */
/*      returns a one row/one column recordset with a integer */
/*      representing the number of jobs assigned to the       */
/*      specified schedule                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_jobcount...'
GO
IF (NOT OBJECT_ID(N'dbo.sp_help_jobcount', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_help_jobcount
GO

CREATE PROCEDURE sp_help_jobcount 
  @schedule_name       sysname  = NULL, -- Specify if @schedule_id is null
  @schedule_id         INT      = NULL  -- Specify if @schedule_name is null
AS
BEGIN
  SET NOCOUNT ON

  DECLARE @retval   INT

  -- Check that we can uniquely identify the schedule. This only returns a schedule that is visible to this user
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @schedule_name    OUTPUT,
                                                            @schedule_id            = @schedule_id      OUTPUT,
                                                            @owner_sid              = NULL,
                                                            @orig_server_id         = NULL
  IF (@retval <> 0)
    RETURN(1) -- Failure 


  SELECT COUNT(*) AS JobCount
  FROM msdb.dbo.sysjobschedules
  WHERE (schedule_id = @schedule_id)


  RETURN (0) -- 0 means success
END
go



/**************************************************************/
/* sp_help_jobs_in_schedule                                   */
/*      At least one parameter must be specified to identify  */
/*      the schedule. Returns the same information as         */
/*      sp_help_job. Only jobs in the specified schedule are  */
/*      in the recordset                                      */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_jobs_in_schedule...'
GO
IF (NOT OBJECT_ID(N'dbo.sp_help_jobs_in_schedule', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_help_jobs_in_schedule
GO

CREATE PROCEDURE sp_help_jobs_in_schedule 
  @schedule_name       sysname  = NULL, -- Specify if @schedule_id is null
  @schedule_id         INT      = NULL  -- Specify if @schedule_name is null
AS
BEGIN
  SET NOCOUNT ON

  DECLARE @retval   INT

  -- Check that we can uniquely identify the schedule. This only returns a schedule that is visible to this user
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @schedule_name    OUTPUT,
                                                            @schedule_id            = @schedule_id      OUTPUT,
                                                            @owner_sid              = NULL,
                                                            @orig_server_id         = NULL
  IF (@retval <> 0)
    RETURN(1) -- Failure 

  EXECUTE @retval = msdb.dbo.sp_get_composite_job_info @schedule_id = @schedule_id
  IF (@retval <> 0)
    RETURN(1) -- Failure 


  RETURN (0) -- 0 means success
END
go


/**************************************************************/
/* SP_MANAGE_JOBS_BY_LOGIN                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_manage_jobs_by_login...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_manage_jobs_by_login')
              AND (type = 'P')))
  DROP PROCEDURE sp_manage_jobs_by_login
go
CREATE PROCEDURE sp_manage_jobs_by_login
  @action                   VARCHAR(10), -- DELETE or REASSIGN
  @current_owner_login_name sysname,
  @new_owner_login_name     sysname = NULL
AS
BEGIN
  DECLARE @current_sid   VARBINARY(85)
  DECLARE @new_sid       VARBINARY(85)
  DECLARE @job_id        UNIQUEIDENTIFIER
  DECLARE @rows_affected INT
  DECLARE @is_sysadmin   INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @action                   = LTRIM(RTRIM(@action))
  SELECT @current_owner_login_name = LTRIM(RTRIM(@current_owner_login_name))
  SELECT @new_owner_login_name     = LTRIM(RTRIM(@new_owner_login_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@new_owner_login_name = N'') SELECT @new_owner_login_name = NULL

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check action
  IF (@action NOT IN ('DELETE', 'REASSIGN'))
  BEGIN
    RAISERROR(14266, -1, -1, '@action', 'DELETE, REASSIGN')
    RETURN(1) -- Failure
  END

  -- Check parameter combinations
  IF ((@action = 'DELETE') AND (@new_owner_login_name IS NOT NULL))
    RAISERROR(14281, 0, 1)

  IF ((@action = 'REASSIGN') AND (@new_owner_login_name IS NULL))
  BEGIN
    RAISERROR(14237, -1, -1)
    RETURN(1) -- Failure
  END

  -- Check current login
  SELECT @current_sid = dbo.SQLAGENT_SUSER_SID(@current_owner_login_name)
  IF (@current_sid IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@current_owner_login_name', @current_owner_login_name)
    RETURN(1) -- Failure
  END

  -- Check new login (if supplied)
  IF (@new_owner_login_name IS NOT NULL)
  BEGIN
    SELECT @new_sid = dbo.SQLAGENT_SUSER_SID(@new_owner_login_name)
    IF (@new_sid IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@new_owner_login_name', @new_owner_login_name)
      RETURN(1) -- Failure
    END
  END

  IF (@action = 'DELETE')
  BEGIN
    DECLARE jobs_to_delete CURSOR LOCAL
    FOR
    SELECT job_id
    FROM msdb.dbo.sysjobs
    WHERE (owner_sid = @current_sid)

    OPEN jobs_to_delete
    FETCH NEXT FROM jobs_to_delete INTO @job_id

    SELECT @rows_affected = 0
    WHILE (@@fetch_status = 0)
    BEGIN
      EXECUTE sp_delete_job @job_id = @job_id
      SELECT @rows_affected = @rows_affected + 1
      FETCH NEXT FROM jobs_to_delete INTO @job_id
    END
    DEALLOCATE jobs_to_delete
    RAISERROR(14238, 0, 1, @rows_affected)
  END
  ELSE
  IF (@action = 'REASSIGN')
  BEGIN
    -- Check if the current owner owns any multi-server jobs.
    -- If they do, then the new owner must be member of the sysadmin role.
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobs       sj,
                     msdb.dbo.sysjobservers sjs
                WHERE (sj.job_id = sjs.job_id)
                  AND (sj.owner_sid = @current_sid)
                  AND (sjs.server_id <> 0)) AND @new_sid <> 0xFFFFFFFF) -- speical account allowed for MSX jobs
    BEGIN
      SELECT @is_sysadmin = 0
      EXECUTE msdb.dbo.sp_sqlagent_has_server_access @login_name = @new_owner_login_name, @is_sysadmin_member = @is_sysadmin OUTPUT
      IF (@is_sysadmin = 0)
      BEGIN
        RAISERROR(14543, -1, -1, @current_owner_login_name, N'sysadmin')
        RETURN(1) -- Failure
      END
    END

    UPDATE msdb.dbo.sysjobs
    SET owner_sid = @new_sid
    WHERE (owner_sid = @current_sid)
    RAISERROR(14239, 0, 1, @@rowcount, @new_owner_login_name)
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_APPLY_JOB_TO_TARGETS                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_apply_job_to_targets...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_apply_job_to_targets')
              AND (type = 'P')))
  DROP PROCEDURE sp_apply_job_to_targets
go
CREATE PROCEDURE sp_apply_job_to_targets
  @job_id               UNIQUEIDENTIFIER = NULL,   -- Must provide either this or job_name
  @job_name             sysname          = NULL,   -- Must provide either this or job_id
  @target_server_groups NVARCHAR(2048)   = NULL,   -- A comma-separated list of target server groups
  @target_servers       NVARCHAR(2048)   = NULL,   -- An comma-separated list of target servers
  @operation            VARCHAR(7)       = 'APPLY' -- Or 'REMOVE'
AS
BEGIN
  DECLARE @retval        INT
  DECLARE @rows_affected INT
  DECLARE @server_name   sysname
  DECLARE @groups        NVARCHAR(2048)
  DECLARE @group         sysname
  DECLARE @servers       NVARCHAR(2048)
  DECLARE @server        sysname
  DECLARE @pos_of_comma  INT

  SET NOCOUNT ON

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @target_server_groups = LTRIM(RTRIM(@target_server_groups))
  SELECT @target_servers       = UPPER(LTRIM(RTRIM(@target_servers)))
  SELECT @operation            = LTRIM(RTRIM(@operation))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@target_server_groups = NULL) SELECT @target_server_groups = NULL
  IF (@target_servers       = NULL) SELECT @target_servers = NULL
  IF (@operation            = NULL) SELECT @operation = NULL

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check operation type
  IF ((@operation <> 'APPLY') AND (@operation <> 'REMOVE'))
  BEGIN
    RAISERROR(14266, -1, -1, '@operation', 'APPLY, REMOVE')
    RETURN(1) -- Failure
  END

  -- Check that we have a target server group list and/or a target server list
  IF ((@target_server_groups IS NULL) AND (@target_servers IS NULL))
  BEGIN
    RAISERROR(14283, -1, -1)
    RETURN(1) -- Failure
  END

  DECLARE @temp_groups TABLE (group_name sysname COLLATE database_default NOT NULL)
  DECLARE @temp_server_name TABLE (server_name sysname COLLATE database_default NOT NULL)

  -- Parse the Target Server comma-separated list (if supplied)
  IF (@target_servers IS NOT NULL)
  BEGIN
    SELECT @servers = @target_servers
    SELECT @pos_of_comma = CHARINDEX(N',', @servers)
    WHILE (@pos_of_comma <> 0)
    BEGIN
      SELECT @server = SUBSTRING(@servers, 1, @pos_of_comma - 1)
      INSERT INTO @temp_server_name (server_name) VALUES (LTRIM(RTRIM(@server)))
      SELECT @servers = RIGHT(@servers, (DATALENGTH(@servers) / 2) - @pos_of_comma)
      SELECT @pos_of_comma = CHARINDEX(N',', @servers)
    END
    INSERT INTO @temp_server_name (server_name) VALUES (LTRIM(RTRIM(@servers)))
  END

  -- Parse the Target Server Groups comma-separated list
  IF (@target_server_groups IS NOT NULL)
  BEGIN
    SELECT @groups = @target_server_groups
    SELECT @pos_of_comma = CHARINDEX(N',', @groups)
    WHILE (@pos_of_comma <> 0)
    BEGIN
      SELECT @group = SUBSTRING(@groups, 1, @pos_of_comma - 1)
      INSERT INTO @temp_groups (group_name) VALUES (LTRIM(RTRIM(@group)))
      SELECT @groups = RIGHT(@groups, (DATALENGTH(@groups) / 2) - @pos_of_comma)
      SELECT @pos_of_comma = CHARINDEX(N',', @groups)
    END
    INSERT INTO @temp_groups (group_name) VALUES (LTRIM(RTRIM(@groups)))
  END

  -- Check server groups
  SET ROWCOUNT 1 -- We do this so that we catch the FIRST invalid group
  SELECT @group = NULL
  SELECT @group = group_name
  FROM @temp_groups
  WHERE group_name NOT IN (SELECT name
                           FROM msdb.dbo.systargetservergroups)
  IF (@group IS NOT NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@target_server_groups', @group)
    RETURN(1) -- Failure
  END
  SET ROWCOUNT 0

  -- Find the distinct list of servers being targeted
  INSERT INTO @temp_server_name (server_name)
  SELECT DISTINCT sts.server_name
  FROM msdb.dbo.systargetservergroups       stsg,
       msdb.dbo.systargetservergroupmembers stsgm,
       msdb.dbo.systargetservers            sts
  WHERE (stsg.name IN (SELECT group_name FROM @temp_groups))
    AND (stsg.servergroup_id = stsgm.servergroup_id)
    AND (stsgm.server_id = sts.server_id)
    AND (UPPER(sts.server_name) NOT IN (SELECT server_name
                                        FROM @temp_server_name))

  IF (@operation = 'APPLY')
  BEGIN
    -- Remove those servers to which the job has already been applied
    DELETE FROM @temp_server_name
    WHERE server_name IN (SELECT sts.server_name
                          FROM msdb.dbo.sysjobservers    sjs,
                               msdb.dbo.systargetservers sts
                          WHERE (sjs.job_id = @job_id)
                            AND (sjs.server_id = sts.server_id))
  END

  IF (@operation = 'REMOVE')
  BEGIN
    -- Remove those servers to which the job is not currently applied
    DELETE FROM @temp_server_name
    WHERE server_name NOT IN (SELECT sts.server_name
                              FROM msdb.dbo.sysjobservers    sjs,
                                   msdb.dbo.systargetservers sts
                              WHERE (sjs.job_id = @job_id)
                                AND (sjs.server_id = sts.server_id))
  END

  SELECT @rows_affected = COUNT(*)
  FROM @temp_server_name

  SET ROWCOUNT 1
  WHILE (EXISTS (SELECT *
                 FROM @temp_server_name))
  BEGIN
    SELECT @server_name = server_name
    FROM @temp_server_name
    IF (@operation = 'APPLY')
      EXECUTE sp_add_jobserver @job_id = @job_id, @server_name = @server_name
    ELSE
    IF (@operation = 'REMOVE')
      EXECUTE sp_delete_jobserver @job_id = @job_id, @server_name = @server_name
    DELETE FROM @temp_server_name
    WHERE (server_name = @server_name)
  END
  SET ROWCOUNT 0

  IF (@operation = 'APPLY')
    RAISERROR(14240, 0, 1, @rows_affected)
  IF (@operation = 'REMOVE')
    RAISERROR(14241, 0, 1, @rows_affected)

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_REMOVE_JOB_FROM_TARGETS                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_remove_job_from_targets...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_remove_job_from_targets')
              AND (type = 'P')))
  DROP PROCEDURE sp_remove_job_from_targets
go
CREATE PROCEDURE sp_remove_job_from_targets
  @job_id               UNIQUEIDENTIFIER = NULL,   -- Must provide either this or job_name
  @job_name             sysname          = NULL,   -- Must provide either this or job_id
  @target_server_groups NVARCHAR(1024)   = NULL,   -- A comma-separated list of target server groups
  @target_servers       NVARCHAR(1024)   = NULL    -- A comma-separated list of target servers
AS
BEGIN
  DECLARE @retval INT

  SET NOCOUNT ON

  EXECUTE @retval = sp_apply_job_to_targets @job_id,
                                            @job_name,
                                            @target_server_groups,
                                            @target_servers,
                                           'REMOVE'
  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_GET_JOB_ALERTS                                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_get_job_alerts...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_get_job_alerts')
              AND (type = 'P')))
  DROP PROCEDURE sp_get_job_alerts
go
CREATE PROCEDURE sp_get_job_alerts
  @job_id   UNIQUEIDENTIFIER = NULL,
  @job_name sysname          = NULL
AS
BEGIN
  DECLARE @retval INT

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  SELECT id,
         name,
         enabled,
       type = CASE ISNULL(performance_condition, '!')
         WHEN '!' THEN 1              -- SQL Server event alert
         ELSE CASE event_id
            WHEN 8 THEN 3          -- WMI event alert
            ELSE 2                    -- SQL Server performance condition alert
         END
       END
  FROM msdb.dbo.sysalerts
  WHERE (job_id = @job_id)

  RETURN(0) -- Success
END
go

/**************************************************************/
/*                                                            */
/*   S  U  P  P  O  R  T     P  R  O  C  E  D  U  R  E  S     */
/*                                                            */
/**************************************************************/

/**************************************************************/
/* SP_CONVERT_JOBID_TO_CHAR [used by SEM only]                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_convert_jobid_to_char...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_convert_jobid_to_char')
              AND (type = 'P')))
  DROP PROCEDURE sp_convert_jobid_to_char
go
CREATE PROCEDURE sp_convert_jobid_to_char
  @job_id         UNIQUEIDENTIFIER,
  @job_id_as_char NVARCHAR(34) OUTPUT -- 34 because of the leading '0x'
AS
BEGIN
  DECLARE @job_id_as_binary BINARY(16)
  DECLARE @temp             NCHAR(8)
  DECLARE @counter          INT
  DECLARE @byte_value       INT
  DECLARE @high_word        INT
  DECLARE @low_word         INT
  DECLARE @high_high_nybble INT
  DECLARE @high_low_nybble  INT
  DECLARE @low_high_nybble  INT
  DECLARE @low_low_nybble   INT

  SET NOCOUNT ON

  SELECT @job_id_as_binary = CONVERT(BINARY(16), @job_id)
  SELECT @temp = CONVERT(NCHAR(8), @job_id_as_binary)

  SELECT @job_id_as_char = N''
  SELECT @counter = 1

  WHILE (@counter <= (DATALENGTH(@temp) / 2))
  BEGIN
    SELECT @byte_value       = CONVERT(INT, CONVERT(BINARY(2), SUBSTRING(@temp, @counter, 1)))
    SELECT @high_word        = (@byte_value & 0xff00) / 0x100
    SELECT @low_word         = (@byte_value & 0x00ff)
    SELECT @high_high_nybble = (@high_word & 0xff) / 16
    SELECT @high_low_nybble  = (@high_word & 0xff) % 16
    SELECT @low_high_nybble  = (@low_word & 0xff) / 16
    SELECT @low_low_nybble   = (@low_word & 0xff) % 16

    IF (@high_high_nybble < 10)
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('0') + @high_high_nybble)
    ELSE
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('A') + (@high_high_nybble - 10))

    IF (@high_low_nybble < 10)
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('0') + @high_low_nybble)
    ELSE
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('A') + (@high_low_nybble - 10))

    IF (@low_high_nybble < 10)
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('0') + @low_high_nybble)
    ELSE
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('A') + (@low_high_nybble - 10))

    IF (@low_low_nybble < 10)
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('0') + @low_low_nybble)
    ELSE
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('A') + (@low_low_nybble - 10))

    SELECT @counter = @counter + 1
  END

  SELECT @job_id_as_char = N'0x' + LOWER(@job_id_as_char)
END
go

/**************************************************************/
/* SP_START_JOB                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_start_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_start_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_start_job
go
CREATE PROCEDURE sp_start_job
  @job_name    sysname          = NULL,
  @job_id      UNIQUEIDENTIFIER = NULL,
  @error_flag  INT              = 1,    -- Set to 0 to suppress the error from sp_sqlagent_notify if SQLServerAgent is not running
  @server_name sysname          = NULL, -- The specific target server to start the [multi-server] job on
  @step_name   sysname          = NULL, -- The name of the job step to start execution with [for use with a local job only]
  @output_flag INT              = 1     -- Set to 0 to suppress the success message
AS
BEGIN
  DECLARE @job_id_as_char VARCHAR(36)
  DECLARE @retval         INT
  DECLARE @step_id        INT
  DECLARE @job_owner_sid  VARBINARY(85)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @job_name    = LTRIM(RTRIM(@job_name))
  SELECT @server_name = UPPER(LTRIM(RTRIM(@server_name)))
  SELECT @step_name   = LTRIM(RTRIM(@step_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@job_name = N'')    SELECT @job_name = NULL
  IF (@server_name = N'') SELECT @server_name = NULL
  IF (@step_name = N'')   SELECT @step_name = NULL

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                               @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check permissions beyond what's checked by the sysjobs_view
  -- SQLAgentReader role can see all jobs but
  -- cannot start/stop jobs they do not own
  IF (@job_owner_sid <> SUSER_SID()                      -- does not own the job
     AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0)     -- is not sysadmin
     AND (ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) = 0))  -- is not SQLAgentOperatorRole
  BEGIN
   RAISERROR(14393, -1, -1);  
   RETURN(1) -- Failure
  END

  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysjobservers
                  WHERE (job_id = @job_id)))
  BEGIN
    SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)
    RAISERROR(14256, -1, -1, @job_name, @job_id_as_char)
    RETURN(1) -- Failure
  END

  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id = 0)))
  BEGIN
    -- The job is local, so start (run) the job locally

    -- Check the step name (if supplied)
    IF (@step_name IS NOT NULL)
    BEGIN
      SELECT @step_id = step_id
      FROM msdb.dbo.sysjobsteps
      WHERE (step_name = @step_name)
        AND (job_id = @job_id)

      IF (@step_id IS NULL)
      BEGIN
        RAISERROR(14262, -1, -1, '@step_name', @step_name)
        RETURN(1) -- Failure
      END
    END

    EXECUTE @retval = msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                                  @job_id      = @job_id,
                                                  @schedule_id = @step_id, -- This is the start step
                                                  @action_type = N'S',
                                                  @error_flag  = @error_flag
    IF ((@retval = 0) AND (@output_flag = 1))
      RAISERROR(14243, 0, 1, @job_name)
  END
  ELSE
  BEGIN
    -- The job is a multi-server job

      -- Only sysadmin can start multi-server job
      IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
      BEGIN
         RAISERROR(14397, -1, -1);
         RETURN(1) -- Failure
      END            

    -- Check target server name (if any)
    IF (@server_name IS NOT NULL)
    BEGIN
      IF (NOT EXISTS (SELECT *
                      FROM msdb.dbo.systargetservers
                      WHERE (UPPER(server_name) = @server_name)))
      BEGIN
        RAISERROR(14262, -1, -1, '@server_name', @server_name)
        RETURN(1) -- Failure
      END
    END

    -- Re-post the job if it's an auto-delete job
    IF ((SELECT delete_level
         FROM msdb.dbo.sysjobs
         WHERE (job_id = @job_id)) <> 0)
      EXECUTE @retval = msdb.dbo.sp_post_msx_operation 'INSERT', 'JOB', @job_id, @server_name

    -- Post start instruction(s)
    EXECUTE @retval = msdb.dbo.sp_post_msx_operation 'START', 'JOB', @job_id, @server_name
  END

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_STOP_JOB                                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_stop_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_stop_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_stop_job
go
CREATE PROCEDURE sp_stop_job
  @job_name           sysname          = NULL,
  @job_id             UNIQUEIDENTIFIER = NULL,
  @originating_server sysname          = NULL, -- So that we can stop ALL jobs that came from the given server
  @server_name        sysname        = NULL  -- The specific target server to stop the [multi-server] job on
AS
BEGIN
  DECLARE @job_id_as_char           VARCHAR(36)
  DECLARE @retval                   INT
  DECLARE @num_parameters           INT
  DECLARE @job_owner_sid         VARBINARY(85)
  
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @job_name           = LTRIM(RTRIM(@job_name))
  SELECT @originating_server = UPPER(LTRIM(RTRIM(@originating_server)))
  SELECT @server_name        = UPPER(LTRIM(RTRIM(@server_name)))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@job_name           = N'') SELECT @job_name = NULL
  IF (@originating_server = N'') SELECT @originating_server = NULL
  IF (@server_name        = N'') SELECT @server_name = NULL

  -- We must have EITHER a job id OR a job name OR an originating server
  SELECT @num_parameters = 0
  IF (@job_id IS NOT NULL)
    SELECT @num_parameters = @num_parameters + 1
  IF (@job_name IS NOT NULL)
    SELECT @num_parameters = @num_parameters + 1
  IF (@originating_server IS NOT NULL)
    SELECT @num_parameters = @num_parameters + 1
  IF (@num_parameters <> 1)
  BEGIN
    RAISERROR(14232, -1, -1)
    RETURN(1) -- Failure
  END
  
  IF (@originating_server IS NOT NULL)
  BEGIN 
    -- Stop (cancel) ALL local jobs that originated from the specified server
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysjobs_view
                    WHERE (originating_server = @originating_server)))
    BEGIN
      RAISERROR(14268, -1, -1, @originating_server)
      RETURN(1) -- Failure
    END

    -- Check permissions beyond what's checked by the sysjobs_view
    -- SQLAgentReader role that can see all jobs but
    -- cannot start/stop jobs they do not own
    IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0)          -- is not sysadmin
       AND (ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) = 0)) -- is not SQLAgentOperatorRole
    BEGIN
       RAISERROR(14393, -1, -1);
       RETURN(1) -- Failure
    END

    DECLARE @total_counter   INT
    DECLARE @success_counter INT

    DECLARE stop_jobs CURSOR LOCAL
    FOR
    SELECT job_id
    FROM msdb.dbo.sysjobs_view
    WHERE (originating_server = @originating_server)

    SELECT @total_counter = 0, @success_counter = 0
    OPEN stop_jobs
    FETCH NEXT FROM stop_jobs INTO @job_id
    WHILE (@@fetch_status = 0)
    BEGIN
      SELECT @total_counter + @total_counter + 1
      EXECUTE @retval = msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                                    @job_id      = @job_id,
                                                    @action_type = N'C'
      IF (@retval = 0)
        SELECT @success_counter = @success_counter + 1
      FETCH NEXT FROM stop_jobs INTO @job_id
    END
    RAISERROR(14253, 0, 1, @success_counter, @total_counter)
    DEALLOCATE stop_jobs

    RETURN(0) -- 0 means success
  END
  ELSE
  BEGIN
    -- Stop ONLY the specified job
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT,
                                                 @owner_sid = @job_owner_sid OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysjobservers
                    WHERE (job_id = @job_id)))
    BEGIN
      SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)
      RAISERROR(14257, -1, -1, @job_name, @job_id_as_char)
      RETURN(1) -- Failure
    END
    
    -- Check permissions beyond what's checked by the sysjobs_view
    -- SQLAgentReader role that can see all jobs but
    -- cannot start/stop jobs they do not own
    IF (@job_owner_sid <> SUSER_SID()                  -- does not own the job
       AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0)       -- is not sysadmin
       AND (ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) = 0)) -- is not SQLAgentOperatorRole
    BEGIN
     RAISERROR(14393, -1, -1);
     RETURN(1) -- Failure
    END

    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                  AND (server_id = 0)))
    BEGIN
      -- The job is local, so stop (cancel) the job locally
      EXECUTE @retval = msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                                    @job_id      = @job_id,
                                                    @action_type = N'C'
      IF (@retval = 0)
        RAISERROR(14254, 0, 1, @job_name)
    END
    ELSE
    BEGIN
      -- The job is a multi-server job

      -- Only sysadmin can stop multi-server job
      IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
      BEGIN
         RAISERROR(14397, -1, -1);
         RETURN(1) -- Failure
      END            

      -- Check target server name (if any)
      IF (@server_name IS NOT NULL)
      BEGIN
        IF (NOT EXISTS (SELECT *
                        FROM msdb.dbo.systargetservers
                        WHERE (UPPER(server_name) = @server_name)))
        BEGIN
          RAISERROR(14262, -1, -1, '@server_name', @server_name)
          RETURN(1) -- Failure
        END
      END

      -- Post the stop instruction(s)
      EXECUTE @retval = sp_post_msx_operation 'STOP', 'JOB', @job_id, @server_name
    END

    RETURN(@retval) -- 0 means success
  END

END
go

/**************************************************************/
/* SP_CYCLE_AGENT_ERRORLOG                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_cycle_agent_errorlog...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_cycle_agent_errorlog')
              AND (type = 'P')))
  DROP PROCEDURE sp_cycle_agent_errorlog
go

CREATE PROCEDURE sp_cycle_agent_errorlog
AS
BEGIN
   exec sp_sqlagent_notify N'L'
END
go

/**************************************************************/
/* SP_GET_CHUNKED_JOBSTEP_PARAMS                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_get_chunked_jobstep_params...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_get_chunked_jobstep_params')
              AND (type = 'P')))
  DROP PROCEDURE sp_get_chunked_jobstep_params
go
CREATE PROCEDURE sp_get_chunked_jobstep_params
  @job_name sysname,
  @step_id  INT = 1
AS
BEGIN
  DECLARE @job_id           UNIQUEIDENTIFIER
  DECLARE @step_id_as_char  VARCHAR(10)
  DECLARE @retval           INT

  SET NOCOUNT ON

  -- Check that the job exists
  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check that the step exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysjobsteps
                  WHERE (job_id = @job_id)
                    AND (step_id = @step_id)))
  BEGIN
    SELECT @step_id_as_char = CONVERT(VARCHAR(10), @step_id)
    RAISERROR(14262, -1, -1, '@step_id', @step_id_as_char)
    RETURN(1) -- Failure
  END

  -- Return the sysjobsteps.additional_parameters 

  SELECT additional_parameters
  FROM msdb.dbo.sysjobsteps
  WHERE (job_id = @job_id)
    AND (step_id = @step_id)
  
  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_CHECK_FOR_OWNED_JOBS                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_check_for_owned_jobs...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_check_for_owned_jobs')
              AND (type = 'P')))
  DROP PROCEDURE sp_check_for_owned_jobs
go
CREATE PROCEDURE sp_check_for_owned_jobs
  @login_name sysname,
  @table_name sysname
AS
BEGIN
  SET NOCOUNT ON

  -- This procedure is called by sp_droplogin to check if the login being dropped
  -- still owns jobs.  The return value (the number of jobs owned) is passed back
  -- via the supplied table name [this cumbersome approach is necessary because
  -- sp_check_for_owned_jobs is invoked via an EXEC() and because we always want
  -- sp_droplogin to work, even if msdb and/or sysjobs does not exist].

  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysobjects
              WHERE (name = N'sysjobs')
                AND (type = 'U')))
  BEGIN
    DECLARE @sql NVARCHAR(1024)
    SET @sql = N'INSERT INTO ' + QUOTENAME(@table_name, N'[') + N' SELECT COUNT(*) FROM msdb.dbo.sysjobs WHERE (owner_sid = SUSER_SID(N' + QUOTENAME(@login_name, '''') + ', 0))' --force case insensitive comparation for NT users
    EXEC sp_executesql @statement = @sql  
  END
END
go

/**************************************************************/
/* SP_CHECK_FOR_OWNED_JOBSTEPS                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_check_for_owned_jobsteps...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_check_for_owned_jobsteps')
              AND (type = 'P')))
  DROP PROCEDURE sp_check_for_owned_jobsteps
go
CREATE PROCEDURE sp_check_for_owned_jobsteps
  @login_name         sysname = NULL,  -- Supply this OR the database_X parameters, but not both
  @database_name      sysname = NULL,
  @database_user_name sysname = NULL
AS
BEGIN
  DECLARE @db_name            NVARCHAR(128)
  DECLARE @delimited_db_name  NVARCHAR(258)
  DECLARE @escaped_db_name    NVARCHAR(256) -- double sysname
  DECLARE @escaped_login_name NVARCHAR(256) -- double sysname

  SET NOCOUNT ON

  CREATE TABLE #work_table
  (
  database_name      sysname COLLATE database_default,
  database_user_name sysname COLLATE database_default
  )

  IF ((@login_name IS NOT NULL) AND (@database_name IS NULL) AND (@database_user_name IS NULL))
  BEGIN
    IF (SUSER_SID(@login_name, 0) IS NULL)--force case insensitive comparation for NT users
    BEGIN
      DROP TABLE #work_table

      RAISERROR(14262, -1, -1, '@login_name', @login_name)
      RETURN(1) -- Failure
    END

    DECLARE all_databases CURSOR LOCAL
    FOR
    SELECT name
    FROM master.dbo.sysdatabases

    OPEN all_databases
    FETCH NEXT FROM all_databases INTO @db_name

    -- Double up any single quotes in @login_name
    SELECT @escaped_login_name = REPLACE(@login_name, N'''', N'''''')

    WHILE (@@fetch_status = 0)
    BEGIN
      SELECT @delimited_db_name = QUOTENAME(@db_name, N'[')
      SELECT @escaped_db_name = REPLACE(@db_name, '''', '''''')
      EXECUTE(N'INSERT INTO #work_table
                SELECT N''' + @escaped_db_name + N''', name
                FROM ' + @delimited_db_name + N'.dbo.sysusers
                WHERE (sid = SUSER_SID(N''' + @escaped_login_name + N''', 0))')--force case insensitive comparation for NT users
      FETCH NEXT FROM all_databases INTO @db_name
    END

    DEALLOCATE all_databases

    -- If the login is an NT login, check for steps run as the login directly (as is the case with transient NT logins)
    IF (@login_name LIKE '%\%')
    BEGIN
      INSERT INTO #work_table
      SELECT database_name, database_user_name
      FROM msdb.dbo.sysjobsteps
      WHERE (database_user_name = @login_name)
    END
  END

  IF ((@login_name IS NULL) AND (@database_name IS NOT NULL) AND (@database_user_name IS NOT NULL))
  BEGIN
    INSERT INTO #work_table
    SELECT @database_name, @database_user_name
  END

  IF (EXISTS (SELECT *
              FROM #work_table wt,
                   msdb.dbo.sysjobsteps sjs
              WHERE (wt.database_name = sjs.database_name)
                AND (wt.database_user_name = sjs.database_user_name)))
  BEGIN
    SELECT sjv.job_id,
           sjv.name,
           sjs.step_id,
           sjs.step_name
    FROM #work_table           wt,
         msdb.dbo.sysjobsteps  sjs,
         msdb.dbo.sysjobs_view sjv
    WHERE (wt.database_name = sjs.database_name)
      AND (wt.database_user_name = sjs.database_user_name)
      AND (sjv.job_id = sjs.job_id)
    ORDER BY sjs.job_id
  END

  DROP TABLE #work_table
  RETURN(0) -- 0 means success
END
go

/**************************************************************/
/* SP_SQLAGENT_REFRESH_JOB                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_refresh_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_sqlagent_refresh_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_refresh_job
go
CREATE PROCEDURE sp_sqlagent_refresh_job
  @job_id      UNIQUEIDENTIFIER = NULL,
  @server_name sysname          = NULL -- This parameter allows a TSX to use this SP when updating a job
AS
BEGIN
  DECLARE @server_id INT

  SET NOCOUNT ON

  IF (@server_name IS NULL) OR (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = '(LOCAL)')
    SELECT @server_name = CONVERT(sysname, SERVERPROPERTY('ServerName'))

  SELECT @server_name = UPPER(@server_name)

  SELECT @server_id = server_id
  FROM msdb.dbo.systargetservers_view
  WHERE (UPPER(server_name) = ISNULL(@server_name, UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))))

  SELECT @server_id = ISNULL(@server_id, 0)

  SELECT sjv.job_id,
         sjv.name,
         sjv.enabled,
         sjv.start_step_id,
         owner = dbo.SQLAGENT_SUSER_SNAME(sjv.owner_sid),
         sjv.notify_level_eventlog,
         sjv.notify_level_email,
         sjv.notify_level_netsend,
         sjv.notify_level_page,
         sjv.notify_email_operator_id,
         sjv.notify_netsend_operator_id,
         sjv.notify_page_operator_id,
         sjv.delete_level,
         has_step = (SELECT COUNT(*)
                     FROM msdb.dbo.sysjobsteps sjst
                     WHERE (sjst.job_id = sjv.job_id)),
         sjv.version_number,
         last_run_date = ISNULL(sjs.last_run_date, 0),
         last_run_time = ISNULL(sjs.last_run_time, 0),
         sjv.originating_server,
         sjv.description,
         agent_account = CASE sjv.owner_sid
              WHEN 0xFFFFFFFF THEN 1
              ELSE                 0
         END
  FROM msdb.dbo.sysjobservers sjs,
       msdb.dbo.sysjobs_view  sjv
  WHERE ((@job_id IS NULL) OR (@job_id = sjv.job_id))
    AND (sjv.job_id = sjs.job_id)
    AND (sjs.server_id = @server_id)
  ORDER BY sjv.job_id
  OPTION (FORCE ORDER)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_JOBHISTORY_ROW_LIMITER                                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_jobhistory_row_limiter...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_jobhistory_row_limiter')
              AND (type = 'P')))
  DROP PROCEDURE dbo.sp_jobhistory_row_limiter
go
CREATE PROCEDURE sp_jobhistory_row_limiter
  @job_id UNIQUEIDENTIFIER
AS
BEGIN
  DECLARE @max_total_rows         INT -- This value comes from the registry (MaxJobHistoryTableRows)
  DECLARE @max_rows_per_job       INT -- This value comes from the registry (MaxJobHistoryRows)
  DECLARE @rows_to_delete         INT
  DECLARE @current_rows           INT
  DECLARE @current_rows_per_job   INT

  SET NOCOUNT ON

  -- Get max-job-history-rows from the registry
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'JobHistoryMaxRows',
                                         @max_total_rows OUTPUT,
                                         N'no_output'

  -- Check if we are limiting sysjobhistory rows
  IF (ISNULL(@max_total_rows, -1) = -1)
    RETURN(0)

  -- Check that max_total_rows is more than 1
  IF (ISNULL(@max_total_rows, 0) < 2)
  BEGIN
    -- It isn't, so set the default to 1000 rows
    SELECT @max_total_rows = 1000
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'JobHistoryMaxRows',
                                            N'REG_DWORD',
                                            @max_total_rows
  END

  -- Get the per-job maximum number of rows to keep
  SELECT @max_rows_per_job = 0
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'JobHistoryMaxRowsPerJob',
                                         @max_rows_per_job OUTPUT,
                                         N'no_output'

  -- Check that max_rows_per_job is <= max_total_rows
  IF ((@max_rows_per_job > @max_total_rows) OR (@max_rows_per_job < 1))
  BEGIN
    -- It isn't, so default the rows_per_job to max_total_rows
    SELECT @max_rows_per_job = @max_total_rows
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'JobHistoryMaxRowsPerJob',
                                            N'REG_DWORD',
                                            @max_rows_per_job
  END

  BEGIN TRANSACTION

  SELECT @current_rows_per_job = COUNT(*)
  FROM msdb.dbo.sysjobhistory with (TABLOCKX)
  WHERE (job_id = @job_id)

  -- Delete the oldest history row(s) for the job being inserted if the new row has
  -- pushed us over the per-job row limit (MaxJobHistoryRows)
  SELECT @rows_to_delete = @current_rows_per_job - @max_rows_per_job

  IF (@rows_to_delete > 0)
  BEGIN
    WITH RowsToDelete AS (
      SELECT TOP (@rows_to_delete) *
      FROM msdb.dbo.sysjobhistory
      WHERE (job_id = @job_id)
      ORDER BY instance_id
    )
    DELETE FROM RowsToDelete;
  END

  -- Delete the oldest history row(s) if inserting the new row has pushed us over the
  -- global MaxJobHistoryTableRows limit.
  SELECT @current_rows = COUNT(*)
  FROM msdb.dbo.sysjobhistory

  SELECT @rows_to_delete = @current_rows - @max_total_rows

  IF (@rows_to_delete > 0)
  BEGIN
    WITH RowsToDelete AS (
      SELECT TOP (@rows_to_delete) *
      FROM msdb.dbo.sysjobhistory
      ORDER BY instance_id
    )
    DELETE FROM RowsToDelete;
  END

  IF (@@trancount > 0)
    COMMIT TRANSACTION

  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_SQLAGENT_LOG_JOBHISTORY                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_log_jobhistory...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_sqlagent_log_jobhistory')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_log_jobhistory
go
CREATE PROCEDURE sp_sqlagent_log_jobhistory
  @job_id               UNIQUEIDENTIFIER,
  @step_id              INT,
  @sql_message_id       INT = 0,
  @sql_severity         INT = 0,
  @message              NVARCHAR(4000) = NULL,
  @run_status           INT, -- SQLAGENT_EXEC_X code
  @run_date             INT,
  @run_time             INT,
  @run_duration         INT,
  @operator_id_emailed  INT = 0,
  @operator_id_netsent  INT = 0,
  @operator_id_paged    INT = 0,
  @retries_attempted    INT,
  @server               sysname = NULL,
  @session_id           INT = 0
AS
BEGIN
  DECLARE @retval              INT
  DECLARE @operator_id_as_char VARCHAR(10)
  DECLARE @step_name           sysname
  DECLARE @error_severity      INT

  SET NOCOUNT ON

  IF (@server IS NULL) OR (UPPER(@server collate SQL_Latin1_General_CP1_CS_AS) = '(LOCAL)')
    SELECT @server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

  -- Check authority (only SQLServerAgent can add a history entry for a job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- NOTE: We raise all errors as informational (sev 0) to prevent SQLServerAgent from caching
  --       the operation (if it fails) since if the operation will never run successfully we
  --       don't want it to stay around in the operation cache.
  SELECT @error_severity = 0

  -- Check job_id
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysjobs_view
                  WHERE (job_id = @job_id)))
  BEGIN
    DECLARE @job_id_as_char      VARCHAR(36)
    SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)
    RAISERROR(14262, @error_severity, -1, 'Job', @job_id_as_char)
    RETURN(1) -- Failure
  END

  -- Check step id
  IF (@step_id <> 0) -- 0 means 'for the whole job'
  BEGIN
    SELECT @step_name = step_name
    FROM msdb.dbo.sysjobsteps
    WHERE (job_id = @job_id)
      AND (step_id = @step_id)
    IF (@step_name IS NULL)
    BEGIN
      DECLARE @step_id_as_char     VARCHAR(10)
      SELECT @step_id_as_char = CONVERT(VARCHAR, @step_id)
      RAISERROR(14262, @error_severity, -1, '@step_id', @step_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
    SELECT @step_name = FORMATMESSAGE(14570)

  -- Check run_status
  IF (@run_status NOT IN (0, 1, 2, 3, 4, 5)) -- SQLAGENT_EXEC_X code
  BEGIN
    RAISERROR(14266, @error_severity, -1, '@run_status', '0, 1, 2, 3, 4, 5')
    RETURN(1) -- Failure
  END

  -- Check run_date
  EXECUTE @retval = sp_verify_job_date @run_date, '@run_date', 10
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check run_time
  EXECUTE @retval = sp_verify_job_time @run_time, '@run_time', 10
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check operator_id_emailed
  IF (@operator_id_emailed <> 0)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysoperators
                    WHERE (id = @operator_id_emailed)))
    BEGIN
      SELECT @operator_id_as_char = CONVERT(VARCHAR, @operator_id_emailed)
      RAISERROR(14262, @error_severity, -1, '@operator_id_emailed', @operator_id_as_char)
      RETURN(1) -- Failure
    END
  END

  -- Check operator_id_netsent
  IF (@operator_id_netsent <> 0)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysoperators
                    WHERE (id = @operator_id_netsent)))
    BEGIN
      SELECT @operator_id_as_char = CONVERT(VARCHAR, @operator_id_netsent)
      RAISERROR(14262, @error_severity, -1, '@operator_id_netsent', @operator_id_as_char)
      RETURN(1) -- Failure
    END
  END

  -- Check operator_id_paged
  IF (@operator_id_paged <> 0)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysoperators
                    WHERE (id = @operator_id_paged)))
    BEGIN
      SELECT @operator_id_as_char = CONVERT(VARCHAR, @operator_id_paged)
      RAISERROR(14262, @error_severity, -1, '@operator_id_paged', @operator_id_as_char)
      RETURN(1) -- Failure
    END
  END

  -- Insert the history row
  INSERT INTO msdb.dbo.sysjobhistory
         (job_id,
          step_id,
          step_name,
          sql_message_id,
          sql_severity,
          message,
          run_status,
          run_date,
          run_time,
          run_duration,
          operator_id_emailed,
          operator_id_netsent,
          operator_id_paged,
          retries_attempted,
          server)
  VALUES (@job_id,
          @step_id,
          @step_name,
          @sql_message_id,
          @sql_severity,
          @message,
          @run_status,
          @run_date,
          @run_time,
          @run_duration,
          @operator_id_emailed,
          @operator_id_netsent,
          @operator_id_paged,
          @retries_attempted,
          @server)

  -- Update sysjobactivity table 
  IF (@step_id = 0) --only update for job, not for each step
  BEGIN
    UPDATE msdb.dbo.sysjobactivity
    SET stop_execution_date = DATEADD(ms, -DATEPART(ms, GetDate()),  GetDate()),
        job_history_id = SCOPE_IDENTITY()
    WHERE
        session_id = @session_id AND job_id = @job_id
  END
  -- Special handling of replication jobs 
  DECLARE @job_name sysname
  DECLARE @category_id int
  SELECT  @job_name = name, @category_id = category_id from msdb.dbo.sysjobs 
   WHERE job_id = @job_id 
 
  -- If replicatio agents (snapshot, logreader, distribution, merge, and queuereader
  -- and the step has been canceled and if we are at the distributor.
  IF @category_id in (10,13,14,15,19) and @run_status = 3 and 
   object_id('MSdistributiondbs') is not null
  BEGIN
    -- Get the database
    DECLARE @database sysname
    SELECT @database = database_name from sysjobsteps where job_id = @job_id and 
   lower(subsystem) in (N'distribution', N'logreader','snapshot',N'merge',
      N'queuereader')
    -- If the database is a distribution database
    IF EXISTS (select * from MSdistributiondbs where name = @database)
    BEGIN
   DECLARE @proc nvarchar(500)
   SELECT @proc = quotename(@database) + N'.dbo.sp_MSlog_agent_cancel'
   EXEC @proc @job_id = @job_id, @category_id = @category_id, 
      @message = @message
    END  
  END

  -- Delete any history rows that are over the registry-defined limits
  EXECUTE msdb.dbo.sp_jobhistory_row_limiter @job_id

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_SQLAGENT_CHECK_MSX_VERSION                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_check_msx_version...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_sqlagent_check_msx_version')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_check_msx_version
go
CREATE PROCEDURE sp_sqlagent_check_msx_version
  @required_microsoft_version INT = NULL
AS
BEGIN
  SET NOCOUNT ON

  DECLARE @msx_version          NVARCHAR(16)
  DECLARE @required_msx_version NVARCHAR(16)

  IF (@required_microsoft_version IS NULL)
    SELECT @required_microsoft_version = 0x07000252 -- 7.0.594

  IF (@@microsoftversion < @required_microsoft_version)
  BEGIN
    SELECT @msx_version = CONVERT( NVARCHAR(2), CONVERT( INT, CONVERT( BINARY(1), @@microsoftversion / 0x1000000 ) ) )
   + N'.' 
   + CONVERT( NVARCHAR(2), CONVERT( INT, CONVERT( BINARY(1), CONVERT( BINARY(2), ((@@microsoftversion / 0x10000) % 0x100) ) ) ) )
   + N'.'
   + CONVERT( NVARCHAR(4), @@microsoftversion % 0x10000 )

    SELECT @required_msx_version = CONVERT( NVARCHAR(2), CONVERT( INT, CONVERT( BINARY(1), @required_microsoft_version / 0x1000000 ) ) )
   + N'.'
   + CONVERT( NVARCHAR(2), CONVERT( INT, CONVERT( BINARY(1), CONVERT( BINARY(2), ((@required_microsoft_version / 0x10000) % 0x100) ) ) ) )
   + N'.' 
   + CONVERT( NVARCHAR(4), @required_microsoft_version % 0x10000 )    

   RAISERROR(14541, -1, -1, @msx_version, @required_msx_version)
    RETURN(1) -- Failure
  END
  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_SQLAGENT_PROBE_MSX                                      */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_probe_msx...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_sqlagent_probe_msx')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_probe_msx
go
CREATE PROCEDURE sp_sqlagent_probe_msx
  @server_name          sysname,  -- The name of the target server probing the MSX
  @local_time           NVARCHAR(100), -- The local time at the target server in the format YYYY/MM/DD HH:MM:SS
  @poll_interval        INT,           -- The frequency (in seconds) with which the target polls the MSX
  @time_zone_adjustment INT = NULL     -- The offset from GMT in minutes (may be NULL if unknown)
AS
BEGIN
  DECLARE @bad_enlistment        BIT
  DECLARE @blocking_instructions INT
  DECLARE @pending_instructions  INT

  SET NOCOUNT ON

  SELECT @server_name = UPPER(@server_name)
  SELECT @bad_enlistment = 0, @blocking_instructions = 0, @pending_instructions = 0

  UPDATE msdb.dbo.systargetservers
  SET last_poll_date = GETDATE(),
      local_time_at_last_poll = CONVERT(DATETIME, @local_time, 111),
      poll_interval = @poll_interval,
      time_zone_adjustment = ISNULL(@time_zone_adjustment, time_zone_adjustment)
  WHERE (UPPER(server_name) = @server_name)

  -- If the systargetservers entry is missing (and no DEFECT instruction has been posted)
  -- then the enlistment is bad
  IF (NOT EXISTS (SELECT 1
                  FROM msdb.dbo.systargetservers
                  WHERE (UPPER(server_name) = @server_name))) AND
     (NOT EXISTS (SELECT 1
                  FROM msdb.dbo.sysdownloadlist
                  WHERE (target_server = @server_name)
                    AND (operation_code = 7)
                    AND (object_type = 2)))
    SELECT @bad_enlistment = 1

  SELECT @blocking_instructions = COUNT(*)
  FROM msdb.dbo.sysdownloadlist
  WHERE (target_server = @server_name)
    AND (error_message IS NOT NULL)

  SELECT @pending_instructions = COUNT(*)
  FROM msdb.dbo.sysdownloadlist
  WHERE (target_server = @server_name)
    AND (error_message IS NULL)
    AND (status = 0)

  SELECT @bad_enlistment, @blocking_instructions, @pending_instructions
END
go

/**************************************************************/
/* SP_SET_LOCAL_TIME                                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_set_local_time...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_set_local_time')
              AND (type = 'P')))
  DROP PROCEDURE sp_set_local_time
go
CREATE PROCEDURE sp_set_local_time
  @server_name           sysname = NULL,
  @adjustment_in_minutes INT          = 0 -- Only needed for Win9x
AS
BEGIN
  DECLARE @ret              INT
  DECLARE @local_time       INT
  DECLARE @local_date       INT
  DECLARE @current_datetime DATETIME
  DECLARE @local_time_sz    VARCHAR(30)
  DECLARE @cmd              NVARCHAR(200)
  DECLARE @date_format      NVARCHAR(64)
  DECLARE @year_sz          NVARCHAR(16)
  DECLARE @month_sz         NVARCHAR(16)
  DECLARE @day_sz           NVARCHAR(16)

  -- Synchronize the clock with the remote server (if supplied)
  -- NOTE: NT takes timezones into account, whereas Win9x does not
  IF (@server_name IS NOT NULL)
  BEGIN
    SELECT @cmd = N'net time \\' + @server_name + N' /set /y'
    EXECUTE @ret = master.dbo.xp_cmdshell @cmd, no_output
    IF (@ret <> 0)
      RETURN(1) -- Failure
  END

  -- Since NET TIME on Win9x does not take time zones into account we need to manually adjust
  -- for this using @adjustment_in_minutes which will be the difference between the MSX GMT
  -- offset and the target server GMT offset
  IF ((PLATFORM() & 0x2) = 0x2) -- Win9x
  BEGIN
    -- Get the date format from the registry (so that we can construct our DATE command-line command)
    EXECUTE master.dbo.xp_regread N'HKEY_CURRENT_USER',
                                  N'Control Panel\International',
                                  N'sShortDate',
                                  @date_format OUTPUT,
                                  N'no_output'
    SELECT @date_format = LOWER(@date_format)

    IF (@adjustment_in_minutes <> 0)
    BEGIN
      -- Wait for SQLServer to re-cache the OS time
      WAITFOR DELAY '00:01:00'

      SELECT @current_datetime = DATEADD(mi, @adjustment_in_minutes, GETDATE())
      SELECT @local_time_sz = SUBSTRING(CONVERT(VARCHAR, @current_datetime, 8), 1, 5)
      SELECT @local_time = CONVERT(INT, LTRIM(SUBSTRING(@local_time_sz, 1, PATINDEX('%:%', @local_time_sz) - 1)  + SUBSTRING(@local_time_sz, PATINDEX('%:%', @local_time_sz) + 1, 2)))
      SELECT @local_date = CONVERT(INT, CONVERT(VARCHAR, @current_datetime, 112))

      -- Set the date
      SELECT @year_sz = CONVERT(NVARCHAR, @local_date / 10000)
      SELECT @month_sz = CONVERT(NVARCHAR, (@local_date % 10000) / 100)
      SELECT @day_sz = CONVERT(NVARCHAR, @local_date % 100)

      IF (@date_format LIKE N'y%m%d')
        SELECT @cmd = N'DATE ' + @year_sz + N'-' + @month_sz + N'-' + @day_sz
      IF (@date_format LIKE N'y%d%m')
        SELECT @cmd = N'DATE ' + @year_sz + N'-' + @day_sz + N'-' + @month_sz
      IF (@date_format LIKE N'm%d%y')
        SELECT @cmd = N'DATE ' + @month_sz + N'-' + @day_sz + N'-' + @year_sz
      IF (@date_format LIKE N'd%m%y')
        SELECT @cmd = N'DATE ' + @day_sz + N'-' + @month_sz + N'-' + @year_sz

      EXECUTE @ret = master.dbo.xp_cmdshell @cmd, no_output
      IF (@ret <> 0)
        RETURN 1 -- Failure

      -- Set the time (NOTE: We can't set the millisecond part of the time, so we may be up to .999 sec off)
      SELECT @cmd = N'TIME ' + CONVERT(NVARCHAR, @local_time / 100) + N':' + CONVERT(NVARCHAR, @local_time % 100) + ':' + CONVERT(NVARCHAR(2), DATEPART(SS, GETDATE()))
      EXECUTE @ret = master.dbo.xp_cmdshell @cmd, no_output
      IF (@ret <> 0)
        RETURN 1 -- Failure
    END

  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_MULTI_SERVER_JOB_SUMMARY [used by SEM only]             */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_multi_server_job_summary...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_multi_server_job_summary')
              AND (type = 'P')))
  DROP PROCEDURE sp_multi_server_job_summary
go
CREATE PROCEDURE sp_multi_server_job_summary
  @job_id   UNIQUEIDENTIFIER = NULL,
  @job_name sysname          = NULL
AS
BEGIN
  DECLARE @retval INT

  SET NOCOUNT ON

  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- NOTE: We join with syscategories - not sysjobservers - since we want to include jobs
  --       which are of type multi-server but which don't currently have any servers
  SELECT 'job_id'   = sj.job_id,
         'job_name' = sj.name,
         'enabled'  = sj.enabled,
         'category_name'  = sc.name,
         'target_servers' = (SELECT COUNT(*)
                             FROM msdb.dbo.sysjobservers sjs
                             WHERE (sjs.job_id = sj.job_id)),
         'pending_download_instructions' = (SELECT COUNT(*)
                                            FROM msdb.dbo.sysdownloadlist sdl
                                            WHERE (sdl.object_id = sj.job_id)
                                              AND (status = 0)),
         'download_errors' = (SELECT COUNT(*)
                              FROM msdb.dbo.sysdownloadlist sdl
                              WHERE (sdl.object_id = sj.job_id)
                                AND (sdl.error_message IS NOT NULL)),
         'execution_failures' = (SELECT COUNT(*)
                                 FROM msdb.dbo.sysjobservers sjs
                                 WHERE (sjs.job_id = sj.job_id)
                                   AND (sjs.last_run_date <> 0)
                                   AND (sjs.last_run_outcome <> 1)) -- 1 is success
  FROM msdb.dbo.sysjobs sj,
       msdb.dbo.syscategories sc
  WHERE (sj.category_id = sc.category_id)
    AND (sc.category_class = 1) -- JOB
    AND (sc.category_type  = 2) -- Multi-Server
    AND ((@job_id IS NULL)   OR (sj.job_id = @job_id))
    AND ((@job_name IS NULL) OR (sj.name = @job_name))

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_TARGET_SERVER_SUMMARY [used by SEM only]                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_target_server_summary...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_target_server_summary')
              AND (type = 'P')))
  DROP PROCEDURE sp_target_server_summary
go
CREATE PROCEDURE sp_target_server_summary
  @target_server sysname = NULL
AS
BEGIN
  SET NOCOUNT ON

  SELECT server_id,
         server_name,
        'local_time' = DATEADD(SS, DATEDIFF(SS, last_poll_date, GETDATE()), local_time_at_last_poll),
         last_poll_date,
        'unread_instructions' = (SELECT COUNT(*)
                                 FROM msdb.dbo.sysdownloadlist sdl
                                 WHERE (UPPER(sdl.target_server) = UPPER(sts.server_name))
                                   AND (sdl.status = 0)),
        'blocked' = (SELECT COUNT(*)
                     FROM msdb.dbo.sysdownloadlist sdl
                     WHERE (UPPER(sdl.target_server) = UPPER(sts.server_name))
                       AND (sdl.error_message IS NOT NULL)),
         poll_interval
  FROM msdb.dbo.systargetservers sts
  WHERE ((@target_server IS NULL) OR (UPPER(@target_server) = UPPER(sts.server_name)))
END
go

CHECKPOINT
go


/**************************************************************/
/*                                                            */
/*         6  .  X     P  R  O  C  E  D  U  R  E  S           */
/*                                                            */
/* These procedures are provided for backwards compatability  */
/* with 6.x scripts and 6.x replication.  The re-implemented  */
/* procedures are as follows:                                 */
/*                                                            */
/* - sp_uniquetaskname  (SQLDMO)                              */
/* - systasks_view      (INSTDIST.SQL)                        */
/* - sp_addtask         (INSTREPL.SQL, INSTDIST.SQL, SQLDMO)  */
/* - sp_droptask        (INSTREPL.SQL, INSTDIST.SQL, SQLDMO)  */
/* - systasks           (For completeness only)               */
/**************************************************************/


/**************************************************************/
/* SP_UNIQUETASKNAME                                          */
/**************************************************************/

PRINT ''
PRINT 'Creating [legacy] procedure sp_uniquetaskname...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_uniquetaskname')
              AND (type = 'P')))
  DROP PROCEDURE sp_uniquetaskname
go
CREATE PROCEDURE sp_uniquetaskname
  @seed NVARCHAR(92)
AS
BEGIN
  DECLARE @newest_suffix INT

  SET NOCOUNT ON

  -- We're going to add a suffix of 8 characters so make sure the seed is at most 84 characters
  SELECT @seed = LTRIM(RTRIM(@seed))
  IF (DATALENGTH(@seed) > 0)
    SELECT @seed = SUBSTRING(@seed, 1, 84)

  -- Find the newest (highest) suffix so far
  SELECT @newest_suffix = MAX(CONVERT(INT, RIGHT(name, 8)))
  FROM msdb.dbo.sysjobs -- DON'T use sysjobs_view here!
  WHERE (name LIKE N'%[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]')

  -- Generate the task name by appending the 'newest suffix' value (plus one) to the seed
  IF (@newest_suffix IS NOT NULL)
  BEGIN
    SELECT @newest_suffix = @newest_suffix + 1
    SELECT 'TaskName' = CONVERT(NVARCHAR(92), @seed + REPLICATE(N'0', 8 - (DATALENGTH(CONVERT(NVARCHAR, @newest_suffix)) / 2)) + CONVERT(NVARCHAR, @newest_suffix))
  END
  ELSE
    SELECT 'TaskName' = CONVERT(NVARCHAR(92), @seed + N'00000001')
END
go

/**************************************************************/
/* SP_ADDTASK                                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating [legacy] procedure sp_addtask...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_addtask')
              AND (type = 'P')))
  DROP PROCEDURE sp_addtask
go
CREATE PROCEDURE sp_addtask
  @name                   sysname,               -- Was VARCHAR(100) in 6.x
  @subsystem              NVARCHAR(40)   = N'TSQL', -- Was VARCHAR(30) in 6.x
  @server                 sysname        = NULL,
  @username               sysname        = NULL, -- Was VARCHAR(30) in 6.x
  @databasename           sysname        = NULL, -- Was VARCHAR(30) in 6.x
  @enabled                TINYINT        = 0,
  @freqtype               INT            = 2,    -- 2 means OnDemand
  @freqinterval           INT            = 1,
  @freqsubtype            INT            = 1,
  @freqsubinterval        INT            = 1,
  @freqrelativeinterval   INT            = 1,
  @freqrecurrencefactor   INT            = 1,
  @activestartdate        INT            = 0,
  @activeenddate          INT            = 0,
  @activestarttimeofday   INT            = 0,
  @activeendtimeofday     INT            = 0,
  @nextrundate            INT            = 0,
  @nextruntime            INT            = 0,
  @runpriority            INT            = 0,
  @emailoperatorname      sysname        = NULL, -- Was VARCHAR(50) in 6.x
  @retryattempts          INT            = 0,
  @retrydelay             INT            = 10,
  @command                NVARCHAR(max) = NULL,
  @loghistcompletionlevel INT            = 2,
  @emailcompletionlevel   INT            = 0,
  @description            NVARCHAR(512)  = NULL, -- Was VARCHAR(255) in 6.x
  @tagadditionalinfo      VARCHAR(96)    = NULL, -- Obsolete in 7.0
  @tagobjectid            INT            = NULL, -- Obsolete in 7.0
  @tagobjecttype          INT            = NULL, -- Obsolete in 7.0
  @newid                  INT            = NULL OUTPUT,
  @parameters             NVARCHAR(max)  = NULL, -- Was TEXT in 6.x
  @cmdexecsuccesscode     INT            = 0,
  @category_name          sysname        = NULL, -- New for 7.0
  @category_id            INT            = NULL  -- New for 7.0
AS
BEGIN
  DECLARE @retval INT
  DECLARE @job_id UNIQUEIDENTIFIER
  DECLARE @id     INT
  DECLARE @distdb sysname
  DECLARE @proc nvarchar(255)

  SET NOCOUNT ON

  SELECT @retval = 1 -- 0 means success, 1 means failure

  -- Set 7.0 category names for 6.5 replication tasks
  IF (LOWER(@subsystem) = N'sync')
    SELECT @category_id = 15
  ELSE IF (LOWER(@subsystem) = N'logreader')
    SELECT @category_id = 13
  ELSE IF (LOWER(@subsystem) = N'distribution')
    SELECT @category_id = 10

  -- Convert old replication synchronization subsystem name to the 7.0 name
  IF (LOWER(@subsystem) = N'sync')
    SELECT @subsystem = N'Snapshot'

  -- If a category ID is provided this overrides any supplied category name
  IF (@category_id IS NOT NULL)
  BEGIN
    SELECT @category_name = name
    FROM msdb.dbo.syscategories
    WHERE (category_id = @category_id)
    SELECT @category_name = ISNULL(@category_name, FORMATMESSAGE(14205))
  END

  -- In 6.x active start date was not restricted, but it is in 7.0; so to avoid a "noisey"
  -- failure in sp_add_jobschedule we modify the value accordingly
  IF ((@activestartdate <> 0) AND (@activestartdate < 19900101))
    SELECT @activestartdate = 19900101

  BEGIN TRANSACTION

    -- Add the job
    EXECUTE @retval = sp_add_job
      @job_name                   = @name,
      @enabled                    = @enabled,
      @start_step_id              = 1,
      @description                = @description,
      @category_name              = @category_name,
      @notify_level_eventlog      = @loghistcompletionlevel,
      @notify_level_email         = @emailcompletionlevel,
      @notify_email_operator_name = @emailoperatorname,
      @job_id                     = @job_id OUTPUT

    IF (@retval <> 0)
    BEGIN
      ROLLBACK TRANSACTION
      GOTO Quit
    END

    -- Add an entry to systaskids for the new job (created by a 6.x client)
    INSERT INTO msdb.dbo.systaskids (job_id) VALUES (@job_id)

    -- Get the assigned task id
    SELECT @id = task_id, @newid = task_id
    FROM msdb.dbo.systaskids
    WHERE (job_id = @job_id)

    -- Add the job step
    EXECUTE @retval = sp_add_jobstep
      @job_id                = @job_id,
      @step_id               = 1,
      @step_name             = N'Step 1',
      @subsystem             = @subsystem,
      @command               = @command,
      @additional_parameters = @parameters,
      @cmdexec_success_code  = @cmdexecsuccesscode,
      @server                = @server,
      @database_name         = @databasename,
      @database_user_name    = @username,
      @retry_attempts        = @retryattempts,
      @retry_interval        = @retrydelay,
      @os_run_priority       = @runpriority

    IF (@retval <> 0)
    BEGIN
      ROLLBACK TRANSACTION
      GOTO Quit
    END

    -- Add the job schedule
    IF (@activestartdate = 0)
      SELECT @activestartdate = NULL
    IF (@activeenddate = 0)
      SELECT @activeenddate = NULL
    IF (@activestarttimeofday = 0)
      SELECT @activestarttimeofday = NULL
    IF (@activeendtimeofday = 0)
      SELECT @activeendtimeofday = NULL
    IF (@freqtype <> 0x2) -- OnDemand tasks simply have no schedule in 7.0
    BEGIN
      EXECUTE @retval = sp_add_jobschedule
        @job_id                 = @job_id,
        @name                   = N'6.x schedule',
        @enabled                = 1,
        @freq_type              = @freqtype,
        @freq_interval          = @freqinterval,
        @freq_subday_type       = @freqsubtype,
        @freq_subday_interval   = @freqsubinterval,
        @freq_relative_interval = @freqrelativeinterval,
        @freq_recurrence_factor = @freqrecurrencefactor,
        @active_start_date      = @activestartdate,
        @active_end_date        = @activeenddate,
        @active_start_time      = @activestarttimeofday,
        @active_end_time        = @activeendtimeofday

      IF (@retval <> 0)
      BEGIN
        ROLLBACK TRANSACTION
        GOTO Quit
      END
    END

    -- And finally, add the job server
    EXECUTE @retval = sp_add_jobserver @job_id = @job_id, @server_name = NULL

    IF (@retval <> 0)
    BEGIN
      ROLLBACK TRANSACTION
      GOTO Quit
    END

    -- Add the replication agent for monitoring
    IF (@category_id = 13) -- Logreader
    BEGIN
      SELECT @distdb = distribution_db from MSdistpublishers where name = @server
      SELECT @proc = @distdb + '.dbo.sp_MSadd_logreader_agent'

      EXECUTE @retval = @proc
        @name = @name,
        @publisher = @server,
        @publisher_db = @databasename,
        @publication = '',
        @local_job = 1,
        @job_existing = 1,
        @job_id = @job_id

      IF (@retval <> 0)
      BEGIN
        ROLLBACK TRANSACTION
        GOTO Quit
      END
    END
    ELSE
    IF (@category_id = 15) -- Snapshot
    BEGIN
      DECLARE @publication sysname

      EXECUTE @retval = master.dbo.sp_MSget_publication_from_taskname
                            @taskname = @name,
                            @publisher = @server,
                            @publisherdb = @databasename,
                            @publication = @publication OUTPUT

      IF (@publication IS NOT NULL)
      BEGIN

        SELECT @distdb = distribution_db from MSdistpublishers where name = @server
        SELECT @proc = @distdb + '.dbo.sp_MSadd_snapshot_agent'

        EXECUTE @retval = @proc
                @name = @name,
                @publisher = @server,
                @publisher_db = @databasename,
                @publication = @publication,
                @local_job = 1,
                @job_existing = 1,
                @snapshot_jobid = @job_id

        IF (@retval <> 0)
        BEGIN
          ROLLBACK TRANSACTION
          GOTO Quit
        END

        SELECT @proc = @distdb + '.dbo.sp_MSadd_publication'
        EXECUTE @retval = @proc
                @publisher = @server,
                @publisher_db = @databasename,
                @publication = @publication,
                @publication_type = 0 -- Transactional
        IF (@retval <> 0)
        BEGIN
          ROLLBACK TRANSACTION
          GOTO Quit
        END
      END
    END

  COMMIT TRANSACTION

  -- If this is an autostart LogReader or Distribution job, add the [new] '-Continuous' paramter to the command
  IF (@freqtype = 0x40) AND ((UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = N'LOGREADER') OR (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = N'DISTRIBUTION'))
  BEGIN
    UPDATE msdb.dbo.sysjobsteps
    SET command = command + N' -Continuous'
    WHERE (job_id = @job_id)
      AND (step_id = 1)
  END

  -- If this is an autostart job, start it now (for backwards compatibility with 6.x SQLExecutive behaviour)
  IF (@freqtype = 0x40)
    EXECUTE msdb.dbo.sp_start_job @job_id = @job_id, @error_flag = 0, @output_flag = 0

Quit:
  RETURN(@retval) -- 0 means success

END
go


/**************************************************************/
/* SP_DROPTASK                                                */
/**************************************************************/

PRINT ''
PRINT 'Creating [legacy] procedure sp_droptask...'

go

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_droptask')
              AND (type = 'P')))
  DROP PROCEDURE sp_droptask

go

CREATE PROCEDURE sp_droptask
  @name      sysname = NULL, -- Was VARCHAR(100) in 6.x
  @loginname sysname = NULL, -- Was VARCHAR(30) in 6.x
  @id        INT     = NULL
AS
BEGIN
  DECLARE @retval INT
  DECLARE @job_id UNIQUEIDENTIFIER
  DECLARE @category_id int

  SET NOCOUNT ON

  IF ((@name      IS NULL)     AND (@id    IS NULL)     AND (@loginname IS NULL)) OR
     ((@name      IS NOT NULL) AND ((@id   IS NOT NULL) OR  (@loginname IS NOT NULL))) OR
     ((@id        IS NOT NULL) AND ((@name IS NOT NULL) OR  (@loginname IS NOT NULL))) OR
     ((@loginname IS NOT NULL) AND ((@name IS NOT NULL) OR  (@id        IS NOT NULL)))
  BEGIN
    RAISERROR(14245, -1, -1)
    RETURN(1) -- Failure
  END

  -- If the name is supplied, get the job_id directly from sysjobs
  IF (@name IS NOT NULL)
  BEGIN
    -- Check if the name is ambiguous
    IF ((SELECT COUNT(*)
         FROM msdb.dbo.sysjobs_view
         WHERE (name = @name)) > 1)
    BEGIN
      RAISERROR(14292, -1, -1, @name, '@id', '@name')
      RETURN(1) -- Failure
    END

    SELECT @job_id = job_id, @category_id = category_id
    FROM msdb.dbo.sysjobs_view
    WHERE (name = @name)

    SELECT @id = task_id
    FROM msdb.dbo.systaskids
    WHERE (job_id = @job_id)

    IF (@job_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@name', @name)
      RETURN(1) -- Failure
    END
  END

  -- If the id is supplied lookup the corresponding job_id from systaskids
  IF (@id IS NOT NULL)
  BEGIN
    SELECT @job_id = job_id
    FROM msdb.dbo.systaskids
    WHERE (task_id = @id)

    -- Check that the job still exists
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysjobs_view
                    WHERE (job_id = @job_id)))
    BEGIN
      SELECT @name = CONVERT(NVARCHAR, @id)
      RAISERROR(14262, -1, -1, '@id', @name)
      RETURN(1) -- Failure
    END

    -- Get the name of this job
    SELECT @name = name, @category_id = category_id
    FROM msdb.dbo.sysjobs_view
    WHERE (job_id = @job_id)
  END

  -- Delete the specific job
  IF (@name IS NOT NULL)
  BEGIN
    BEGIN TRANSACTION

    DELETE FROM msdb.dbo.systaskids
    WHERE (job_id = @job_id)
    EXECUTE @retval = sp_delete_job @job_id = @job_id
    IF (@retval <> 0)
   BEGIN
      ROLLBACK TRANSACTION
     GOTO Quit
   END

   -- If a Logreader or Snapshot task, delete corresponding replication agent information
   IF @category_id = 13 or @category_id = 15
   BEGIN
        EXECUTE @retval = sp_MSdrop_6x_replication_agent @job_id, @category_id
     IF (@retval <> 0)
     BEGIN
      ROLLBACK TRANSACTION
      GOTO Quit
     END
   END

    COMMIT TRANSACTION
  END

  -- Delete all jobs belonging to the specified login
  IF (@loginname IS NOT NULL)
  BEGIN
    BEGIN TRANSACTION

    DELETE FROM msdb.dbo.systaskids
    WHERE job_id IN (SELECT job_id
                     FROM msdb.dbo.sysjobs_view
                     WHERE (owner_sid = SUSER_SID(@loginname)))
    EXECUTE @retval = sp_manage_jobs_by_login @action = 'DELETE',
                                              @current_owner_login_name = @loginname
    IF (@retval <> 0)
    BEGIN
      ROLLBACK TRANSACTION
      GOTO Quit
    END     

    COMMIT TRANSACTION
  END

Quit:
  RETURN(@retval) -- 0 means success

END
go



/**************************************************************/
/*                                                            */
/*         E  R  R  O  R    M  E  S  S  A  G  E  S            */
/*                                                            */
/*  These are now created by MESSAGES.SQL.                    */
/*                                                            */
/*  NOTE: 14255 and 14265 are called by dynamic SQL generated */
/*        by SQLServerAgent.                                  */
/**************************************************************/


/**************************************************************/
/*                                                            */
/*                   T  R  I  G  G  E  R  S                   */
/*                                                            */
/**************************************************************/

/**************************************************************/
/* TRIG_TARGETSERVER_INSERT                                   */
/**************************************************************/

PRINT ''
PRINT 'Creating trigger trig_targetserver_insert...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_targetserver_insert')
              AND (type = 'TR')))
  DROP TRIGGER dbo.trig_targetserver_insert
go
CREATE TRIGGER trig_targetserver_insert
ON msdb.dbo.systargetservers
FOR INSERT, DELETE
AS
BEGIN
  SET NOCOUNT ON

  -- Disallow the insert if the server is called 'ALL'
  -- NOTE: We have to do this check here in the trigger since there is no sp_add_targetserver
  --       (target servers insert a row for themselves when they 'enlist' in an MSX)
  IF (EXISTS (SELECT *
              FROM inserted
              WHERE (server_name = N'ALL')))
  BEGIN
    DELETE FROM msdb.dbo.systargetservers
    WHERE (server_name = N'ALL')
    RAISERROR(14271, -1, -1, 'ALL')
    RETURN
  END

  -- Set (or delete) the registy flag (so that SETUP can detect if we're an MSX)
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.systargetservers) = 0)
  BEGIN
    DECLARE @val INT

    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'MSXServer',
                                           @val OUTPUT,
                                           N'no_output'
    IF (@val IS NOT NULL)
      EXECUTE master.dbo.xp_instance_regdeletevalue N'HKEY_LOCAL_MACHINE',
                                                    N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                                    N'MSXServer'
  END
  ELSE
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'MSXServer',
                                            N'REG_DWORD',
                                            1
END
go

CHECKPOINT
go



/**************************************************************/
/**                                                          **/
/**          A L E R T S  A N D  O P E R A T O R S           **/
/**                                                          **/
/**************************************************************/

/**************************************************************/
/*                                                            */
/*        C  O  R  E     P  R  O  C  E  D  U  R  E  S         */
/*                                                            */
/**************************************************************/


/**************************************************************/
/* SP_ADD_ALERT_INTERNAL                                      */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_alert_internal...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_alert_internal')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_alert_internal
go
CREATE PROCEDURE sp_add_alert_internal
  @name                         sysname,
  @message_id                   INT              = 0,
  @severity                     INT              = 0,
  @enabled                      TINYINT          = 1,
  @delay_between_responses      INT              = 0,
  @notification_message         NVARCHAR(512)    = NULL,
  @include_event_description_in TINYINT          = 5,    -- 0 = None, 1 = Email, 2 = Pager, 4 = NetSend, 7 = All
  @database_name                sysname          = NULL,
  @event_description_keyword    NVARCHAR(100)    = NULL,
  @job_id                       UNIQUEIDENTIFIER = NULL, -- If provided must NOT also provide job_name
  @job_name                     sysname          = NULL, -- If provided must NOT also provide job_id
  @raise_snmp_trap              TINYINT          = 0,
  @performance_condition        NVARCHAR(512)    = NULL, -- New for 7.0
  @category_name                sysname          = NULL, -- New for 7.0
 @wmi_namespace                NVARCHAR(512)     = NULL, -- New for 9.0
  @wmi_query                    NVARCHAR(512)     = NULL, -- New for 9.0
  @verify_alert                    TINYINT             = 1     -- 0 = do not verify alert, 1(or anything else) = verify alert before adding
AS
BEGIN
  DECLARE @event_source           NVARCHAR(100)
  DECLARE @event_category_id      INT
  DECLARE @event_id               INT
  DECLARE @last_occurrence_date   INT
  DECLARE @last_occurrence_time   INT
  DECLARE @last_notification_date INT
  DECLARE @last_notification_time INT
  DECLARE @occurrence_count       INT
  DECLARE @count_reset_date       INT
  DECLARE @count_reset_time       INT
  DECLARE @has_notification       INT
  DECLARE @return_code            INT
  DECLARE @duplicate_name         sysname
  DECLARE @category_id            INT
  DECLARE @alert_id               INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name                      = LTRIM(RTRIM(@name))
  SELECT @notification_message      = LTRIM(RTRIM(@notification_message))
  SELECT @database_name             = LTRIM(RTRIM(@database_name))
  SELECT @event_description_keyword = LTRIM(RTRIM(@event_description_keyword))
  SELECT @job_name                  = LTRIM(RTRIM(@job_name))
  SELECT @performance_condition     = LTRIM(RTRIM(@performance_condition))
  SELECT @category_name             = LTRIM(RTRIM(@category_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@notification_message      = N'') SELECT @notification_message = NULL
  IF (@database_name             = N'') SELECT @database_name = NULL
  IF (@event_description_keyword = N'') SELECT @event_description_keyword = NULL
  IF (@job_name                  = N'') SELECT @job_name = NULL
  IF (@performance_condition     = N'') SELECT @performance_condition = NULL
  IF (@category_name             = N'') SELECT @category_name = NULL

  SELECT @message_id = ISNULL(@message_id, 0)
  SELECT @severity = ISNULL(@severity, 0)

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check if SQLServerAgent is in the process of starting
  EXECUTE @return_code = msdb.dbo.sp_is_sqlagent_starting
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  -- Hard-code the new Alert defaults
  -- event source needs to be instance aware
  DECLARE @instance_name sysname
  SELECT @instance_name = CONVERT (sysname, SERVERPROPERTY ('InstanceName'))
  IF (@instance_name IS NULL OR @instance_name = N'MSSQLSERVER')
    SELECT @event_source  = N'MSSQLSERVER'
  ELSE
    SELECT @event_source  = N'MSSQL$' + @instance_name

  SELECT @event_category_id = NULL
  SELECT @event_id = NULL
  SELECT @last_occurrence_date = 0
  SELECT @last_occurrence_time = 0
  SELECT @last_notification_date = 0
  SELECT @last_notification_time = 0
  SELECT @occurrence_count = 0
  SELECT @count_reset_date = 0
  SELECT @count_reset_time = 0
  SELECT @has_notification = 0
  
  IF (@category_name IS NULL)
  BEGIN
    --Default category_id for alerts
    SELECT @category_id = 98

    SELECT @category_name = name
    FROM msdb.dbo.syscategories
    WHERE (category_id = 98)
  END

  -- Map a job_id of 0 to the real value we use to mean 'no job'
  IF (@job_id = CONVERT(UNIQUEIDENTIFIER, 0x00)) AND (@job_name IS NULL)
    SELECT @job_name = N''

  -- Verify the Alert if @verify_alert <> 0
  IF (@verify_alert <> 0)
  BEGIN
    IF (@job_id = CONVERT(UNIQUEIDENTIFIER, 0x00))
        SELECT @job_id = NULL
    EXECUTE @return_code = sp_verify_alert @name,
                                            @message_id,
                                            @severity,
                                            @enabled,
                                            @delay_between_responses,
                                            @notification_message,
                                            @include_event_description_in,
                                            @database_name,
                                            @event_description_keyword,
                                            @job_id OUTPUT,
                                            @job_name OUTPUT,
                                            @occurrence_count,
                                            @raise_snmp_trap,
                                            @performance_condition,
                                            @category_name,
                                            @category_id OUTPUT,
                                            @count_reset_date,
                                            @count_reset_time,
                                            @wmi_namespace,
                                            @wmi_query,
                                            @event_id OUTPUT
    IF (@return_code <> 0)
    BEGIN
        RETURN(1) -- Failure
    END
  END

  -- For WMI alerts replace 
  -- database_name with wmi_namespace and 
  -- performance_conditon with wmi_query
  -- so we can store them in those columns in sysalerts table
  IF (@event_id = 8)
  BEGIN
    SELECT @database_name = @wmi_namespace
    SELECT @performance_condition = @wmi_query
  END
  
  -- Check if this Alert already exists
  SELECT @duplicate_name = FORMATMESSAGE(14205)
  SELECT @duplicate_name = name
  FROM msdb.dbo.sysalerts
  WHERE ((event_id = 8) AND 
       (ISNULL(performance_condition, N'') = ISNULL(@performance_condition, N'')) AND
       (ISNULL(database_name, N'') = ISNULL(@database_name, N''))) OR
      ((ISNULL(event_id,1) <> 8) AND 
       (ISNULL(performance_condition, N'apples') = ISNULL(@performance_condition, N'oranges'))) OR 
      ((performance_condition IS NULL) AND
         (message_id = @message_id) AND
         (severity = @severity) AND
         (ISNULL(database_name, N'') = ISNULL(@database_name, N'')) AND
         (ISNULL(event_description_keyword, N'') = ISNULL(@event_description_keyword, N'')))
  IF (@duplicate_name <> FORMATMESSAGE(14205))
  BEGIN
    RAISERROR(14501, 16, 1, @duplicate_name)
    RETURN(1) -- Failure
  END
  
  -- Finally, do the actual INSERT
  INSERT INTO msdb.dbo.sysalerts
         (name,
          event_source,
          event_category_id,
          event_id,
          message_id,
          severity,
          enabled,
          delay_between_responses,
          last_occurrence_date,
          last_occurrence_time,
          last_response_date,
          last_response_time,
          notification_message,
          include_event_description,
          database_name,
          event_description_keyword,
          occurrence_count,
          count_reset_date,
          count_reset_time,
          job_id,
          has_notification,
          flags,
          performance_condition,
          category_id)
  VALUES (@name,
          @event_source,
          @event_category_id,
          @event_id,
          @message_id,
          @severity,
          @enabled,
          @delay_between_responses,
          @last_occurrence_date,
          @last_occurrence_time,
          @last_notification_date,
          @last_notification_time,
          @notification_message,
          @include_event_description_in,
          @database_name,
          @event_description_keyword,
          @occurrence_count,
          @count_reset_date,
          @count_reset_time,
          ISNULL(@job_id, CONVERT(UNIQUEIDENTIFIER, 0x00)),
          @has_notification,
          @raise_snmp_trap,
          @performance_condition,
          @category_id)

  -- Notify SQLServerAgent of the change
  SELECT @alert_id = id
  FROM msdb.dbo.sysalerts
  WHERE (name = @name)
  EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'A',
                                      @alert_id    = @alert_id,
                                      @action_type = N'I'
  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_ADD_ALERT                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_alert...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_alert')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_alert
go
CREATE PROCEDURE sp_add_alert
  @name                         sysname,
  @message_id                   INT              = 0,
  @severity                     INT              = 0,
  @enabled                      TINYINT          = 1,
  @delay_between_responses      INT              = 0,
  @notification_message         NVARCHAR(512)    = NULL,
  @include_event_description_in TINYINT          = 5,    -- 0 = None, 1 = Email, 2 = Pager, 4 = NetSend, 7 = All
  @database_name                sysname          = NULL,
  @event_description_keyword    NVARCHAR(100)    = NULL,
  @job_id                       UNIQUEIDENTIFIER = NULL, -- If provided must NOT also provide job_name
  @job_name                     sysname          = NULL, -- If provided must NOT also provide job_id
  @raise_snmp_trap              TINYINT          = 0,
  @performance_condition        NVARCHAR(512)    = NULL, -- New for 7.0
  @category_name                sysname          = NULL, -- New for 7.0
  @wmi_namespace                sysname             = NULL, -- New for 9.0
  @wmi_query                    NVARCHAR(512)     = NULL  -- New for 9.0
AS
BEGIN
  DECLARE @verify_alert         INT
  
  --Always verify alerts before adding
  SELECT @verify_alert = 1

  EXECUTE msdb.dbo.sp_add_alert_internal @name,
                                         @message_id,
                                         @severity,
                                         @enabled,
                                         @delay_between_responses,
                                         @notification_message,
                                         @include_event_description_in,
                                         @database_name,
                                         @event_description_keyword,
                                         @job_id,
                                         @job_name,
                                         @raise_snmp_trap,
                                         @performance_condition,
                                         @category_name,
                                         @wmi_namespace,
                                         @wmi_query,
                                         @verify_alert
END
GO


/**************************************************************/
/* SP_DELETE_ALERT                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_alert...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_alert')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_alert
go
CREATE PROCEDURE sp_delete_alert
  @name sysname
AS
BEGIN
  DECLARE @alert_id    INT
  DECLARE @return_code INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name = LTRIM(RTRIM(@name))

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check if SQLServerAgent is in the process of starting
  EXECUTE @return_code = msdb.dbo.sp_is_sqlagent_starting
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  -- Check if this Alert exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysalerts
                  WHERE (name = @name)))
  BEGIN
    RAISERROR(14262, 16, 1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Convert the Name to it's ID
  SELECT @alert_id = id
  FROM msdb.dbo.sysalerts
  WHERE (name = @name)

  BEGIN TRANSACTION

    -- Delete sysnotifications entries
    DELETE FROM msdb.dbo.sysnotifications
    WHERE (alert_id = @alert_id)

    -- Finally, do the actual DELETE
    DELETE FROM msdb.dbo.sysalerts
    WHERE (id = @alert_id)

  COMMIT TRANSACTION

  -- Notify SQLServerAgent of the change
  EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'A',
                                      @alert_id    = @alert_id,
                                      @action_type = N'D'
  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_HELP_ALERT                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_alert...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_alert')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_alert
go
CREATE PROCEDURE sp_help_alert
  @alert_name    sysname = NULL,
  @order_by      sysname = N'name',
  @alert_id      INT     = NULL,
  @category_name sysname = NULL,
  @legacy_format BIT  = 0 
AS
BEGIN
  DECLARE @alert_id_as_char NVARCHAR(10)
  DECLARE @escaped_alert_name NVARCHAR(256) -- double sysname
  DECLARE @escaped_category_name NVARCHAR(256) -- double sysname
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @alert_name    = LTRIM(RTRIM(@alert_name))
  SELECT @order_by      = LTRIM(RTRIM(@order_by))
  SELECT @category_name = LTRIM(RTRIM(@category_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@category_name = N'') SELECT @category_name = NULL
  IF (@alert_name = N'')    SELECT @alert_name = NULL

  -- Check alert name
  IF (@alert_name IS NOT NULL)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysalerts
                    WHERE (name = @alert_name)))
    BEGIN
      RAISERROR(14262, -1, -1, '@alert_name', @alert_name)
      RETURN(1) -- Failure
    END
  END

  -- Check alert id
  IF (@alert_id IS NOT NULL)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysalerts
                    WHERE (id = @alert_id)))
    BEGIN
      SELECT @alert_id_as_char = CONVERT(VARCHAR, @alert_id)
      RAISERROR(14262, -1, -1, '@alert_id', @alert_id_as_char)
      RETURN(1) -- Failure
    END
  END

  IF (@alert_id IS NOT NULL)
    SELECT @alert_id_as_char = CONVERT(VARCHAR, @alert_id)
  ELSE
    SELECT @alert_id_as_char = N'NULL'

  -- Double up any single quotes in @alert_name
  IF (@alert_name IS NOT NULL)
    SELECT @escaped_alert_name = REPLACE(@alert_name, N'''', N'''''')

  -- Double up any single quotes in @category_name
  IF (@category_name IS NOT NULL)
    SELECT @escaped_category_name = REPLACE(@category_name, N'''', N'''''')

  IF (@legacy_format <> 0)
  BEGIN
    
     -- @order_by parameter validation. 
     IF  ( (@order_by IS NOT NULL) AND 
           (EXISTS(SELECT so.object_id FROM msdb.sys.objects so 
                      JOIN msdb.sys.columns sc ON (so.object_id = sc.object_id) 
                   WHERE so.type='U' AND so.name='sysalerts' 
                                     AND LOWER(sc.name collate SQL_Latin1_General_CP1_CS_AS)=LOWER(@order_by collate SQL_Latin1_General_CP1_CS_AS)
                  )
          ) )
     BEGIN
       SELECT @order_by = N'sa.' + @order_by
     END
     ELSE 
     BEGIN
        IF (LOWER(@order_by collate SQL_Latin1_General_CP1_CS_AS) NOT IN ( N'job_name', N'category_name', N'type' ) )
           AND --special "order by" clause used only by sqlagent. if you change it you need to change agent too
           (@order_by <> N'event_id DESC, severity ASC, message_id ASC, database_name DESC') 
           AND
           (@order_by <> N'severity ASC, message_id ASC, database_name DESC')
        BEGIN
          RAISERROR(18750, -1, -1, 'sp_help_alert', '@order_by')
          RETURN(1) -- Failure
        END
     END
    
    -- Old query version (for SQL Server 2000 and older servers)
    -- database_name and performance_conditions are reported
    -- directly from sysalerts columns
    EXECUTE (N'SELECT sa.id,
               sa.name,
                    sa.event_source,
                    sa.event_category_id,
                    sa.event_id,
                    sa.message_id,
                    sa.severity,
                    sa.enabled,
                    sa.delay_between_responses,
                    sa.last_occurrence_date,
                    sa.last_occurrence_time,
                    sa.last_response_date,
                    sa.last_response_time,
                    sa.notification_message,
                    sa.include_event_description,
                    sa.database_name,
                    sa.event_description_keyword,
                    sa.occurrence_count,
                    sa.count_reset_date,
                    sa.count_reset_time,
                    sjv.job_id,
                    job_name = sjv.name,
                    sa.has_notification,
                    sa.flags,
                    sa.performance_condition,
                    category_name = sc.name,
                    type = CASE ISNULL(sa.performance_condition, ''!'')
                  WHEN ''!'' THEN 1            -- SQL Server event alert
                  ELSE CASE sa.event_id
                     WHEN 8 THEN 4          -- WMI event alert
                     ELSE 2                    -- SQL Server performance condition alert
                  END
               END
             FROM msdb.dbo.sysalerts                     sa
                  LEFT OUTER JOIN msdb.dbo.sysjobs_view  sjv ON (sa.job_id = sjv.job_id)
                  LEFT OUTER JOIN msdb.dbo.syscategories sc  ON (sa.category_id = sc.category_id)
             WHERE ((N''' + @escaped_alert_name + N''' = N'''') OR (sa.name = N''' + @escaped_alert_name + N'''))
               AND ((' + @alert_id_as_char + N' IS NULL) OR (sa.id = ' + @alert_id_as_char + N'))
               AND ((N''' + @escaped_category_name + N''' = N'''') OR (sc.name = N''' + @escaped_category_name + N'''))
             ORDER BY ' + @order_by)
  END
  ELSE
  BEGIN

     -- @order_by parameter validation. 
     IF  ( (@order_by IS NOT NULL) AND 
           (EXISTS(SELECT so.object_id FROM msdb.sys.objects so 
                      JOIN msdb.sys.columns sc ON (so.object_id = sc.object_id) 
                   WHERE so.type='U' AND so.name='sysalerts' 
                                     AND LOWER(sc.name collate SQL_Latin1_General_CP1_CS_AS)=LOWER(@order_by collate SQL_Latin1_General_CP1_CS_AS)
                  )
          ) )
     BEGIN
       SELECT @order_by = N'sa.' + @order_by
     END
     ELSE 
     BEGIN
        IF (LOWER(@order_by collate SQL_Latin1_General_CP1_CS_AS) NOT IN (N'database_name', N'job_name', N'performance_condition', N'category_name', N'wmi_namespace', N'wmi_query', N'type' ) )
           AND --special "order by" clause used only by sqlagent. if you change it you need to change agent too
           (@order_by <> N'event_id DESC, severity ASC, message_id ASC, database_name DESC') 
           AND
           (@order_by <> N'severity ASC, message_id ASC, database_name DESC')
        BEGIN
           RAISERROR(18750, -1, -1, 'sp_help_alert', '@order_by')
           RETURN(1) -- Failure
        END
     END

    -- New query version. If alert is a WMI alert 
    -- then database_name is reported as wmi_namespace and
    -- performance_condition is reported as wmi_query.
    -- For other alerts those two new columns are NULL
    EXECUTE (N'SELECT sa.id,
                    sa.name,
                    sa.event_source,
                    sa.event_category_id,
                    sa.event_id,
                    sa.message_id,
                    sa.severity,
                    sa.enabled,
                    sa.delay_between_responses,
                    sa.last_occurrence_date,
                    sa.last_occurrence_time,
                    sa.last_response_date,
                    sa.last_response_time,
                    sa.notification_message,
                    sa.include_event_description,
               database_name = CASE ISNULL(sa.event_id, 1)
                  WHEN 8 THEN NULL
                  ELSE sa.database_name 
               END,
                    sa.event_description_keyword,
                    sa.occurrence_count,
                    sa.count_reset_date,
                    sa.count_reset_time,
                    sjv.job_id,
                    job_name = sjv.name,
                    sa.has_notification,
                    sa.flags,
               performance_condition = CASE ISNULL(sa.event_id, 1)
                  WHEN 8 THEN NULL
                  ELSE sa.performance_condition 
               END,
                    category_name = sc.name,
                    wmi_namespace = CASE ISNULL(sa.event_id, 1)
                  WHEN 8 THEN sa.database_name
                  ELSE NULL
               END,
               wmi_query = CASE ISNULL(sa.event_id, 1)
                  WHEN 8 THEN sa.performance_condition
                  ELSE NULL
               END,
                    type = CASE ISNULL(sa.performance_condition, ''!'')
                  WHEN ''!'' THEN 1            -- SQL Server event alert
                  ELSE CASE sa.event_id
                     WHEN 8 THEN 4          -- WMI event alert
                     ELSE 2                    -- SQL Server performance condition alert
                  END
               END
             FROM msdb.dbo.sysalerts                     sa
                  LEFT OUTER JOIN msdb.dbo.sysjobs_view  sjv ON (sa.job_id = sjv.job_id)
                  LEFT OUTER JOIN msdb.dbo.syscategories sc  ON (sa.category_id = sc.category_id)
             WHERE ((N''' + @escaped_alert_name + N''' = N'''') OR (sa.name = N''' + @escaped_alert_name + N'''))
               AND ((' + @alert_id_as_char + N' IS NULL) OR (sa.id = ' + @alert_id_as_char + N'))
               AND ((N''' + @escaped_category_name + N''' = N'''') OR (sc.name = N''' + @escaped_category_name + N'''))
             ORDER BY ' + @order_by)
  END

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_VERIFY_OPERATOR                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_operator...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_operator')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_operator
go
CREATE PROCEDURE sp_verify_operator
  @name                      sysname,
  @enabled                   TINYINT,
  @pager_days                TINYINT,
  @weekday_pager_start_time  INT,
  @weekday_pager_end_time    INT,
  @saturday_pager_start_time INT,
  @saturday_pager_end_time   INT,
  @sunday_pager_start_time   INT,
  @sunday_pager_end_time     INT,
  @category_name             sysname,
  @category_id               INT OUTPUT
AS
BEGIN
  DECLARE @return_code     TINYINT
  DECLARE @res_valid_range NVARCHAR(100)

  SET NOCOUNT ON

  SELECT @res_valid_range = FORMATMESSAGE(14209)

  -- Remove any leading/trailing spaces from parameters
  SELECT @name          = LTRIM(RTRIM(@name))
  SELECT @category_name = LTRIM(RTRIM(@category_name))

  -- The name must be unique
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysoperators
              WHERE (name = @name)))
  BEGIN
    RAISERROR(14261, 16, 1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Enabled must be 0 or 1
  IF (@enabled NOT IN (0, 1))
  BEGIN
    RAISERROR(14266, 16, 1, '@enabled', '0, 1')
    RETURN(1) -- Failure
  END

  -- Check PagerDays
  IF (@pager_days < 0) OR (@pager_days > 127)
  BEGIN
    RAISERROR(14266, 16, 1, '@pager_days', @res_valid_range)
    RETURN(1) -- Failure
  END

  -- Check Start/End Times
  EXECUTE @return_code = sp_verify_job_time @weekday_pager_start_time, '@weekday_pager_start_time'
  IF (@return_code <> 0)
    RETURN(1)

  EXECUTE @return_code = sp_verify_job_time @weekday_pager_end_time, '@weekday_pager_end_time'
  IF (@return_code <> 0)
    RETURN(1)

  EXECUTE @return_code = sp_verify_job_time @saturday_pager_start_time, '@saturday_pager_start_time'
  IF (@return_code <> 0)
    RETURN(1)

  EXECUTE @return_code = sp_verify_job_time @saturday_pager_end_time, '@saturday_pager_end_time'
  IF (@return_code <> 0)
    RETURN(1)

  EXECUTE @return_code = sp_verify_job_time @sunday_pager_start_time, '@sunday_pager_start_time'
  IF (@return_code <> 0)
    RETURN(1)

  EXECUTE @return_code = sp_verify_job_time @sunday_pager_end_time, '@sunday_pager_end_time'
  IF (@return_code <> 0)
    RETURN(1)

  -- Check category name
  IF (@category_name = N'[DEFAULT]')
    SELECT @category_id = 99
  ELSE
  BEGIN
    SELECT @category_id = category_id
    FROM msdb.dbo.syscategories
    WHERE (category_class = 3) -- Operators
      AND (category_type = 3) -- None
      AND (name = @category_name)
  END
  IF (@category_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@category_name', @category_name)
    RETURN(1) -- Failure
  END

  RETURN(0)
END
go

/**************************************************************/
/* SP_ADD_OPERATOR                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_operator...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_operator')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_operator
go
CREATE PROCEDURE sp_add_operator
  @name                      sysname,
  @enabled                   TINYINT       = 1,
  @email_address             NVARCHAR(100) = NULL,
  @pager_address             NVARCHAR(100) = NULL,
  @weekday_pager_start_time  INT           = 090000, -- HHMMSS using 24 hour clock
  @weekday_pager_end_time    INT           = 180000, -- As above
  @saturday_pager_start_time INT           = 090000, -- As above
  @saturday_pager_end_time   INT           = 180000, -- As above
  @sunday_pager_start_time   INT           = 090000, -- As above
  @sunday_pager_end_time     INT           = 180000, -- As above
  @pager_days                TINYINT       = 0,      -- 1 = Sunday .. 64 = Saturday
  @netsend_address           NVARCHAR(100) = NULL,   -- New for 7.0
  @category_name             sysname       = NULL    -- New for 7.0
AS
BEGIN
  DECLARE @return_code TINYINT
  DECLARE @category_id INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name            = LTRIM(RTRIM(@name))
  SELECT @email_address   = LTRIM(RTRIM(@email_address))
  SELECT @pager_address   = LTRIM(RTRIM(@pager_address))
  SELECT @netsend_address = LTRIM(RTRIM(@netsend_address))
  SELECT @category_name   = LTRIM(RTRIM(@category_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@email_address   = N'') SELECT @email_address   = NULL
  IF (@pager_address   = N'') SELECT @pager_address   = NULL
  IF (@netsend_address = N'') SELECT @netsend_address = NULL
  IF (@category_name   = N'') SELECT @category_name   = NULL

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  IF (@category_name IS NULL)
  BEGIN
    SELECT @category_name = name
    FROM msdb.dbo.syscategories
    WHERE (category_id = 99)
  END

  -- Verify the operator
  EXECUTE @return_code = sp_verify_operator @name,
                                            @enabled,
                                            @pager_days,
                                            @weekday_pager_start_time,
                                            @weekday_pager_end_time,
                                            @saturday_pager_start_time,
                                            @saturday_pager_end_time,
                                            @sunday_pager_start_time,
                                            @sunday_pager_end_time,
                                            @category_name,
                                            @category_id OUTPUT
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  -- Finally, do the INSERT
  INSERT INTO msdb.dbo.sysoperators
         (name,
          enabled,
          email_address,
          last_email_date,
          last_email_time,
          pager_address,
          last_pager_date,
          last_pager_time,
          weekday_pager_start_time,
          weekday_pager_end_time,
          saturday_pager_start_time,
          saturday_pager_end_time,
          sunday_pager_start_time,
          sunday_pager_end_time,
          pager_days,
          netsend_address,
          last_netsend_date,
          last_netsend_time,
          category_id)
  VALUES (@name,
          @enabled,
          @email_address,
          0,
          0,
          @pager_address,
          0,
          0,
          @weekday_pager_start_time,
          @weekday_pager_end_time,
          @saturday_pager_start_time,
          @saturday_pager_end_time,
          @sunday_pager_start_time,
          @sunday_pager_end_time,
          @pager_days,
          @netsend_address,
          0,
          0,
          @category_id)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_UPDATE_OPERATOR                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_operator...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_operator')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_operator
go
CREATE PROCEDURE sp_update_operator
  @name                      sysname,
  @new_name                  sysname       = NULL,
  @enabled                   TINYINT       = NULL,
  @email_address             NVARCHAR(100) = NULL,
  @pager_address             NVARCHAR(100) = NULL,
  @weekday_pager_start_time  INT           = NULL, -- HHMMSS using 24 hour clock
  @weekday_pager_end_time    INT           = NULL, -- As above
  @saturday_pager_start_time INT           = NULL, -- As above
  @saturday_pager_end_time   INT           = NULL, -- As above
  @sunday_pager_start_time   INT           = NULL, -- As above
  @sunday_pager_end_time     INT           = NULL, -- As above
  @pager_days                TINYINT       = NULL,
  @netsend_address           NVARCHAR(100) = NULL, -- New for 7.0
  @category_name             sysname       = NULL  -- New for 7.0
AS
BEGIN
  DECLARE @x_enabled                   TINYINT
  DECLARE @x_email_address             NVARCHAR(100)
  DECLARE @x_pager_address             NVARCHAR(100)
  DECLARE @x_weekday_pager_start_time  INT
  DECLARE @x_weekday_pager_end_time    INT
  DECLARE @x_saturday_pager_start_time INT
  DECLARE @x_saturday_pager_end_time   INT
  DECLARE @x_sunday_pager_start_time   INT
  DECLARE @x_sunday_pager_end_time     INT
  DECLARE @x_pager_days                TINYINT
  DECLARE @x_netsend_address           NVARCHAR(100)
  DECLARE @x_category_id               INT

  DECLARE @return_code                 INT
  DECLARE @notification_method         INT
  DECLARE @alert_fail_safe_operator    sysname
  DECLARE @current_msx_server          sysname
  DECLARE @category_id                 INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name            = LTRIM(RTRIM(@name))
  SELECT @new_name        = LTRIM(RTRIM(@new_name))
  SELECT @email_address   = LTRIM(RTRIM(@email_address))
  SELECT @pager_address   = LTRIM(RTRIM(@pager_address))
  SELECT @netsend_address = LTRIM(RTRIM(@netsend_address))
  SELECT @category_name   = LTRIM(RTRIM(@category_name))

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check if this Operator exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysoperators
                  WHERE (name = @name)))
  BEGIN
    RAISERROR(14262, 16, 1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Check if this operator is 'MSXOperator'
  IF (@name = N'MSXOperator')
  BEGIN
    -- Disallow the update operation if we're at a TSX for all callers other than xp_msx_enlist
    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'MSXServerName',
                                           @current_msx_server OUTPUT,
                                           N'no_output'
    IF ((@current_msx_server IS NOT NULL) AND (PROGRAM_NAME() <> N'xp_msx_enlist'))
    BEGIN
      RAISERROR(14223, 16, 1, 'MSXOperator', 'TSX')
      RETURN(1) -- Failure
    END
  END

  -- Get existing (@x_) operator property values
  SELECT @x_enabled                   = enabled,
         @x_email_address             = email_address,
         @x_pager_address             = pager_address,
         @x_weekday_pager_start_time  = weekday_pager_start_time,
         @x_weekday_pager_end_time    = weekday_pager_end_time,
         @x_saturday_pager_start_time = saturday_pager_start_time,
         @x_saturday_pager_end_time   = saturday_pager_end_time,
         @x_sunday_pager_start_time   = sunday_pager_start_time,
         @x_sunday_pager_end_time     = sunday_pager_end_time,
         @x_pager_days                = pager_days,
         @x_netsend_address           = netsend_address,
         @x_category_id               = category_id
  FROM msdb.dbo.sysoperators
  WHERE (name = @name)

  -- Fill out the values for all non-supplied parameters from the existsing values
  IF (@enabled                   IS NULL) SELECT @enabled                   = @x_enabled
  IF (@email_address             IS NULL) SELECT @email_address             = @x_email_address
  IF (@pager_address             IS NULL) SELECT @pager_address             = @x_pager_address
  IF (@weekday_pager_start_time  IS NULL) SELECT @weekday_pager_start_time  = @x_weekday_pager_start_time
  IF (@weekday_pager_end_time    IS NULL) SELECT @weekday_pager_end_time    = @x_weekday_pager_end_time
  IF (@saturday_pager_start_time IS NULL) SELECT @saturday_pager_start_time = @x_saturday_pager_start_time
  IF (@saturday_pager_end_time   IS NULL) SELECT @saturday_pager_end_time   = @x_saturday_pager_end_time
  IF (@sunday_pager_start_time   IS NULL) SELECT @sunday_pager_start_time   = @x_sunday_pager_start_time
  IF (@sunday_pager_end_time     IS NULL) SELECT @sunday_pager_end_time     = @x_sunday_pager_end_time
  IF (@pager_days                IS NULL) SELECT @pager_days                = @x_pager_days
  IF (@netsend_address           IS NULL) SELECT @netsend_address           = @x_netsend_address
  IF (@category_name             IS NULL) SELECT @category_name = name FROM msdb.dbo.syscategories WHERE (category_id = @x_category_id)

  IF (@category_name IS NULL)
  BEGIN
    SELECT @category_name = name
    FROM msdb.dbo.syscategories
    WHERE (category_id = 99)
  END

  -- Turn [nullable] empty string parameters into NULLs
  IF (@email_address   = N'') SELECT @email_address   = NULL
  IF (@pager_address   = N'') SELECT @pager_address   = NULL
  IF (@netsend_address = N'') SELECT @netsend_address = NULL
  IF (@category_name   = N'') SELECT @category_name   = NULL

  -- Verify the operator
  EXECUTE @return_code = sp_verify_operator @new_name,
                                            @enabled,
                                            @pager_days,
                                            @weekday_pager_start_time,
                                            @weekday_pager_end_time,
                                            @saturday_pager_start_time,
                                            @saturday_pager_end_time,
                                            @sunday_pager_start_time,
                                            @sunday_pager_end_time,
                                            @category_name,
                                            @category_id OUTPUT
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  -- If no new name is supplied, use the old one
  -- NOTE: We must do this AFTER calling sp_verify_operator.
  IF (@new_name IS NULL)
    SELECT @new_name = @name
  ELSE
  BEGIN
    -- You can't rename the MSXOperator
    IF (@name = N'MSXOperator')
    BEGIN
      RAISERROR(14222, 16, 1, 'MSXOperator')
      RETURN(1) -- Failure
    END
  END

  -- Do the UPDATE
  UPDATE msdb.dbo.sysoperators
  SET name                      = @new_name,
      enabled                   = @enabled,
      email_address             = @email_address,
      pager_address             = @pager_address,
      weekday_pager_start_time  = @weekday_pager_start_time,
      weekday_pager_end_time    = @weekday_pager_end_time,
      saturday_pager_start_time = @saturday_pager_start_time,
      saturday_pager_end_time   = @saturday_pager_end_time,
      sunday_pager_start_time   = @sunday_pager_start_time,
      sunday_pager_end_time     = @sunday_pager_end_time,
      pager_days                = @pager_days,
      netsend_address           = @netsend_address,
      category_id               = @category_id
  WHERE (name = @name)

  -- Check if the operator is 'MSXOperator', in which case we need to re-enlist all the targets
  -- so that they will download the new MSXOperator details
  IF ((@name = N'MSXOperator') AND ((SELECT COUNT(*) FROM msdb.dbo.systargetservers) > 0))
    EXECUTE msdb.dbo.sp_post_msx_operation 'RE-ENLIST', 'SERVER', 0x00

  -- Check if this operator is the FailSafe Operator
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'AlertFailSafeOperator',
                                         @alert_fail_safe_operator OUTPUT,
                                         N'no_output'

  -- If it is, we update the 4 'AlertFailSafe...' registry entries and AlertNotificationMethod
  IF (LTRIM(RTRIM(@alert_fail_safe_operator)) = @name)
  BEGIN
    -- Update AlertFailSafeX values
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'AlertFailSafeOperator',
                                            N'REG_SZ',
                                            @new_name
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'AlertFailSafeEmailAddress',
                                            N'REG_SZ',
                                            @email_address
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'AlertFailSafePagerAddress',
                                            N'REG_SZ',
                                            @pager_address
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'AlertFailSafeNetSendAddress',
                                            N'REG_SZ',
                                            @netsend_address

    -- Update AlertNotificationMethod values
    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'AlertNotificationMethod',
                                           @notification_method OUTPUT,
                                           N'no_output'
    IF (LTRIM(RTRIM(@email_address)) IS NULL)
      SELECT @notification_method = @notification_method & ~1
    IF (LTRIM(RTRIM(@pager_address)) IS NULL)
      SELECT @notification_method = @notification_method & ~2
    IF (LTRIM(RTRIM(@netsend_address)) IS NULL)
      SELECT @notification_method = @notification_method & ~4
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'AlertNotificationMethod',
                                            N'REG_DWORD',
                                            @notification_method

    -- And finally, let SQLServerAgent know of the changes
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type = N'G'
  END

  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_HELP_OPERATOR                                           */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_operator...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_operator')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_operator
go
CREATE PROCEDURE sp_help_operator
  @operator_name sysname = NULL,
  @operator_id   INT     = NULL
AS
BEGIN
  DECLARE @operator_id_as_char VARCHAR(10)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @operator_name = LTRIM(RTRIM(@operator_name))
  IF (@operator_name = '') SELECT @operator_name = NULL

  -- Check operator name
  IF (@operator_name IS NOT NULL)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysoperators
                    WHERE (name = @operator_name)))
    BEGIN
      RAISERROR(14262, -1, -1, '@operator_name', @operator_name)
      RETURN(1) -- Failure
    END
  END

  -- Check operator id
  IF (@operator_id IS NOT NULL)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysoperators
                    WHERE (id = @operator_id)))
    BEGIN
      SELECT @operator_id_as_char = CONVERT(VARCHAR, @operator_id)
      RAISERROR(14262, -1, -1, '@operator_id', @operator_id_as_char)
      RETURN(1) -- Failure
    END
  END

  SELECT so.id,
         so.name,
         so.enabled,
         so.email_address,
         so.last_email_date,
         so.last_email_time,
         so.pager_address,
         so.last_pager_date,
         so.last_pager_time,
         so.weekday_pager_start_time,
         so.weekday_pager_end_time,
         so.saturday_pager_start_time,
         so.saturday_pager_end_time,
         so.sunday_pager_start_time,
         so.sunday_pager_end_time,
         so.pager_days,
         so.netsend_address,
         so.last_netsend_date,
         so.last_netsend_time,
         category_name = sc.name
  FROM msdb.dbo.sysoperators                  so
       LEFT OUTER JOIN msdb.dbo.syscategories sc ON (so.category_id = sc.category_id)
  WHERE ((@operator_name IS NULL) OR (so.name = @operator_name))
    AND ((@operator_id IS NULL) OR (so.id = @operator_id))
  ORDER BY so.name

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_HELP_OPERATOR_JOBS                                      */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_operator_jobs...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_help_operator_jobs')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_operator_jobs
go
CREATE PROCEDURE sp_help_operator_jobs
  @operator_name sysname = NULL
AS
BEGIN
  DECLARE @operator_id INT

  SET NOCOUNT ON

  -- Check operator name
  SELECT @operator_id = id
  FROM msdb.dbo.sysoperators
  WHERE (name = @operator_name)
  IF (@operator_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@operator_name', @operator_name)
    RETURN(1) -- Failure
  END

  -- Get the job info
  SELECT job_id, name, notify_level_email, notify_level_netsend, notify_level_page
  FROM msdb.dbo.sysjobs_view
  WHERE ((notify_email_operator_id = @operator_id)   AND (notify_level_email <> 0))
     OR ((notify_netsend_operator_id = @operator_id) AND (notify_level_netsend <> 0))
     OR ((notify_page_operator_id = @operator_id)    AND (notify_level_page <> 0))

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_VERIFY_OPERATOR_IDENTIFIERS                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_operator_identifiers...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_operator_identifiers', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_operator_identifiers
go

CREATE PROCEDURE sp_verify_operator_identifiers
   @name_of_name_parameter [varchar](60),
   @name_of_id_parameter [varchar](60),
   @operator_name [sysname] OUTPUT,
   @operator_id [INT] OUTPUT
AS
BEGIN
  DECLARE @retval              INT
  DECLARE @operator_id_as_char NVARCHAR(36)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name_of_name_parameter = LTRIM(RTRIM(@name_of_name_parameter))
  SELECT @name_of_id_parameter   = LTRIM(RTRIM(@name_of_id_parameter))
  SELECT @operator_name             = LTRIM(RTRIM(@operator_name))

  IF (@operator_name = N'') SELECT @operator_name = NULL

  IF ((@operator_name IS NULL)     AND (@operator_id IS NULL)) OR
     ((@operator_name IS NOT NULL) AND (@operator_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, @name_of_id_parameter, @name_of_name_parameter)
    RETURN(1) -- Failure
  END

  -- Check job id
  IF (@operator_id IS NOT NULL)
  BEGIN
    SELECT @operator_name = name
    FROM msdb.dbo.sysoperators
    WHERE (id = @operator_id)
    IF (@operator_name IS NULL)
    BEGIN
     SELECT @operator_id_as_char = CONVERT(nvarchar(36), @operator_id)
      RAISERROR(14262, -1, -1, '@operator_id', @operator_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check proxy name
  IF (@operator_name IS NOT NULL)
  BEGIN
    -- The name is not ambiguous, so get the corresponding operator_id (if the job exists)
    SELECT @operator_id = id
    FROM msdb.dbo.sysoperators
    WHERE (name = @operator_name)
    IF (@operator_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@operator_name', @operator_name)
      RETURN(1) -- Failure
    END
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_NOTIFY_OPERATOR                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_notify_operator...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_notify_operator')
              AND (type = 'P')))
  DROP PROCEDURE sp_notify_operator
go
CREATE PROCEDURE sp_notify_operator
  @profile_name           sysname       = NULL,          
  --name of Database Mail profile to be used for sending email, cannot be null
  @id                     INT            = NULL,  
  @name                   sysname        = NULL, 
  --mutual exclusive, one and only one should be non null. Specify the operator whom mail adress will be used to send this email
  @subject                NVARCHAR(256)  = NULL,
  @body                   NVARCHAR(MAX)  = NULL, 
  -- This is the body of the email message
  @file_attachments       NVARCHAR(512)  = NULL,
  @mail_database          sysname       = N'msdb'
  -- Have infrastructure in place to support this but disabled by default
  -- For first implementation we will have this parameters but using it will generate an error - not implemented yet.
AS
BEGIN
  DECLARE @retval INT
  DECLARE @email_address NVARCHAR(100)
  DECLARE @enabled TINYINT
  DECLARE @qualified_sp_sendmail sysname
  DECLARE @db_id INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @profile_name              = LTRIM(RTRIM(@profile_name))
  SELECT @name                      = LTRIM(RTRIM(@name))
  SELECT @file_attachments          = LTRIM(RTRIM(@file_attachments))
  SELECT @mail_database          = LTRIM(RTRIM(@mail_database))


  IF @profile_name       = ''    SELECT @profile_name      = NULL
  IF @name               = ''    SELECT @name              = NULL
  IF @file_attachments   = ''    SELECT @file_attachments  = NULL
  IF @mail_database      = ''    SELECT @mail_database      = NULL

  EXECUTE @retval = sp_verify_operator_identifiers '@name',
                                                   '@id',
                                                   @name OUTPUT,
                                                   @id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  --is operator enabled?
  SELECT @enabled = enabled, @email_address = email_address FROM sysoperators WHERE id = @id
  IF @enabled = 0 
  BEGIN
    RAISERROR(14601, 16, 1, @name)
    RETURN 1
  END
  
  IF @email_address IS NULL
  BEGIN
    RAISERROR(14602, 16, 1, @name)
    RETURN 1
  END
  
  SELECT @qualified_sp_sendmail = @mail_database + '.dbo.sp_send_dbmail'

  EXEC   @retval = @qualified_sp_sendmail @profile_name = @profile_name,
                               @recipients       = @email_address,
                               @subject          = @subject,
                               @body              = @body,
                               @file_attachments = @file_attachments
  RETURN @retval                            
END
go

/**************************************************************/
/* SP_VERIFY_NOTIFICATION                                     */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_notification...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_notification')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_notification
go
CREATE PROCEDURE sp_verify_notification
  @alert_name          sysname,
  @operator_name       sysname,
  @notification_method TINYINT,
  @alert_id            INT OUTPUT,
  @operator_id         INT OUTPUT
AS
BEGIN
  DECLARE @res_valid_range NVARCHAR(100)

  SET NOCOUNT ON

  SELECT @res_valid_range = FORMATMESSAGE(14208)

  -- Remove any leading/trailing spaces from parameters
  SELECT @alert_name    = LTRIM(RTRIM(@alert_name))
  SELECT @operator_name = LTRIM(RTRIM(@operator_name))

  -- Check if the AlertName is valid
  SELECT @alert_id = id
  FROM msdb.dbo.sysalerts
  WHERE (name = @alert_name)

  IF (@alert_id IS NULL)
  BEGIN
    RAISERROR(14262, 16, 1, '@alert_name', @alert_name)
    RETURN(1) -- Failure
  END

  -- Check if the OperatorName is valid
  SELECT @operator_id = id
  FROM msdb.dbo.sysoperators
  WHERE (name = @operator_name)

  IF (@operator_id IS NULL)
  BEGIN
    RAISERROR(14262, 16, 1, '@operator_name', @operator_name)
    RETURN(1) -- Failure
  END

  -- If we're at a TSX, we disallow using operator 'MSXOperator'
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.systargetservers)) AND
     (@operator_name = N'MSXOperator')
  BEGIN
    RAISERROR(14251, -1, -1, @operator_name)
    RETURN(1) -- Failure
  END

  -- Check if the NotificationMethod is valid
  IF ((@notification_method < 1) OR (@notification_method > 7))
  BEGIN
    RAISERROR(14266, 16, 1, '@notification_method', @res_valid_range)
    RETURN(1) -- Failure
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_ADD_NOTIFICATION                                        */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_notification...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_notification')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_notification
go
CREATE PROCEDURE sp_add_notification
  @alert_name          sysname,
  @operator_name       sysname,
  @notification_method TINYINT -- 1 = Email, 2 = Pager, 4 = NetSend, 7 = All
AS
BEGIN
  DECLARE @alert_id             INT
  DECLARE @operator_id          INT
  DECLARE @notification         NVARCHAR(512)
  DECLARE @retval               INT
  DECLARE @old_has_notification INT
  DECLARE @new_has_notification INT
  DECLARE @res_notification     NVARCHAR(100)

  SET NOCOUNT ON

  SELECT @res_notification = FORMATMESSAGE(14210)

  -- Remove any leading/trailing spaces from parameters
  SELECT @alert_name    = LTRIM(RTRIM(@alert_name))
  SELECT @operator_name = LTRIM(RTRIM(@operator_name))

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check if the Notification is valid
  EXECUTE @retval = msdb.dbo.sp_verify_notification @alert_name,
                                                    @operator_name,
                                                    @notification_method,
                                                    @alert_id     OUTPUT,
                                                    @operator_id  OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check if this notification already exists
  -- NOTE: The unique index would catch this, but testing for the problem here lets us
  --       control the message.
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysnotifications
              WHERE (alert_id = @alert_id)
                AND (operator_id = @operator_id)))
  BEGIN
    SELECT @notification = @alert_name + N' / ' + @operator_name 
    RAISERROR(14261, 16, 1, @res_notification, @notification)
    RETURN(1) -- Failure
  END

  SELECT @old_has_notification = has_notification
  FROM msdb.dbo.sysalerts
  WHERE (id = @alert_id)

  -- Do the INSERT
  INSERT INTO msdb.dbo.sysnotifications
         (alert_id,
          operator_id,
          notification_method)
  VALUES (@alert_id,
          @operator_id,
          @notification_method)

  SELECT @retval = @@error

  SELECT @new_has_notification = has_notification
  FROM msdb.dbo.sysalerts
  WHERE (id = @alert_id)

  -- Notify SQLServerAgent of the change - if any - to has_notifications
  IF (@old_has_notification <> @new_has_notification)
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'A',
                                        @alert_id    = @alert_id,
                                        @action_type = N'U'

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_UPDATE_NOTIFICATION                                     */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_notification...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_notification')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_notification
go
CREATE PROCEDURE sp_update_notification
  @alert_name          sysname,
  @operator_name       sysname,
  @notification_method TINYINT -- 1 = Email, 2 = Pager, 4 = NetSend, 7 = All
AS
BEGIN
  DECLARE @alert_id             INT
  DECLARE @operator_id          INT
  DECLARE @notification         NVARCHAR(512)
  DECLARE @retval               INT
  DECLARE @old_has_notification INT
  DECLARE @new_has_notification INT
  DECLARE @res_notification     NVARCHAR(100)

  SET NOCOUNT ON

  SELECT @res_notification = FORMATMESSAGE(14210)

  -- Remove any leading/trailing spaces from parameters
  SELECT @alert_name    = LTRIM(RTRIM(@alert_name))
  SELECT @operator_name = LTRIM(RTRIM(@operator_name))

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check if the Notification is valid
  EXECUTE sp_verify_notification @alert_name,
                                 @operator_name,
                                 @notification_method,
                                 @alert_id     OUTPUT,
                                 @operator_id  OUTPUT

  -- Check if this notification exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysnotifications
                  WHERE (alert_id = @alert_id)
                    AND (operator_id = @operator_id)))
  BEGIN
    SELECT @notification = @alert_name + N' / ' + @operator_name
    RAISERROR(14262, 16, 1, @res_notification, @notification)
    RETURN(1) -- Failure
  END

  SELECT @old_has_notification = has_notification
  FROM msdb.dbo.sysalerts
  WHERE (id = @alert_id)

  -- Do the UPDATE
  UPDATE msdb.dbo.sysnotifications
  SET notification_method = @notification_method
  WHERE (alert_id = @alert_id)
    AND (operator_id = @operator_id)

  SELECT @retval = @@error

  SELECT @new_has_notification = has_notification
  FROM msdb.dbo.sysalerts
  WHERE (id = @alert_id)

  -- Notify SQLServerAgent of the change - if any - to has_notifications
  IF (@old_has_notification <> @new_has_notification)
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type       = N'A',
                                          @alert_id    = @alert_id,
                                          @action_type = N'U'

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_NOTIFICATION                                     */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_notification...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_notification')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_notification
go
CREATE PROCEDURE sp_delete_notification
  @alert_name    sysname,
  @operator_name sysname
AS
BEGIN
  DECLARE @alert_id             INT
  DECLARE @operator_id          INT
  DECLARE @ignored              TINYINT
  DECLARE @notification         NVARCHAR(512)
  DECLARE @retval               INT
  DECLARE @old_has_notification INT
  DECLARE @new_has_notification INT
  DECLARE @res_notification     NVARCHAR(100)

  SET NOCOUNT ON

  SELECT @res_notification = FORMATMESSAGE(14210)

  -- Remove any leading/trailing spaces from parameters
  SELECT @alert_name    = LTRIM(RTRIM(@alert_name))
  SELECT @operator_name = LTRIM(RTRIM(@operator_name))

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Get the alert and operator ID's
  EXECUTE sp_verify_notification @alert_name,
                                 @operator_name,
                                 7,           -- A dummy (but valid) value
                                 @alert_id    OUTPUT,
                                 @operator_id OUTPUT

  -- Check if this notification exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysnotifications
                  WHERE (alert_id = @alert_id)
                    AND (operator_id = @operator_id)))
  BEGIN
    SELECT @notification = @alert_name + N' / ' + @operator_name
    RAISERROR(14262, 16, 1, @res_notification, @notification)
    RETURN(1) -- Failure
  END

  SELECT @old_has_notification = has_notification
  FROM msdb.dbo.sysalerts
  WHERE (id = @alert_id)

  -- Do the Delete
  DELETE FROM msdb.dbo.sysnotifications
  WHERE (alert_id = @alert_id)
    AND (operator_id = @operator_id)

  SELECT @retval = @@error

  SELECT @new_has_notification = has_notification
  FROM msdb.dbo.sysalerts
  WHERE (id = @alert_id)

  -- Notify SQLServerAgent of the change - if any - to has_notifications
  IF (@old_has_notification <> @new_has_notification)
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type       = N'A',
                                          @alert_id    = @alert_id,
                                          @action_type = N'U'

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_HELP_NOTIFICATION                                       */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_notification...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_notification')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_notification
go
CREATE PROCEDURE sp_help_notification
  @object_type          CHAR(9),   -- Either 'ALERTS'    (enumerates Alerts for given Operator)
                                   --     or 'OPERATORS' (enumerates Operators for given Alert)
  @name                 sysname,   -- Either an Operator Name (if @object_type is 'ALERTS')
                                   --     or an Alert Name    (if @object_type is 'OPERATORS')
  @enum_type            CHAR(10),  -- Either 'ALL'    (enumerate all objects [eg. all alerts irrespective of whether 'Fred' receives a notification for them])
                                   --     or 'ACTUAL' (enumerate only the associated objects [eg. only the alerts which 'Fred' receives a notification for])
                                   --     or 'TARGET' (enumerate only the objects matching @target_name [eg. a single row showing how 'Fred' is notfied for alert 'Test'])
  @notification_method  TINYINT,   -- Either 1 (Email)   - Modifies the result set to only show use_email column
                                   --     or 2 (Pager)   - Modifies the result set to only show use_pager column
                                   --     or 4 (NetSend) - Modifies the result set to only show use_netsend column
                                   --     or 7 (All)     - Modifies the result set to show all the use_xxx columns
  @target_name   sysname = NULL    -- Either an Alert Name    (if @object_type is 'ALERTS')
                                   --     or an Operator Name (if @object_type is 'OPERATORS')
                                   -- NOTE: This parameter is only required if @enum_type is 'TARGET')
AS
BEGIN
  DECLARE @id              INT    -- We use this to store the decode of @name
  DECLARE @target_id       INT    -- We use this to store the decode of @target_name
  DECLARE @select_clause   NVARCHAR(1024)
  DECLARE @from_clause     NVARCHAR(512)
  DECLARE @where_clause    NVARCHAR(512)
  DECLARE @res_valid_range NVARCHAR(100)

  SET NOCOUNT ON

  SELECT @res_valid_range = FORMATMESSAGE(14208)

  -- Remove any leading/trailing spaces from parameters
  SELECT @object_type = UPPER(LTRIM(RTRIM(@object_type)) collate SQL_Latin1_General_CP1_CS_AS)
  SELECT @name        = LTRIM(RTRIM(@name))
  SELECT @enum_type   = UPPER(LTRIM(RTRIM(@enum_type)) collate SQL_Latin1_General_CP1_CS_AS)
  SELECT @target_name = LTRIM(RTRIM(@target_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@target_name = N'') SELECT @target_name = NULL

  -- Check ObjectType
  IF (@object_type NOT IN ('ALERTS', 'OPERATORS'))
  BEGIN
    RAISERROR(14266, 16, 1, '@object_type', 'ALERTS, OPERATORS')
    RETURN(1) -- Failure
  END

  -- Check AlertName
  IF (@object_type = 'OPERATORS') AND
     (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysalerts
                  WHERE (name = @name)))
  BEGIN
    RAISERROR(14262, 16, 1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Check OperatorName
  IF (@object_type = 'ALERTS') AND
     (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysoperators
                  WHERE (name = @name)))
  BEGIN
    RAISERROR(14262, 16, 1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Check EnumType
  IF (@enum_type NOT IN ('ALL', 'ACTUAL', 'TARGET'))
  BEGIN
    RAISERROR(14266, 16, 1, '@enum_type', 'ALL, ACTUAL, TARGET')
    RETURN(1) -- Failure
  END

  -- Check Notification Method
  IF ((@notification_method < 1) OR (@notification_method > 7))
  BEGIN
    RAISERROR(14266, 16, 1, '@notification_method', @res_valid_range)
    RETURN(1) -- Failure
  END

  -- If EnumType is 'TARGET', check if we have a @TargetName parameter
  IF (@enum_type = 'TARGET') AND (@target_name IS NULL)
  BEGIN
    RAISERROR(14502, 16, 1)
    RETURN(1) -- Failure
  END

  -- If EnumType isn't 'TARGET', we shouldn't have an @target_name parameter
  IF (@enum_type <> 'TARGET') AND (@target_name IS NOT NULL)
  BEGIN
    RAISERROR(14503, 16, 1)
    RETURN(1) -- Failure
  END

  -- Translate the Name into an ID
  IF (@object_type = 'ALERTS')
  BEGIN
    SELECT @id = id
    FROM msdb.dbo.sysoperators
    WHERE (name = @name)
  END
  IF (@object_type = 'OPERATORS')
  BEGIN
    SELECT @id = id
    FROM msdb.dbo.sysalerts
    WHERE (name = @name)
  END

  -- Translate the TargetName into a TargetID
  IF (@target_name IS NOT NULL)
  BEGIN
    IF (@object_type = 'OPERATORS')
    BEGIN
      SELECT @target_id = id
      FROM msdb.dbo.sysoperators
      WHERE (name = @target_name )
    END
    IF (@object_type = 'ALERTS')
    BEGIN
      SELECT @target_id = id
      FROM msdb.dbo.sysalerts
      WHERE (name = @target_name)
    END
    IF (@target_id IS NULL) -- IE. the Target Name is invalid
    BEGIN
      RAISERROR(14262, 16, 1, @object_type, @target_name)
      RETURN(1) -- Failure
    END
  END

  -- Ok, the parameters look good so generate the SQL then EXECUTE() it...

  -- Generate the 'stub' SELECT clause and the FROM clause
  IF (@object_type = 'OPERATORS') -- So we want a list of Operators for the supplied AlertID
  BEGIN
    SELECT @select_clause = N'SELECT operator_id = o.id, operator_name = o.name, '
    IF (@enum_type = 'ALL')
      SELECT @from_clause = N'FROM msdb.dbo.sysoperators o LEFT OUTER JOIN msdb.dbo.sysnotifications sn ON (o.id = sn.operator_id) '
    ELSE
      SELECT @from_clause = N'FROM msdb.dbo.sysoperators o, msdb.dbo.sysnotifications sn '
  END
  IF (@object_type = 'ALERTS') -- So we want a list of Alerts for the supplied OperatorID
  BEGIN
    SELECT @select_clause = N'SELECT alert_id = a.id, alert_name = a.name, '
    IF (@enum_type = 'ALL')
      SELECT @from_clause = N'FROM msdb.dbo.sysalerts a LEFT OUTER JOIN msdb.dbo.sysnotifications sn ON (a.id = sn.alert_id) '
    ELSE
      SELECT @from_clause = N'FROM msdb.dbo.sysalerts a, msdb.dbo.sysnotifications sn '
  END

  -- Add the required use_xxx columns to the SELECT clause
  IF (@notification_method & 1 = 1)
    SELECT @select_clause = @select_clause + N'use_email = ISNULL((sn.notification_method & 1) / POWER(2, 0), 0), '
  IF (@notification_method & 2 = 2)
    SELECT @select_clause = @select_clause + N'use_pager = ISNULL((sn.notification_method & 2) / POWER(2, 1), 0), '
  IF (@notification_method & 4 = 4)
    SELECT @select_clause = @select_clause + N'use_netsend = ISNULL((sn.notification_method & 4) / POWER(2, 2), 0), '

  -- Remove the trailing comma
  SELECT @select_clause = SUBSTRING(@select_clause, 1, (DATALENGTH(@select_clause) / 2) - 2) + N' '

  -- Generate the WHERE clause
  IF (@object_type = 'OPERATORS')
  BEGIN
    IF (@enum_type = 'ALL')
      SELECT @from_clause = @from_clause + N' AND (sn.alert_id = ' + CONVERT(VARCHAR(10), @id) + N')'

    IF (@enum_type = 'ACTUAL')
      SELECT @where_clause = N'WHERE (o.id = sn.operator_id) AND (sn.alert_id = ' + CONVERT(VARCHAR(10), @id) + N') AND (sn.notification_method & ' + CONVERT(VARCHAR, @notification_method) + N' <> 0)'

    IF (@enum_type = 'TARGET')
      SELECT @where_clause = N'WHERE (o.id = sn.operator_id) AND (sn.operator_id = ' + CONVERT(VARCHAR(10), @target_id) + N') AND (sn.alert_id = ' + CONVERT(VARCHAR(10), @id) + N')'
  END
  IF (@object_type = 'ALERTS')
  BEGIN
    IF (@enum_type = 'ALL')
      SELECT @from_clause = @from_clause + N' AND (sn.operator_id = ' + CONVERT(VARCHAR(10), @id) + N')'

    IF (@enum_type = 'ACTUAL')
      SELECT @where_clause = N'WHERE (a.id = sn.alert_id) AND (sn.operator_id = ' + CONVERT(VARCHAR(10), @id) + N') AND (sn.notification_method & ' + CONVERT(VARCHAR, @notification_method) + N' <> 0)'

    IF (@enum_type = 'TARGET')
      SELECT @where_clause = N'WHERE (a.id = sn.alert_id) AND (sn.alert_id = ' + CONVERT(VARCHAR(10), @target_id) + N') AND (sn.operator_id = ' + CONVERT(VARCHAR(10), @id) + N')'
  END

  -- Add the has_email and has_pager columns to the SELECT clause
  IF (@object_type = 'OPERATORS')
  BEGIN
    SELECT @select_clause = @select_clause + N', has_email = PATINDEX(N''%[^ ]%'', ISNULL(o.email_address, N''''))'
    SELECT @select_clause = @select_clause + N', has_pager = PATINDEX(N''%[^ ]%'', ISNULL(o.pager_address, N''''))'
    SELECT @select_clause = @select_clause + N', has_netsend = PATINDEX(N''%[^ ]%'', ISNULL(o.netsend_address, N''''))'
  END
  IF (@object_type = 'ALERTS')
  BEGIN
    -- NOTE: We return counts so that the UI can detect 'unchecking' the last notification
    SELECT @select_clause = @select_clause + N', has_email = (SELECT COUNT(*) FROM sysnotifications WHERE (alert_id = a.id) AND ((notification_method & 1) = 1)) '
    SELECT @select_clause = @select_clause + N', has_pager = (SELECT COUNT(*) FROM sysnotifications WHERE (alert_id = a.id) AND ((notification_method & 2) = 2)) '
    SELECT @select_clause = @select_clause + N', has_netsend = (SELECT COUNT(*) FROM sysnotifications WHERE (alert_id = a.id) AND ((notification_method & 4) = 4)) '
  END

  EXECUTE (@select_clause + @from_clause + @where_clause)

  RETURN(@@error) -- 0 means success
END
go

PRINT ''
PRINT 'Creating procedure sp_help_jobactivity...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_help_jobactivity')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobactivity
go
CREATE PROCEDURE sp_help_jobactivity
  @job_id     UNIQUEIDENTIFIER = NULL,  -- If provided should NOT also provide job_name
  @job_name   sysname          = NULL,  -- If provided should NOT also provide job_id
  @session_id INT = NULL
AS
BEGIN
  DECLARE @retval          INT
  DECLARE @session_id_as_char NVARCHAR(16)
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters (except @owner_login_name)
  SELECT @job_name         = LTRIM(RTRIM(@job_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@job_name         = N'') SELECT @job_name = NULL

  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END
  


  IF @session_id IS NULL
    SELECT TOP(1) @session_id = session_id FROM syssessions ORDER by agent_start_date DESC 
  ELSE IF NOT EXISTS( SELECT * FROM syssessions WHERE session_id = @session_id)
  BEGIN
    SELECT @session_id_as_char = CONVERT(NVARCHAR(16), @session_id)
    RAISERROR(14262, -1, -1, '@session_id', @session_id_as_char)
    RETURN(1) --failure
  END

  SELECT
      ja.session_id,                
      ja.job_id,
    j.name AS job_name,
    ja.run_requested_date,        
    ja.run_requested_source,      
    ja.queued_date,               
    ja.start_execution_date,      
    ja.last_executed_step_id,     
    ja.last_executed_step_date,   
    ja.stop_execution_date,       
    ja.next_scheduled_run_date,
    ja.job_history_id,            
    jh.message,
    jh.run_status,
    jh.operator_id_emailed,
    jh.operator_id_netsent,
    jh.operator_id_paged
  FROM
    (msdb.dbo.sysjobactivity ja LEFT JOIN msdb.dbo.sysjobhistory jh ON ja.job_history_id = jh.instance_id)
    join msdb.dbo.sysjobs_view j on ja.job_id = j.job_id
  WHERE
    (@job_id IS NULL OR ja.job_id = @job_id) AND
     ja.session_id = @session_id

  RETURN(0)
END
go
/**************************************************************/
/*                                                            */
/*                    T  R  I G  G  E  R  S                   */
/*                                                            */
/**************************************************************/

/**************************************************************/
/* DROP THE OLD 6.x TRIGGERS                                  */
/* [multiple triggers of the same type are allowed in 7.0]    */
/**************************************************************/

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'NewOrChangedNotification')
              AND (type = 'TR')))
  DROP TRIGGER NewOrChangedNotification

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'RemovedNotification')
              AND (type = 'TR')))
  DROP TRIGGER RemovedNotification
go

/**************************************************************/
/* TRIG_NOTIFICATION_INS_OR_UPD                               */
/**************************************************************/

PRINT ''
PRINT 'Creating trigger trig_notification_ins_or_upd...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_notification_ins_or_upd')
              AND (type = 'TR')))
  DROP TRIGGER trig_notification_ins_or_upd
go
CREATE TRIGGER trig_notification_ins_or_upd
ON msdb.dbo.sysnotifications
FOR INSERT,
    UPDATE
AS
BEGIN
  SET NOCOUNT ON

  -- First, throw out 'non-notification' rows
  DELETE FROM msdb.dbo.sysnotifications
  WHERE (notification_method = 0)

  -- Reset the has_notification flag for the affected alerts
  UPDATE msdb.dbo.sysalerts
  SET has_notification = 0
  FROM inserted           i,
       msdb.dbo.sysalerts sa
  WHERE (i.alert_id = sa.id)

  -- Update sysalerts.has_notification (for email)
  UPDATE msdb.dbo.sysalerts
  SET has_notification = has_notification | 1
  FROM msdb.dbo.sysalerts        sa,
       msdb.dbo.sysnotifications sn,
       inserted                  i
  WHERE (sa.id = sn.alert_id)
    AND (sa.id = i.alert_id)
    AND ((sn.notification_method & 1) = 1)

  -- Update sysalerts.has_notification (for pager)
  UPDATE msdb.dbo.sysalerts
  SET has_notification = has_notification | 2
  FROM msdb.dbo.sysalerts        sa,
       msdb.dbo.sysnotifications sn,
       inserted                  i
  WHERE (sa.id = sn.alert_id)
    AND (sa.id = i.alert_id)
    AND ((sn.notification_method & 2) = 2)

  -- Update sysalerts.has_notification (for netsend)
  UPDATE msdb.dbo.sysalerts
  SET has_notification = has_notification | 4
  FROM msdb.dbo.sysalerts        sa,
       msdb.dbo.sysnotifications sn,
       inserted                  i
  WHERE (sa.id = sn.alert_id)
    AND (sa.id = i.alert_id)
    AND ((sn.notification_method & 4) = 4)
END
go

/**************************************************************/
/* TRIG_NOTIFICATION_DELETE                                   */
/**************************************************************/

PRINT ''
PRINT 'Creating trigger trig_notification_delete...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_notification_delete')
              AND (type = 'TR')))
  DROP TRIGGER trig_notification_delete
go
CREATE TRIGGER trig_notification_delete
ON msdb.dbo.sysnotifications
FOR DELETE
AS
BEGIN
  SET NOCOUNT ON

  -- Reset the has_notification flag for the affected alerts
  UPDATE msdb.dbo.sysalerts
  SET has_notification = 0
  FROM deleted            d,
       msdb.dbo.sysalerts sa
  WHERE (d.alert_id = sa.id)

  -- Update sysalerts.has_notification (for email)
  UPDATE msdb.dbo.sysalerts
  SET has_notification = has_notification | 1
  FROM msdb.dbo.sysalerts        sa,
       msdb.dbo.sysnotifications sn,
       deleted                   d
  WHERE (sa.id = sn.alert_id)
    AND (sa.id = d.alert_id)
    AND ((sn.notification_method & 1) = 1)

  -- Update sysalerts.has_notification (for pager)
  UPDATE msdb.dbo.sysalerts
  SET has_notification = has_notification | 2
  FROM msdb.dbo.sysalerts        sa,
       msdb.dbo.sysnotifications sn,
       deleted                   d
  WHERE (sa.id = sn.alert_id)
    AND (sa.id = d.alert_id)
    AND ((sn.notification_method & 2) = 2)

  -- Update sysalerts.has_notification (for netsend)
  UPDATE msdb.dbo.sysalerts
  SET has_notification = has_notification | 4
  FROM msdb.dbo.sysalerts        sa,
       msdb.dbo.sysnotifications sn,
       deleted                   d
  WHERE (sa.id = sn.alert_id)
    AND (sa.id = d.alert_id)
    AND ((sn.notification_method & 4) = 4)
END
go

/**************************************************************/
/**                                                          **/
/**       B A C K U P   H I S T O R Y   S U P P O R T        **/
/**                                                          **/
/**************************************************************/

/**************************************************************/
/* T A B L E S                                                */
/**************************************************************/

/**************************************************************/
/* BACKUPMEDIASET                                             */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'backupmediaset')))
BEGIN
  PRINT ''
  PRINT 'Creating table backupmediaset...'

  CREATE TABLE backupmediaset
  (
  media_set_id       INT IDENTITY     NOT NULL PRIMARY KEY,
  media_uuid         UNIQUEIDENTIFIER NULL,  -- Null if this media set only one media family
  media_family_count TINYINT          NULL,  -- Number of media families in the media set
  name               NVARCHAR(128)    NULL,
  description        NVARCHAR(255)    NULL,
  software_name      NVARCHAR(128)    NULL,
  software_vendor_id INT              NULL,
  MTF_major_version  TINYINT          NULL,
  mirror_count       TINYINT          NULL,   -- number of mirror plexes
  is_password_protected BIT           NULL,
  is_compressed      BIT              NULL    -- 1 if backup compression was used
  )

  CREATE INDEX backupmediasetuuid ON backupmediaset (media_uuid)
END
ELSE
BEGIN
  IF EXISTS (
    select * from msdb.dbo.syscolumns where name='password_protected' and id =
        (select id from msdb.dbo.sysobjects where name='backupmediaset'))

    ALTER TABLE backupmediaset DROP COLUMN password_protected

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='is_password_protected' and id =
        (select id from msdb.dbo.sysobjects where name='backupmediaset'))

    ALTER TABLE backupmediaset ADD is_password_protected BIT NULL

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='is_compressed' and id =
        (select id from msdb.dbo.sysobjects where name='backupmediaset'))

    ALTER TABLE backupmediaset ADD is_compressed BIT NULL

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='mirror_count' and id =
        (select id from msdb.dbo.sysobjects where name='backupmediaset'))

    ALTER TABLE backupmediaset ADD mirror_count TINYINT NULL

END
go

/**************************************************************/
/* BACKUPMEDIAFAMILY                                          */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'backupmediafamily')))
BEGIN
  PRINT ''
  PRINT 'Creating table backupmediafamily...'

  CREATE TABLE backupmediafamily
  (
  media_set_id           INT              NOT NULL REFERENCES backupmediaset(media_set_id),
  family_sequence_number TINYINT          NOT NULL, -- Raid sequence number
  media_family_id        UNIQUEIDENTIFIER NULL,     -- This will be a uuid in MTF 2.0, allow space
  media_count            INT              NULL,     -- Number of media in the family
  logical_device_name    NVARCHAR(128)    NULL,     -- Name from sysdevices, if any
  physical_device_name   NVARCHAR(260)    NULL,     -- To facilitate restores from online media (disk)
  device_type            TINYINT          NULL,  -- Disk, tape, pipe, ...
  physical_block_size    INT              NULL,
  mirror             TINYINT        DEFAULT 0 NOT NULL
  PRIMARY KEY (media_set_id, family_sequence_number, mirror)
  )

  CREATE INDEX backupmediafamilyuuid ON backupmediafamily (media_family_id)
END
ELSE
BEGIN
  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='mirror' and id =
        (select id from msdb.dbo.sysobjects where name='backupmediafamily'))
  BEGIN
    begin tran

    -- remove any old constraint, not involving mirror
   declare @pkName sysname
   DECLARE @sql NVARCHAR(4000)
   select @pkName=i.name
      from
         sys.indexes i,
         sys.all_objects o
      where
         o.object_id = object_id ('backupmediafamily')
         and o.object_id = i.object_id
         and i.is_primary_key = 1
   IF (@pkName IS NOT NULL)
   begin
      select @sql = N'ALTER TABLE backupmediafamily DROP CONSTRAINT ' + @pkName
      EXEC (@sql)
   end

   ALTER TABLE backupmediafamily ADD mirror TINYINT DEFAULT 0 NOT NULL

   ALTER TABLE backupmediafamily ADD CONSTRAINT backupmediafamily_PK
      PRIMARY KEY (media_set_id, family_sequence_number, mirror)

   commit    
  END
END
go

/**************************************************************/
/* BACKUPSET - One row per backup operation.                  */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'backupset')))
BEGIN
  PRINT ''
  PRINT 'Creating table backupset...'

  CREATE TABLE backupset
  (
  backup_set_id          INT IDENTITY     NOT NULL PRIMARY KEY,
  backup_set_uuid        UNIQUEIDENTIFIER NOT NULL,
  media_set_id           INT              NOT NULL REFERENCES backupmediaset(media_set_id),
  first_family_number    TINYINT          NULL,  -- family number & media number of the media
  first_media_number     SMALLINT         NULL,  -- containing the start of this backup (first SSET)
  last_family_number     TINYINT          NULL,  -- family number & media number of the media
  last_media_number      SMALLINT         NULL,  -- containing the end of this backup (ESET after MBC)
  catalog_family_number  TINYINT          NULL,  -- family number & media number of the media
  catalog_media_number   SMALLINT         NULL,  -- containing the start of the 'directory' data stream

  position               INT              NULL,  -- For FILE=
  expiration_date        DATETIME         NULL,

  -- From SSET...
  software_vendor_id     INT              NULL,  -- Might want table for sw vendors
  name                   NVARCHAR(128)    NULL,
  description            NVARCHAR(255)    NULL,
  user_name              NVARCHAR(128)    NULL,
  software_major_version TINYINT          NULL, 
  software_minor_version TINYINT          NULL, 
  software_build_version SMALLINT         NULL,
  time_zone              SMALLINT         NULL,    
  mtf_minor_version      TINYINT          NULL,

  -- From CONFIG_INFO...
  first_lsn              NUMERIC(25,0)    NULL,
  last_lsn               NUMERIC(25,0)    NULL,
  checkpoint_lsn         NUMERIC(25,0)    NULL,
  database_backup_lsn    NUMERIC(25,0)    NULL,
  database_creation_date DATETIME         NULL,
  backup_start_date      DATETIME         NULL,
  backup_finish_date     DATETIME         NULL,
  type                   CHAR(1)          NULL,
  sort_order             SMALLINT         NULL,
  code_page              SMALLINT         NULL,
  compatibility_level    TINYINT          NULL,
  database_version       INT              NULL,
  backup_size            NUMERIC(20,0)    NULL,
  database_name          NVARCHAR(128)    NULL,
  server_name            NVARCHAR(128)    NULL,
  machine_name           NVARCHAR(128)    NULL,
  flags                  INT              NULL,
  unicode_locale         INT              NULL,
  unicode_compare_style  INT              NULL,
  collation_name         NVARCHAR(128)    NULL,
  is_password_protected  BIT              NULL,
  recovery_model         NVARCHAR(60)     NULL,
  has_bulk_logged_data   BIT              NULL,
  is_snapshot            BIT              NULL,
  is_readonly            BIT              NULL,
  is_single_user         BIT              NULL,
  has_backup_checksums   BIT              NULL,
  is_damaged             BIT              NULL,
  begins_log_chain       BIT              NULL,
  has_incomplete_metadata BIT             NULL,
  is_force_offline       BIT              NULL,
  is_copy_only           BIT              NULL,
  first_recovery_fork_guid UNIQUEIDENTIFIER NULL,
  last_recovery_fork_guid UNIQUEIDENTIFIER NULL,
  fork_point_lsn         NUMERIC(25,0)    NULL,
  database_guid          UNIQUEIDENTIFIER NULL,
  family_guid            UNIQUEIDENTIFIER NULL,
  differential_base_lsn  NUMERIC(25,0)    NULL,
  differential_base_guid UNIQUEIDENTIFIER NULL,
  compressed_backup_size NUMERIC(20,0)    NULL
  )

  CREATE INDEX backupsetuuid ON backupset (backup_set_uuid)
  CREATE INDEX backupsetDate ON backupset (backup_finish_date) -- helps sp_delete_backuphistory
  CREATE INDEX backupsetMediaSetId ON backupset (media_set_id) -- helps sp_delete_backuphistory
  CREATE INDEX backupsetDatabaseName ON backupset (database_name) INCLUDE (backup_set_id, media_set_id) -- helps sp_delete_backuphistory
END
ELSE
BEGIN
  IF EXISTS (
    select * from msdb.dbo.syscolumns where name='password_protected' and id =
        (select id from msdb.dbo.sysobjects where name='backupset'))

    ALTER TABLE backupset DROP COLUMN password_protected

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='flags' and id =
        (select id from msdb.dbo.sysobjects where name='backupset'))

    ALTER TABLE backupset ADD flags INT NULL

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='collation_name' and id =
        (select id from msdb.dbo.sysobjects where name='backupset'))

    ALTER TABLE backupset ADD
      unicode_locale         INT              NULL,
      unicode_compare_style  INT              NULL,
      collation_name         NVARCHAR(128)    NULL

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='is_password_protected' and id =
        (select id from msdb.dbo.sysobjects where name='backupset'))

    ALTER TABLE backupset ADD is_password_protected BIT NULL

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='compressed_backup_size' and id =
        (select id from msdb.dbo.sysobjects where name='backupset'))

    ALTER TABLE backupset ADD compressed_backup_size NUMERIC(20,0) NULL

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='recovery_model' and id =
        (select id from msdb.dbo.sysobjects where name='backupset'))
   ALTER TABLE backupset ADD
      recovery_model         NVARCHAR(60)     NULL,
      has_bulk_logged_data   BIT              NULL,
      is_snapshot            BIT              NULL,
      is_readonly            BIT              NULL,
      is_single_user         BIT              NULL,
      has_backup_checksums   BIT              NULL,
      is_damaged             BIT              NULL,
      begins_log_chain       BIT              NULL,
      has_incomplete_metadata BIT             NULL,
      is_force_offline       BIT              NULL,
      is_copy_only           BIT              NULL,
      first_recovery_fork_guid UNIQUEIDENTIFIER NULL,
      last_recovery_fork_guid UNIQUEIDENTIFIER NULL,
      fork_point_lsn         NUMERIC(25,0)    NULL,
      database_guid          UNIQUEIDENTIFIER NULL,
      family_guid            UNIQUEIDENTIFIER NULL,
      differential_base_lsn  NUMERIC(25,0)    NULL,
      differential_base_guid UNIQUEIDENTIFIER NULL

  IF NOT EXISTS (
      SELECT *
      FROM msdb.sys.indexes
      WHERE (name = 'backupsetDate'))
    CREATE INDEX backupsetDate ON backupset (backup_finish_date)
  IF NOT EXISTS (
      SELECT *
      FROM msdb.sys.indexes
      WHERE (name = 'backupsetMediaSetId'))
    CREATE INDEX backupsetMediaSetId ON backupset (media_set_id)
  IF NOT EXISTS (
      SELECT *
      FROM msdb.sys.indexes
      WHERE (name = 'backupsetDatabaseName'))
    CREATE INDEX backupsetDatabaseName ON backupset (database_name) INCLUDE (backup_set_id, media_set_id)
END
go

/**************************************************************/
-- BACKUPFILE/FILEGROUP 
-- One row per file/filegroup backed up
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'backupfilegroup')))
BEGIN
  PRINT ''
  PRINT 'Creating table backupfilegroup...'

  CREATE TABLE backupfilegroup
  (
  backup_set_id          INT           NOT NULL REFERENCES backupset(backup_set_id),
  name                   NVARCHAR(128) NOT NULL,
  filegroup_id           INT           NOT NULL,
  filegroup_guid         UNIQUEIDENTIFIER NULL,   
  type                   CHAR(2)       NOT NULL,
  type_desc              NVARCHAR(60)  NOT NULL,
  is_default             BIT           NOT NULL,
  is_readonly            BIT           NOT NULL,
  log_filegroup_guid     UNIQUEIDENTIFIER NULL
  PRIMARY KEY (backup_set_id, filegroup_id)
  )
END
go

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'backupfile')))
BEGIN
  PRINT ''
  PRINT 'Creating table backupfile...'

  CREATE TABLE backupfile
  (
  backup_set_id          INT           NOT NULL REFERENCES backupset(backup_set_id),
  first_family_number    TINYINT       NULL,     -- Family number & media number of he first media
  first_media_number     SMALLINT      NULL,     -- containing this file
  filegroup_name         NVARCHAR(128) NULL,
  page_size              INT           NULL,
  file_number            NUMERIC(10,0) NOT NULL,
  backed_up_page_count   NUMERIC(10,0) NULL,
  file_type              CHAR(1)       NULL,     -- database or log
  source_file_block_size NUMERIC(10,0) NULL,
  file_size              NUMERIC(20,0) NULL,
  logical_name           NVARCHAR(128) NULL,
  physical_drive         NVARCHAR(260) NULL,     -- Drive or partition name
  physical_name          NVARCHAR(260) NULL,     -- Remainder of physical (OS) filename
  state                  TINYINT       NULL,
  state_desc             NVARCHAR(64)  NULL,
  create_lsn             NUMERIC(25,0) NULL,
  drop_lsn               NUMERIC(25,0) NULL,
  file_guid              UNIQUEIDENTIFIER NULL,
  read_only_lsn          NUMERIC(25,0) NULL,
  read_write_lsn         NUMERIC(25,0) NULL,
  differential_base_lsn  NUMERIC(25,0) NULL,
  differential_base_guid UNIQUEIDENTIFIER NULL,
  backup_size            NUMERIC(20,0) NULL,
  filegroup_guid         UNIQUEIDENTIFIER NULL,
  is_readonly            BIT NULL,
  is_present             BIT NULL
  PRIMARY KEY (backup_set_id, file_number)
  )
END
ELSE
BEGIN
  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='state' and id =
        (select id from msdb.dbo.sysobjects where name='backupfile'))
  BEGIN
    -- we want NVARCHAR instead of VARCHAR
   ALTER TABLE backupfile ALTER COLUMN
      physical_drive         NVARCHAR(260) NULL
   ALTER TABLE backupfile ALTER COLUMN
      physical_name          NVARCHAR(260) NULL

    ALTER TABLE backupfile ADD
     state                  TINYINT       NULL,
     state_desc             NVARCHAR(64)  NULL,
     create_lsn             NUMERIC(25,0) NULL,
     drop_lsn               NUMERIC(25,0) NULL,
     file_guid              UNIQUEIDENTIFIER NULL,
     read_only_lsn          NUMERIC(25,0) NULL,
     read_write_lsn         NUMERIC(25,0) NULL,
     differential_base_lsn  NUMERIC(25,0) NULL,
     differential_base_guid UNIQUEIDENTIFIER NULL,
     backup_size            NUMERIC(20,0) NULL,
     filegroup_guid         UNIQUEIDENTIFIER NULL,
     is_readonly            BIT NULL,
     is_present             BIT NULL
  END
END
go

/**************************************************************/
/* RESTOREHISTORY - One row per restore operation.            */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'restorehistory')))
BEGIN
  PRINT ''
  PRINT 'Creating table restorehistory...'

  CREATE TABLE restorehistory
  (
  restore_history_id        INT           NOT NULL IDENTITY PRIMARY KEY,
  restore_date              DATETIME      NULL,
  destination_database_name NVARCHAR(128) NULL,
  user_name                 NVARCHAR(128) NULL,
  backup_set_id             INT           NOT NULL REFERENCES backupset(backup_set_id), -- The backup set restored
  restore_type              CHAR(1)       NULL,      -- Database, file, filegroup, log, verifyonly, ...

  -- Various options...
  replace                   BIT           NULL,      -- Replace(1), Noreplace(0)
  recovery                  BIT           NULL,      -- Recovery(1), Norecovery(0)
  restart                   BIT           NULL,      -- Restart(1), Norestart(0)
  stop_at                   DATETIME      NULL,
  device_count              TINYINT       NULL,      -- Can be less than number of media families
  stop_at_mark_name         NVARCHAR(128) NULL,
  stop_before               BIT           NULL
  )

  CREATE INDEX restorehistorybackupset ON restorehistory (backup_set_id)
END

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.syscolumns
                WHERE (name in ('stop_at_mark_name', 'stop_before'))
                AND (id = (SELECT id
                          FROM msdb.dbo.sysobjects
                          WHERE (name = 'restorehistory')))))
BEGIN
  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='stop_before' and id =
        (select id from msdb.dbo.sysobjects where name='restorehistory'))
  BEGIN
    PRINT ''
    PRINT 'Adding columns to table restorehistory...'

    ALTER TABLE restorehistory
      ADD
      stop_at_mark_name       NVARCHAR(128) NULL,
      stop_before             BIT           NULL
  END
END
go

/**************************************************************/
/* RESTOREFILE - One row per file restored.                   */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'restorefile')))
BEGIN
  PRINT ''
  PRINT 'Creating table restorefile...'

  CREATE TABLE restorefile
  (
  restore_history_id     INT           NOT NULL REFERENCES restorehistory(restore_history_id),
  file_number            NUMERIC(10,0) NULL,      -- Note: requires database to make unique
  destination_phys_drive NVARCHAR(260)  NULL,
  destination_phys_name  NVARCHAR(260)  NULL
  )

  CREATE INDEX restorefileRestoreHistoryId ON restorefile (restore_history_id)
END
ELSE
BEGIN
  ALTER TABLE restorefile ALTER COLUMN
   destination_phys_drive NVARCHAR(260) NULL
  ALTER TABLE restorefile ALTER COLUMN
   destination_phys_name  NVARCHAR(260) NULL
  IF NOT EXISTS (
      SELECT *
      FROM msdb.sys.indexes
      WHERE (name = 'restorefileRestoreHistoryId'))
    CREATE INDEX restorefileRestoreHistoryId ON restorefile (restore_history_id)
END
go

/**************************************************************/
/* RESTOREFILEGROUP - One row per filegroup restored.         */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'restorefilegroup')))
BEGIN
  PRINT ''
  PRINT 'Creating table restorefilegroup...'

  CREATE TABLE restorefilegroup
  (
  restore_history_id INT           NOT NULL REFERENCES restorehistory(restore_history_id),
  filegroup_name     NVARCHAR(128) NULL
  )

  CREATE INDEX restorefilegroupRestoreHistoryId ON restorefilegroup (restore_history_id)
END 
ELSE
BEGIN
  IF NOT EXISTS (
      SELECT *
      FROM msdb.sys.indexes
      WHERE (name = 'restorefilegroupRestoreHistoryId'))
    CREATE INDEX restorefilegroupRestoreHistoryId ON restorefilegroup (restore_history_id)
END
go

/**************************************************************/
/* LOGMARKHISTORY - One row per log mark generated            */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'logmarkhistory')))
BEGIN
  PRINT ''
  PRINT 'Creating table logmarkhistory...'

  CREATE TABLE logmarkhistory
  (
  database_name     NVARCHAR(128)   NOT NULL,
  mark_name         NVARCHAR(128)   NOT NULL,
  description       NVARCHAR(255)   NULL,
  user_name         NVARCHAR(128)   NOT NULL,
  lsn               NUMERIC(25,0)   NOT NULL,
  mark_time         DATETIME        NOT NULL
  )

  CREATE INDEX logmarkhistory1 ON logmarkhistory (database_name, mark_name)

  CREATE INDEX logmarkhistory2 ON logmarkhistory (database_name, lsn)
END
go

IF (EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'trig_backupset_delete')
                  AND (OBJECTPROPERTY(id, 'IsTrigger') != 0)))
BEGIN
  DROP TRIGGER trig_backupset_delete
END
go

CREATE TRIGGER trig_backupset_delete ON msdb.dbo.backupset FOR DELETE AS
BEGIN
  DELETE FROM msdb.dbo.logmarkhistory from deleted
  WHERE (msdb.dbo.logmarkhistory.database_name = deleted.database_name)
    AND (msdb.dbo.logmarkhistory.lsn >= deleted.first_lsn)
    AND (msdb.dbo.logmarkhistory.lsn < deleted.last_lsn)
END
go

/**************************************************************/
/* suspect_pages */
/**************************************************************/

IF (EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'badpagehistory')))
   DROP TABLE badpagehistory
go               
IF (EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'suspect_page_table')))
   DROP TABLE suspect_page_table
go               

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'suspect_pages')))
BEGIN
  PRINT ''
  PRINT 'Creating table suspect_pages...'

  CREATE TABLE suspect_pages
  (
  database_id           INT          NOT NULL,
  file_id               INT          NOT NULL,
  page_id               bigint       NOT NULL, -- we only use unsigned 32bits
  event_type            INT          NOT NULL,
  error_count           INT          NOT NULL,
  last_update_date      DATETIME     NOT NULL DEFAULT GETDATE()
  )
END
go


/**************************************************************/
/**                                                          **/
/**           O B J E C T    P E R M I S S I O N S           **/
/**                                                          **/
/**************************************************************/

--------------------------------------------------------------
-- SQL Agent roles and procs
--------------------------------------------------------------
PRINT ''
PRINT 'Setting object permissions...'
go
-- Create the TargetServers role (for use by target servers when downloading jobs / uploading status)
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysusers
            WHERE (name = N'TargetServersRole')
              AND (issqlrole = 1)))
BEGIN
  -- If there are no members in the role, then drop and re-create it
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.sysusers   su,
            msdb.dbo.sysmembers sm
       WHERE (su.uid = sm.groupuid)
         AND (su.name = N'TargetServersRole')
         AND (su.issqlrole = 1)) = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_droprole @rolename = N'TargetServersRole'
    EXECUTE msdb.dbo.sp_addrole @rolename = N'TargetServersRole'
  END
END
ELSE
  EXECUTE msdb.dbo.sp_addrole @rolename = N'TargetServersRole'
  
  
 -- Create the SQLAgentUserRole role
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysusers
            WHERE (name = N'SQLAgentUserRole')
              AND (issqlrole = 1)))
BEGIN
  -- If there are no members in the role, then drop and re-create it
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.sysusers   su,
            msdb.dbo.sysmembers sm
       WHERE (su.uid = sm.groupuid)
         AND (su.name = N'SQLAgentUserRole')
         AND (su.issqlrole = 1)) = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_droprole @rolename = N'SQLAgentUserRole'
    EXECUTE msdb.dbo.sp_addrole @rolename = N'SQLAgentUserRole'
  END
END
ELSE
  EXECUTE msdb.dbo.sp_addrole @rolename = N'SQLAgentUserRole'  

-- Create the SQLAgentReaderRole role
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysusers
            WHERE (name = N'SQLAgentReaderRole')
              AND (issqlrole = 1)))
BEGIN
  -- If there are no members in the role, then drop and re-create it
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.sysusers   su,
            msdb.dbo.sysmembers sm
       WHERE (su.uid = sm.groupuid)
         AND (su.name = N'SQLAgentReaderRole')
         AND (su.issqlrole = 1)) = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_droprole @rolename = N'SQLAgentReaderRole'
    EXECUTE msdb.dbo.sp_addrole @rolename = N'SQLAgentReaderRole'
  END
END
ELSE
  EXECUTE msdb.dbo.sp_addrole @rolename = N'SQLAgentReaderRole'  

-- Create the SQLAgentOperatorRole role
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysusers
            WHERE (name = N'SQLAgentOperatorRole')
              AND (issqlrole = 1)))
BEGIN
  -- If there are no members in the role, then drop and re-create it
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.sysusers   su,
            msdb.dbo.sysmembers sm
       WHERE (su.uid = sm.groupuid)
         AND (su.name = N'SQLAgentOperatorRole')
         AND (su.issqlrole = 1)) = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_droprole @rolename = N'SQLAgentOperatorRole'
    EXECUTE msdb.dbo.sp_addrole @rolename = N'SQLAgentOperatorRole'
  END
END
ELSE
  EXECUTE msdb.dbo.sp_addrole @rolename = N'SQLAgentOperatorRole'  


-- Add roles to each other.
-- SQLAgentReaderRole is also SQLAgentUserRole
-- SQLAgentOperatorRole is also SQLAgentReaderRole and SQLAgentUserRole

EXECUTE sp_addrolemember @rolename = 'SQLAgentUserRole' , 
                   @membername = 'SQLAgentReaderRole' 

EXECUTE sp_addrolemember @rolename = 'SQLAgentReaderRole' , 
                   @membername = 'SQLAgentOperatorRole' 
go

GRANT EXECUTE ON sp_notify_operator          TO SQLAgentUserRole

-- Permissions a non-SA needs to create/update/delete a job
GRANT EXECUTE ON sp_get_sqlagent_properties  TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_category            TO SQLAgentUserRole
GRANT EXECUTE ON sp_enum_sqlagent_subsystems TO SQLAgentUserRole
GRANT EXECUTE ON sp_add_jobserver            TO SQLAgentUserRole
GRANT EXECUTE ON sp_delete_jobserver         TO SQLAgentUserRole
GRANT SELECT  ON syscategories               TO SQLAgentUserRole

GRANT EXECUTE ON sp_help_jobhistory  TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobhistory_full TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobhistory_summary TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobhistory_sem TO SQLAgentUserRole

GRANT EXECUTE ON sp_purge_jobhistory  TO SQLAgentOperatorRole

GRANT EXECUTE ON sp_add_jobstep    TO SQLAgentUserRole
GRANT EXECUTE ON sp_update_jobstep TO SQLAgentUserRole
GRANT EXECUTE ON sp_delete_jobstep TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobstep   TO SQLAgentUserRole

GRANT EXECUTE ON sp_help_jobsteplog      TO SQLAgentUserRole
GRANT EXECUTE ON sp_delete_jobsteplog    TO SQLAgentUserRole

--Schedule related SP's
GRANT EXECUTE ON sp_add_schedule       TO SQLAgentUserRole
GRANT EXECUTE ON sp_update_schedule       TO SQLAgentUserRole
GRANT EXECUTE ON sp_delete_schedule       TO SQLAgentUserRole
GRANT EXECUTE ON sp_attach_schedule       TO SQLAgentUserRole
GRANT EXECUTE ON sp_detach_schedule       TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_schedule         TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobcount         TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobs_in_schedule TO SQLAgentUserRole

GRANT EXECUTE ON sp_add_jobschedule    TO SQLAgentUserRole
GRANT EXECUTE ON sp_update_jobschedule TO SQLAgentUserRole
GRANT EXECUTE ON sp_delete_jobschedule TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobschedule   TO SQLAgentUserRole

GRANT EXECUTE ON sp_add_job             TO SQLAgentUserRole
GRANT EXECUTE ON sp_update_job          TO SQLAgentUserRole
GRANT EXECUTE ON sp_delete_job          TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_job            TO SQLAgentUserRole
GRANT EXECUTE ON sp_start_job           TO SQLAgentUserRole
GRANT EXECUTE ON sp_stop_job            TO SQLAgentUserRole

--alert spocs
GRANT EXECUTE ON sp_help_alert              TO SQLAgentOperatorRole

--proxy sprocs
GRANT EXECUTE ON sp_help_proxy           TO SQLAgentUserRole
GRANT EXECUTE ON sp_enum_login_for_proxy TO SQLAgentOperatorRole

--other
GRANT EXECUTE ON sp_help_jobserver        TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_targetserver    TO SQLAgentOperatorRole
GRANT EXECUTE ON sp_help_notification    TO SQLAgentOperatorRole

GRANT EXECUTE ON sp_check_for_owned_jobs     TO SQLAgentUserRole
GRANT EXECUTE ON sp_check_for_owned_jobsteps TO SQLAgentUserRole
GRANT EXECUTE ON sp_get_jobstep_db_username  TO SQLAgentUserRole
GRANT EXECUTE ON sp_get_job_alerts           TO SQLAgentUserRole

GRANT EXECUTE ON sp_uniquetaskname TO SQLAgentUserRole
GRANT EXECUTE ON sp_addtask        TO SQLAgentUserRole
GRANT EXECUTE ON sp_droptask       TO SQLAgentUserRole

GRANT SELECT ON sysjobs_view                    TO SQLAgentUserRole
GRANT SELECT ON sysschedules_localserver_view   TO SQLAgentUserRole

GRANT SELECT  ON sysnotifications        TO SQLAgentOperatorRole
GRANT SELECT  ON sysoperators            TO SQLAgentOperatorRole
GRANT SELECT  ON sysalerts               TO SQLAgentOperatorRole

REVOKE ALL ON systargetservers                 FROM PUBLIC
REVOKE ALL ON systargetservers_view            FROM PUBLIC
REVOKE ALL ON systargetservergroups            FROM PUBLIC
REVOKE ALL ON systargetservergroupmembers      FROM PUBLIC
REVOKE INSERT, UPDATE, DELETE ON syscategories FROM PUBLIC
REVOKE ALL ON sysalerts                        FROM PUBLIC
REVOKE ALL ON sysoperators                     FROM PUBLIC
REVOKE ALL ON sysnotifications                 FROM PUBLIC

REVOKE ALL ON systargetservers                 FROM SQLAgentUserRole
REVOKE ALL ON systargetservers_view            FROM SQLAgentUserRole
REVOKE ALL ON systargetservergroups            FROM SQLAgentUserRole
REVOKE ALL ON systargetservergroupmembers      FROM SQLAgentUserRole
REVOKE INSERT, UPDATE, DELETE ON syscategories FROM SQLAgentUserRole

REVOKE ALL ON sysalerts                        FROM SQLAgentUserRole
REVOKE ALL ON sysoperators                     FROM SQLAgentUserRole
REVOKE ALL ON sysnotifications                 FROM SQLAgentUserRole

--DENY TargetServerRole permission that would allow modifying of jobs
DENY ALL ON sp_add_jobserver     TO TargetServersRole
DENY ALL ON sp_delete_jobserver  TO TargetServersRole

DENY ALL ON sp_add_jobstep    TO TargetServersRole
DENY ALL ON sp_update_jobstep TO TargetServersRole
DENY ALL ON sp_delete_jobstep TO TargetServersRole

DENY ALL ON sp_add_jobschedule    TO TargetServersRole
DENY ALL ON sp_update_jobschedule TO TargetServersRole
DENY ALL ON sp_delete_jobschedule TO TargetServersRole

DENY ALL ON sp_add_job    TO TargetServersRole
DENY ALL ON sp_update_job TO TargetServersRole
DENY ALL ON sp_delete_job TO TargetServersRole
DENY ALL ON sp_start_job  TO TargetServersRole
DENY ALL ON sp_stop_job   TO TargetServersRole
DENY ALL ON sp_post_msx_operation       TO TargetServersRole

DENY ALL ON sp_addtask        TO TargetServersRole
DENY ALL ON sp_droptask       TO TargetServersRole


GRANT SELECT ON backupfile        TO PUBLIC
GRANT SELECT ON backupmediafamily TO PUBLIC
GRANT SELECT ON backupmediaset    TO PUBLIC
GRANT SELECT ON backupset         TO PUBLIC
GRANT SELECT ON restorehistory    TO PUBLIC
GRANT SELECT ON restorefile       TO PUBLIC
GRANT SELECT ON restorefilegroup  TO PUBLIC
GRANT SELECT ON logmarkhistory    TO PUBLIC
GRANT SELECT ON suspect_pages     TO PUBLIC

GRANT SELECT, UPDATE, DELETE ON sysdownloadlist               TO TargetServersRole
GRANT SELECT, UPDATE         ON sysjobservers                 TO TargetServersRole
GRANT SELECT, UPDATE         ON systargetservers              TO TargetServersRole
GRANT EXECUTE                ON sp_downloaded_row_limiter     TO TargetServersRole
GRANT SELECT                 ON sysjobs                       TO TargetServersRole
GRANT EXECUTE                ON sp_help_jobstep               TO TargetServersRole
GRANT EXECUTE                ON sp_help_jobschedule           TO TargetServersRole
GRANT EXECUTE                ON sp_sqlagent_refresh_job       TO TargetServersRole
GRANT EXECUTE                ON sp_sqlagent_probe_msx         TO TargetServersRole
GRANT EXECUTE                ON sp_sqlagent_check_msx_version TO TargetServersRole
GRANT EXECUTE                ON sp_enlist_tsx                 TO TargetServersRole
GRANT SELECT                 ON syssubsystems                 TO TargetServersRole

GRANT EXECUTE                ON sp_help_jobactivity           TO SQLAgentUserRole
GRANT EXECUTE                ON sp_help_operator              TO SQLAgentUserRole
                                                    
go

USE msdb                                                  
go                                                                          
                                                          
/**************************************************************/         
/**************************************************************/         
/* BEGIN DTS                                                  */         
/**************************************************************/         
/**************************************************************/

/**************************************************************/
/* DTS TABLES                                                 */
/* These are never dropped since we dropped MSDB itself if    */
/* this was an upgrade from pre-beta3, and we preserve beta3  */
/* packages.  However, we need to add the owner_sid column    */
/* if it's not there already, defaulting to sa ownership.     */
/**************************************************************/

/**************************************************************/
/* SYSDTSCATEGORIES                                           */
/**************************************************************/
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysdtscategories')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysdtscategories...'
  CREATE TABLE sysdtscategories
  (
    name                   sysname             NOT NULL,
    description            NVARCHAR(1024)      NULL,
    id                     UNIQUEIDENTIFIER    NOT NULL,
    parentid               UNIQUEIDENTIFIER    NOT NULL,         --// IID_NULL if a predefined root category
    CONSTRAINT pk_dtscategories PRIMARY KEY (id),
    CONSTRAINT uq_dtscategories_name_parent UNIQUE (name, parentid)
  )

  /**************************************************************/
  /* PREDEFINED DTS CATEGORIES                                  */
  /**************************************************************/
  PRINT ''
  PRINT 'Adding predefined dts categories...'
  --// MUST BE IN SYNC with DTSPkg.h!
  --// These must be INSERTed explicitly as the IID_NULL parent does not exist.
  INSERT sysdtscategories VALUES (N'Local', 'DTS Packages stored on local SQL Server', 'B8C30000-A282-11D1-B7D9-00C04FB6EFD5', '00000000-0000-0000-0000-000000000000')
  INSERT sysdtscategories VALUES (N'Repository', 'DTS Packages stored on Repository', 'B8C30001-A282-11D1-B7D9-00C04FB6EFD5', '00000000-0000-0000-0000-000000000000')

  --// Default location for DTSPackage.SaveToSQLServer
  INSERT sysdtscategories VALUES (N'LocalDefault', 'Default local subcategory for DTS Packages', 'B8C30002-A282-11D1-B7D9-00C04FB6EFD5', 'B8C30000-A282-11D1-B7D9-00C04FB6EFD5')
END
GO

/**************************************************************/
/* SYSDTSPACKAGES                                             */
/**************************************************************/
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysdtspackages')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysdtspackages...'
  CREATE TABLE sysdtspackages
  (
    name                   sysname             NOT NULL,                   --// May have multiple ids
    id                     UNIQUEIDENTIFIER    NOT NULL,                   --// May have multiple versionids
    versionid              UNIQUEIDENTIFIER    NOT NULL UNIQUE,
    description            NVARCHAR(1024)      NULL,
    categoryid             UNIQUEIDENTIFIER    NOT NULL REFERENCES sysdtscategories (id),
    createdate             DATETIME,
    owner                  sysname,
    packagedata            IMAGE,
    owner_sid              VARBINARY(85)       NOT NULL DEFAULT SUSER_SID(N'sa'),
    packagetype            int                 NOT NULL DEFAULT 0          --// DTSPkgType_Default
    CONSTRAINT pk_dtspackages PRIMARY KEY (id, versionid)
  )
END ELSE BEGIN
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.syscolumns
                  WHERE name = N'owner_sid' AND id = OBJECT_ID(N'sysdtspackages')))
  BEGIN
    PRINT ''
    PRINT 'Altering table sysdtspackages for owner_sid and packagetype...'
    ALTER TABLE sysdtspackages ADD owner_sid VARBINARY(85) NOT NULL DEFAULT SUSER_SID(N'sa'),
                                   packagetype int NOT NULL DEFAULT 0      --// DTSPkgType_Default
  END
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.syscolumns
                  WHERE name = N'packagetype' AND id = OBJECT_ID(N'sysdtspackages')))
  BEGIN
    PRINT ''
    PRINT 'Altering table sysdtspackages for packagetype...'
    ALTER TABLE sysdtspackages ADD packagetype int NOT NULL DEFAULT 0      --// DTSPkgType_Default
  END
END
GO


/***************************************************************/
/* Create SSIS roles										   */
/***************************************************************/
if not exists (select * from dbo.sysusers where [name] = N'db_ssisadmin' and [issqlrole] = 1)
BEGIN
EXEC sp_addrole N'db_ssisadmin' 
END
GO

if not exists (select * from dbo.sysusers where [name] = N'db_ssisltduser' and [issqlrole] = 1)
BEGIN
EXEC sp_addrole N'db_ssisltduser'
END
GO

if not exists (select * from dbo.sysusers where [name] = N'db_ssisoperator' and [issqlrole] = 1)
BEGIN
EXEC sp_addrole N'db_ssisoperator'
END
GO


/**************************************************************/
/* SP_MAKE_DTSPACKAGENAME                                     */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_get_dtsversion...'
go
IF OBJECT_ID(N'sp_get_dtsversion') IS NOT NULL
  DROP PROCEDURE sp_get_dtsversion
go
CREATE PROCEDURE sp_get_dtsversion
AS
  /* Values for this are same as @@microsoftversion */
  /* @@microsoftversion format is 0xaaiibbbb (aa = major, ii = minor, bb[bb] = build #) */
  DECLARE @i INT
  select @i = 0x08000000   /* Must be in hex! */

  /* Select the numeric value, and a conversion to make it readable */
  select N'Microsoft SQLDTS Scripts' = @i, N'Version' = convert(binary(4), @i)
GO
GRANT  EXECUTE  ON sp_get_dtsversion	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_get_dtsversion	TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_get_dtsversion	TO [db_ssisoperator]
GO

/**************************************************************/
/* SP_MAKE_DTSPACKAGENAME                                     */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_make_dtspackagename...'
go
IF OBJECT_ID(N'sp_make_dtspackagename') IS NOT NULL
  DROP PROCEDURE sp_make_dtspackagename
go
CREATE PROCEDURE sp_make_dtspackagename
  @categoryid UNIQUEIDENTIFIER,
  @name sysname OUTPUT,
  @flags int = 0
AS
  SET NOCOUNT ON

  --// If NULL catid, default to the LocalDefault category.
  IF (@categoryid IS NULL)
    SELECT @categoryid = 'B8C30002-A282-11d1-B7D9-00C04FB6EFD5'

  --// Validate category.  We'll generate a unique name within category.
  DECLARE @stringfromclsid NVARCHAR(200)
  IF NOT EXISTS (SELECT * FROM sysdtscategories WHERE id = @categoryid)
  BEGIN
    SELECT @stringfromclsid = CONVERT(NVARCHAR(50), @categoryid)
    RAISERROR(14262, 16, 1, '@categoryid', @stringfromclsid)
    RETURN(1) -- Failure
  END

  --// Autogenerate the next name in our format.
  DECLARE @max sysname, @i INT, @spidchar NVARCHAR(20)

  --// Any logic we use may have collisions so let's get the max and wrap if we have to.
  --// @@spid is necessary for guaranteed uniqueness but makes it ugly so for now, don't use it.
  --// Note:  use only 9 characters as it makes the pattern match easier without overflowing.
  SELECT @i = 0, @spidchar = '_'               -- + LTRIM(STR(@@spid)) + '_'
  SELECT @max = MAX(name)
    FROM sysdtspackages
    WHERE name like 'DTS_' + @spidchar + '[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]'
  IF @max IS NOT NULL
    SELECT @i = CONVERT(INT, SUBSTRING(@max, (DATALENGTH(N'DTS_' + @spidchar) / 2) + 1, 9))

  --// Wrap if needed.  Find a gap in the names.
  IF @i < 999999999
  BEGIN
    SELECT @i = @i + 1
  END ELSE BEGIN
    SELECT @i = 1
    DECLARE @existingname sysname
    DECLARE hC CURSOR LOCAL FOR SELECT name FROM sysdtspackages WHERE categoryid = @categoryid ORDER BY name FOR READ ONLY
    OPEN hC
    FETCH NEXT FROM hC INTO @existingname
    WHILE @@FETCH_STATUS = 0 AND @i < 999999999
    BEGIN
      SELECT @name = 'DTS_' + @spidchar + REPLICATE('0', 9 - DATALENGTH(LTRIM(STR(@i)))) + LTRIM(STR(@i))
      IF @existingname > @name
        BREAK
      SELECT @i = @i + 1
      FETCH NEXT FROM hC INTO @existingname
    END
    CLOSE hC
    DEALLOCATE hC
  END

  --// Set the name.
  SELECT @name = 'DTS_' + @spidchar + REPLICATE('0', 9 - DATALENGTH(LTRIM(STR(@i)))) + LTRIM(STR(@i))
  IF (@flags & 1) <> 0
    SELECT @name
GO
GRANT  EXECUTE  ON sp_make_dtspackagename	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_make_dtspackagename	TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_make_dtspackagename	TO [db_ssisoperator]
GO

/**************************************************************/
/* SP_ADD_DTSPACKAGE                                          */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_add_dtspackage...'
GO
IF OBJECT_ID(N'sp_add_dtspackage') IS NOT NULL
  DROP PROCEDURE sp_add_dtspackage
GO
CREATE PROCEDURE sp_add_dtspackage
  @name sysname,
  @id UNIQUEIDENTIFIER,
  @versionid UNIQUEIDENTIFIER,
  @description NVARCHAR(255),
  @categoryid UNIQUEIDENTIFIER,
  @owner sysname,
  @packagedata IMAGE,
  @packagetype int = 0     --// DTSPkgType_Default
AS
  SET NOCOUNT ON

  --// If NULL catid, default to the LocalDefault category.
  IF (@categoryid IS NULL)
    SELECT @categoryid = 'B8C30002-A282-11d1-B7D9-00C04FB6EFD5'

  --// Autogenerate name if it came in NULL.  If it didn't, the below will validate uniqueness.
  IF DATALENGTH(@name) = 0
    SELECT @name = NULL
  IF @name IS NULL
  BEGIN
    --// First see if they specified a new version based on id instead of name.
    if @id IS NOT NULL
    BEGIN
      SELECT @name = name
        FROM sysdtspackages WHERE @id = id
      IF @name IS NOT NULL
        GOTO AddPackage          -- OK, add with the existing name
    END

    --// Name not available, autogenerate one.
    exec sp_make_dtspackagename @categoryid, @name OUTPUT
    GOTO AddPackage
  END

  --// Verify name unique within category.  Allow a new versionid of the same name though.
  IF EXISTS (SELECT * FROM sysdtspackages WHERE name = @name AND categoryid = @categoryid AND id <> @id)
  BEGIN
    RAISERROR (14590, -1, -1, @name)
    RETURN(1) -- Failure
  END

  --// Verify that the same id is not getting a different name.
  IF EXISTS (SELECT * FROM sysdtspackages WHERE id = @id AND name <> @name)
  BEGIN
    DECLARE @stringfromclsid NVARCHAR(200)
    SELECT @stringfromclsid = CONVERT(NVARCHAR(50), @id)
    RAISERROR (14597, -1, -1, @stringfromclsid)
    RETURN(1) -- Failure
  END

  --// Verify all versions of a package go in the same category.
  IF EXISTS (SELECT * FROM sysdtspackages WHERE id = @id AND categoryid <> @categoryid)
  BEGIN
    RAISERROR (14596, -1, -1, @name)
    RETURN(1) -- Failure
  END

  --// The real information is in the IMAGE; the rest is "documentary".
  --// Therefore, there is no need to verify anything.
  --// The REFERENCE in sysdtspackages will validate @categoryid.
AddPackage:

  --// We will use the original owner_sid for all new versions - all must have the same owner.
  --// New packages will get the current login's SID as owner_sid.
  DECLARE @owner_sid VARBINARY(85)
  SELECT @owner_sid = MIN(owner_sid) FROM sysdtspackages WHERE id = @id
  IF @@rowcount = 0 OR @owner_sid IS NULL
  BEGIN
    SELECT @owner_sid = SUSER_SID()
  END ELSE BEGIN
    --// Only the owner of DTS Package ''%s'' or a member of the sysadmin role may create new versions of it.
    IF (@owner_sid <> SUSER_SID() AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
    BEGIN
      RAISERROR (14586, -1, -1, @name)
      RETURN(1) -- Failure
    END
  END

  --// Everything checks out, add the package or its new version.
  INSERT sysdtspackages (
    name,
    id,
    versionid,
    description,
    categoryid,
    createdate,
    owner,
    packagedata,
    owner_sid,
   packagetype
  ) VALUES (
    @name,
    @id,
    @versionid,
    @description,
    @categoryid,
    GETDATE(),
    @owner,
    @packagedata,
    @owner_sid,
   @packagetype
  )
  RETURN 0    -- SUCCESS
GO
GRANT  EXECUTE  ON sp_add_dtspackage	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_add_dtspackage	TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_add_dtspackage	TO [db_ssisoperator]
GO

/**************************************************************/
/* SP_DROP_DTSPACKAGE                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_drop_dtspackage...'
go
IF OBJECT_ID(N'sp_drop_dtspackage') IS NOT NULL
  DROP PROCEDURE sp_drop_dtspackage
go
CREATE PROCEDURE sp_drop_dtspackage
  @name sysname,
  @id UNIQUEIDENTIFIER,
  @versionid UNIQUEIDENTIFIER
AS
  SET NOCOUNT ON

  --// Does the specified package (uniquely) exist?  Referencing by name only may not be unique.
  --// We do a bit of a temporary solution here as SQL can't handle a DISTINCT clause with UNIQUEIDENTIFIER.
  --// @id will get the first id returned; if only name specified, see if there are more.
  DECLARE @findid UNIQUEIDENTIFIER
  SELECT @findid = id FROM sysdtspackages
    WHERE (@name IS NOT NULL OR @id IS NOT NULL OR @versionid IS NOT NULL)
      AND (@name IS NULL OR @name = name)
      AND (@id IS NULL OR @id = id)
      AND (@versionid IS NULL or @versionid = versionid)
  IF @@rowcount = 0
  BEGIN
    DECLARE @pkgnotfound NVARCHAR(200)
    DECLARE @dts_package_res NVARCHAR(100)
    SELECT @pkgnotfound = FORMATMESSAGE(14599) + ' = ''' + ISNULL(@name, FORMATMESSAGE(14589)) + '''; ' + FORMATMESSAGE(14588) + ' {'
    SELECT @pkgnotfound = @pkgnotfound + CASE WHEN @id IS NULL THEN FORMATMESSAGE(14589) ELSE CONVERT(NVARCHAR(50), @id) END + '}.{'
    SELECT @pkgnotfound = @pkgnotfound + CASE WHEN @versionid IS NULL THEN FORMATMESSAGE(14589) ELSE CONVERT(NVARCHAR(50), @versionid) END + '}'
    SELECT @dts_package_res = FORMATMESSAGE(14594)
    RAISERROR(14262, 16, 1, @dts_package_res, @pkgnotfound)
    RETURN(1) -- Failure
  END ELSE IF @name IS NOT NULL AND @id IS NULL AND @versionid IS NULL AND
      EXISTS (SELECT * FROM sysdtspackages WHERE name = @name AND id <> @findid)
  BEGIN
    RAISERROR(14595, -1, -1, @name)
    RETURN(1) -- Failure
  END
  SELECT @id = @findid

  --// Only the owner of DTS Package ''%s'' or a member of the sysadmin role may drop it or any of its versions.
  --// sp_add_dtspackage ensures that all versions have the same owner_sid.
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    IF (NOT EXISTS (SELECT * FROM sysdtspackages WHERE id = @id AND owner_sid = SUSER_SID()))
    BEGIN
      SELECT @name = name FROM sysdtspackages WHERE id = @id
      RAISERROR (14587, -1, -1, @name)
      RETURN(1) -- Failure
    END
  END

  --// If @versionid is NULL, drop all versions of name, else only the @versionid version.
  DELETE sysdtspackages
  WHERE id = @id
    AND (@versionid IS NULL OR @versionid = versionid)
  RETURN 0    -- SUCCESS
go
GRANT  EXECUTE  ON sp_drop_dtspackage	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_drop_dtspackage	TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_drop_dtspackage	TO [db_ssisoperator]
go

/**************************************************************/
/* SP_REASSIGN_DTSPACKAGEOWNER                                */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_reassign_dtspackageowner...'
go
IF OBJECT_ID(N'sp_reassign_dtspackageowner') IS NOT NULL
  DROP PROCEDURE sp_reassign_dtspackageowner
go
CREATE PROCEDURE sp_reassign_dtspackageowner
  @name sysname,
  @id UNIQUEIDENTIFIER,
  @newloginname sysname
AS
  SET NOCOUNT ON

  --// First, is this a valid login?
  IF SUSER_SID(@newloginname) IS NULL
  BEGIN
    RAISERROR(14262, -1, -1, '@newloginname', @newloginname)
    RETURN(1) -- Failure
  END

  --// Does the specified package (uniquely) exist?  Referencing by name only may not be unique.
  --// We do a bit of a temporary solution here as SQL can't handle a DISTINCT clause with UNIQUEIDENTIFIER.
  --// @id will get the first id returned; if only name specified, see if there are more.
  DECLARE @findid UNIQUEIDENTIFIER
  SELECT @findid = id FROM sysdtspackages
    WHERE (@name IS NOT NULL OR @id IS NOT NULL)
      AND (@name IS NULL OR @name = name)
      AND (@id IS NULL OR @id = id)
  IF @@rowcount = 0
  BEGIN
    DECLARE @pkgnotfound NVARCHAR(200)
    DECLARE @dts_package_res NVARCHAR(100)
    SELECT @pkgnotfound = FORMATMESSAGE(14599) + ' = ''' + ISNULL(@name, FORMATMESSAGE(14589)) + '''; ' + FORMATMESSAGE(14588) + ' {'
    SELECT @pkgnotfound = @pkgnotfound + CASE WHEN @id IS NULL THEN FORMATMESSAGE(14589) ELSE CONVERT(NVARCHAR(50), @id) END + '}.{'
    SELECT @pkgnotfound = @pkgnotfound + FORMATMESSAGE(14589) + '}'
    SELECT @dts_package_res = FORMATMESSAGE(14594)
    RAISERROR(14262, 16, 1, @dts_package_res, @pkgnotfound)
    RETURN(1) -- Failure
  END ELSE IF @name IS NOT NULL AND @id IS NULL AND
      EXISTS (SELECT * FROM sysdtspackages WHERE name = @name AND id <> @findid)
  BEGIN
    RAISERROR(14595, -1, -1, @name)
    RETURN(1) -- Failure
  END
  SELECT @id = @findid

  --// Only the owner of DTS Package ''%s'' or a member of the sysadmin role may reassign its ownership.
  --// sp_add_dtspackage ensures that all versions have the same owner_sid.
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    IF (NOT EXISTS (SELECT * FROM sysdtspackages WHERE id = @id AND owner_sid = SUSER_SID()))
    BEGIN
      SELECT @name = name FROM sysdtspackages WHERE id = @id
      RAISERROR (14585, -1, -1, @name)
      RETURN(1) -- Failure
    END
  END

  --// Everything checks out, so reassign the owner.
  --// Note that @newloginname may be a sql server login rather than a network user,
  --// which is not quite the same as when a package is created.
  UPDATE sysdtspackages
    SET owner_sid = SUSER_SID(@newloginname),
       owner = @newloginname
   WHERE id = @id

  RETURN 0    -- SUCCESS
GO
GRANT  EXECUTE  ON sp_reassign_dtspackageowner	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_reassign_dtspackageowner	TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_reassign_dtspackageowner	TO [db_ssisoperator]
GO

/**************************************************************/
/* SP_GET_DTSPACKAGE                                          */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_get_dtspackage...'
go
IF OBJECT_ID(N'sp_get_dtspackage') IS NOT NULL
  DROP PROCEDURE sp_get_dtspackage
go
CREATE PROCEDURE sp_get_dtspackage
  @name sysname,
  @id UNIQUEIDENTIFIER,
  @versionid UNIQUEIDENTIFIER
AS
  SET NOCOUNT ON

  --// Does the specified package (uniquely) exist?  Dropping by name only may not be unique.
  --// We do a bit of a temporary solution here as SQL can't handle a DISTINCT clause with UNIQUEIDENTIFIER.
  --// @id will get the first id returned; if only name specified, see if there are more.
  DECLARE @findid UNIQUEIDENTIFIER
  SELECT @findid = id FROM sysdtspackages
    WHERE (@name IS NOT NULL OR @id IS NOT NULL OR @versionid IS NOT NULL)
      AND (@name IS NULL OR @name = name)
      AND (@id IS NULL OR @id = id)
      AND (@versionid IS NULL or @versionid = versionid)
  IF @@rowcount = 0
  BEGIN
    DECLARE @pkgnotfound NVARCHAR(200)
    DECLARE @dts_package_res NVARCHAR(100)
    SELECT @pkgnotfound = FORMATMESSAGE(14599) + ' = ''' + ISNULL(@name, FORMATMESSAGE(14589)) + '''; ' + FORMATMESSAGE(14588) + ' {'
    SELECT @pkgnotfound = @pkgnotfound + CASE WHEN @id IS NULL THEN FORMATMESSAGE(14589) ELSE CONVERT(NVARCHAR(50), @id) END + '}.{'
    SELECT @pkgnotfound = @pkgnotfound + CASE WHEN @versionid IS NULL THEN FORMATMESSAGE(14589) ELSE CONVERT(NVARCHAR(50), @versionid) END + '}'
    SELECT @dts_package_res = FORMATMESSAGE(14594)
    RAISERROR(14262, 16, 1, @dts_package_res, @pkgnotfound)
    RETURN(1) -- Failure
  END ELSE IF @name IS NOT NULL AND @id IS NULL AND @versionid IS NULL AND
      EXISTS (SELECT * FROM sysdtspackages WHERE name = @name AND id <> @findid)
  BEGIN
    RAISERROR(14595, -1, -1, @name)
    RETURN(1) -- Failure
  END
  SELECT @id = @findid

  --// If @versionid is NULL, select all versions of name, else only the @versionid version.
  --// This must return the IMAGE as the rightmost column.
  SELECT
    name,
    id,
    versionid,
    description,
    createdate,
    owner,
    pkgsize = datalength(packagedata),
    packagedata,
    isowner = CASE WHEN (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1 OR owner_sid = SUSER_SID()) THEN 1 ELSE 0 END,
   packagetype
  FROM sysdtspackages
  WHERE id = @id
    AND (@versionid IS NULL OR @versionid = versionid)
  ORDER BY name, createdate DESC

  RETURN 0    -- SUCCESS
go
GRANT  EXECUTE  ON sp_get_dtspackage	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_get_dtspackage	TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_get_dtspackage	TO [db_ssisoperator]
go

/**************************************************************/
/* SP_REASSIGN_DTSPACKAGECATEGORY                             */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_reassign_dtspackagecategory...'
go
IF OBJECT_ID(N'sp_reassign_dtspackagecategory') IS NOT NULL
  DROP PROCEDURE sp_reassign_dtspackagecategory
go
CREATE PROCEDURE sp_reassign_dtspackagecategory
  @packageid UNIQUEIDENTIFIER,
  @categoryid UNIQUEIDENTIFIER
AS
  SET NOCOUNT ON

  --// Does the package exist?
  DECLARE @stringfromclsid NVARCHAR(200)
  IF NOT EXISTS (SELECT * from sysdtspackages WHERE id = @packageid)
  BEGIN
    SELECT @stringfromclsid = CONVERT(NVARCHAR(50), @packageid)
    RAISERROR(14262, 16, 1, '@packageid', @stringfromclsid)
    RETURN(1) -- Failure
  END

  --// Does the category exist?
  IF NOT EXISTS (SELECT * FROM sysdtscategories WHERE id = @categoryid)
  BEGIN
    SELECT @stringfromclsid = CONVERT(NVARCHAR(50), @categoryid)
    RAISERROR(14262, 16, 1, '@categoryid', @stringfromclsid)
    RETURN(1) -- Failure
  END

  UPDATE sysdtspackages SET categoryid = @categoryid WHERE id = @packageid
go

/**************************************************************/
/* SP_ENUM_DTSPACKAGES                                        */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_enum_dtspackages...'
go
IF OBJECT_ID(N'sp_enum_dtspackages') IS NOT NULL
  DROP PROCEDURE sp_enum_dtspackages
go
CREATE PROCEDURE sp_enum_dtspackages
  @name_like sysname = '%',
  @description_like NVARCHAR(255) = '%',
  @categoryid UNIQUEIDENTIFIER = NULL,
  @flags INT = 0,          --// Bitmask:  0x01 == return image data
                           --//           0x02 == recursive (packagenames and categorynames only)
                           --//           0x04 == all versions (default == only most-recent-versions)
                           --//           0x08 == all prior versions versions (not most-recent; requires @id)
  @id UNIQUEIDENTIFIER = NULL,    --// If non-NULL, enum versions of this package.
  @wanttype int = NULL            --// If non-NULL, enum only packages of the given type
AS
  IF (@flags & 0x02) <> 0
    GOTO DO_RECURSE

  --// Just return the non-IMAGE stuff - sp_get_dtspackage will return the
  --// actual dtspackage info.
  DECLARE @latestversiondate datetime
  SELECT @latestversiondate = NULL
  IF (@flags & 0x08 = 0x08)
  BEGIN
    SELECT @latestversiondate = MAX(t.createdate) FROM sysdtspackages t WHERE t.id = @id
    IF @latestversiondate IS NULL
    BEGIN
      DECLARE @pkgnotfound NVARCHAR(200)
      DECLARE @dts_package_res NVARCHAR(100)
      SELECT @pkgnotfound = FORMATMESSAGE(14599) + ' = ' + FORMATMESSAGE(14589) + '; ' + FORMATMESSAGE(14588) + ' {'
      SELECT @pkgnotfound = @pkgnotfound + CASE WHEN @id IS NULL THEN FORMATMESSAGE(14589) ELSE CONVERT(NVARCHAR(50), @id) END + '}.{'
      SELECT @pkgnotfound = @pkgnotfound + FORMATMESSAGE(14589) + '}'
      SELECT @dts_package_res = FORMATMESSAGE(14594)
      RAISERROR(14262, 16, 1, @dts_package_res, @pkgnotfound)
      RETURN(1) -- Failure
    END
  END
  SELECT
    p.name,
    p.id,
    p.versionid,
    p.description,
    p.createdate,
    p.owner,
    size = datalength(p.packagedata),
    packagedata = CASE (@flags & 0x01) WHEN 0 THEN NULL ELSE p.packagedata END,
    isowner = CASE WHEN (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1 OR p.owner_sid = SUSER_SID()) THEN 1 ELSE 0 END,
   p.packagetype
  FROM sysdtspackages p
  WHERE (@name_like IS NULL OR p.name LIKE @name_like)
    AND (@description_like IS NULL OR p.description LIKE @description_like)
    AND (@categoryid IS NULL OR p.categoryid = @categoryid)
    AND (@id is NULL OR p.id = @id)
    -- These filter by version
    AND ( (@flags & 0x08 = 0x08 AND p.createdate < @latestversiondate)
          OR ( (@flags & 0x04 = 0x04)
             OR (@flags & 0x08 = 0 AND p.createdate = (SELECT MAX(t.createdate) FROM sysdtspackages t WHERE t.id = p.id))
             )
        )
   AND (@wanttype is NULL or p.packagetype = @wanttype)
  ORDER BY id, createdate DESC
  RETURN 0    -- SUCCESS

  DO_RECURSE:
  DECLARE @packagesfound INT
  SELECT @packagesfound = 0

  --// Starting parent category.  If null, start at root.
  if (@categoryid IS NULL)
    SELECT @categoryid = '00000000-0000-0000-0000-000000000000'

  IF EXISTS (SELECT *
      FROM sysdtspackages p INNER JOIN sysdtscategories c ON p.categoryid = c.id
      WHERE p.categoryid = @categoryid
      AND (@name_like IS NULL OR p.name LIKE @name_like)
      AND (@description_like IS NULL OR p.description LIKE @description_like)
    )
    SELECT @packagesfound = 1

  IF (@packagesfound <> 0)
  BEGIN
    --// Identify the category and list its Packages.
    SELECT 'Level' = @@nestlevel, 'PackageName' = p.name, 'CategoryName' = c.name
        FROM sysdtspackages p INNER JOIN sysdtscategories c ON p.categoryid = c.id
        WHERE p.categoryid = @categoryid
        AND (@name_like IS NULL OR p.name LIKE @name_like)
        AND (@description_like IS NULL OR p.description LIKE @description_like)
  END

  --// List its subcategories' packages
  DECLARE @childid UNIQUEIDENTIFIER
  DECLARE hC CURSOR LOCAL FOR SELECT id FROM sysdtscategories c WHERE parentid = @categoryid ORDER BY c.name FOR READ ONLY
  OPEN hC
  FETCH NEXT FROM hC INTO @childid
  WHILE @@FETCH_STATUS = 0
  BEGIN
    EXECUTE sp_enum_dtspackages @name_like, @description_like, @childid, @flags
    FETCH NEXT FROM hC INTO @childid
  END
  CLOSE hC
  DEALLOCATE hC
  RETURN 0
go

GRANT  EXECUTE  ON sp_enum_dtspackages	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_enum_dtspackages	TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_enum_dtspackages	TO [db_ssisoperator]
go

/**************************************************************/
/* SP_ADD_DTSCATEGORY                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_add_dtscategory...'
go
IF OBJECT_ID(N'sp_add_dtscategory') IS NOT NULL
  DROP PROCEDURE sp_add_dtscategory
go
CREATE PROCEDURE sp_add_dtscategory
  @name sysname,
  @description NVARCHAR(1024),
  @id UNIQUEIDENTIFIER,
  @parentid UNIQUEIDENTIFIER
AS
  SET NOCOUNT ON

  --// If parentid is NULL, use 'Local'
  IF @parentid IS NULL
    SELECT @parentid = 'B8C30000-A282-11d1-B7D9-00C04FB6EFD5'

  --// First do some simple validation of "non-assert" cases.  UI should validate others and the table
  --// definitions will act as an "assert", but we check here (with a nice message) for user-error stuff
  --// it would be hard for UI to validate.
  IF NOT EXISTS (SELECT * FROM sysdtscategories WHERE id = @parentid)
  BEGIN
    DECLARE @stringfromclsid NVARCHAR(200)
    SELECT @stringfromclsid = CONVERT(NVARCHAR(50), @parentid)
    RAISERROR(14262, 16, 1, '@parentid', @stringfromclsid)
    RETURN(1) -- Failure
  END

  IF EXISTS (SELECT * FROM sysdtscategories WHERE name = @name AND parentid = @parentid)
  BEGIN
    RAISERROR(14591, 16, -1, @name)
    RETURN(1) -- Failure
  END

  --// id uniqueness is ensured by the primary key.
  INSERT sysdtscategories (
    name,
    description,
    id,
    parentid
  ) VALUES (
    @name,
    @description,
    @id,
    @parentid
  )
  RETURN 0    -- SUCCESS
go

/**************************************************************/
/* SP_DROP_DTSCATEGORY                                        */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_drop_dtscategory...'
go
IF OBJECT_ID(N'sp_drop_dtscategory') IS NOT NULL
  DROP PROCEDURE sp_drop_dtscategory
go
CREATE PROCEDURE sp_drop_dtscategory
  @name_like sysname,
  @id UNIQUEIDENTIFIER = NULL,
  @flags INT = 0           --// Bitmask:  0x01 == recursive (drop all subcategories and packages)
AS
  SET NOCOUNT ON

  --// Temp table in case recursion is needed.
  DECLARE @recurse TABLE (id UNIQUEIDENTIFIER, passcount INT DEFAULT(0))

  IF (@name_like IS NOT NULL)
  BEGIN
    INSERT @recurse (id) SELECT id FROM sysdtscategories WHERE name LIKE @name_like
    IF @@rowcount = 0
    BEGIN
      RAISERROR(14262, 16, 1, '@name_like', @name_like)
      RETURN(1) -- Failure
    END
    IF @@rowcount > 1
    BEGIN
      RAISERROR(14592, 16, -1, @name_like)
      RETURN(1) -- Failure
    END
    SELECT @name_like = name, @id = id FROM sysdtscategories WHERE name LIKE @name_like
  END ELSE BEGIN
    --// Verify the id.  @name_like will be NULL if we're here so no need to initialize.
    SELECT @name_like = name FROM sysdtscategories WHERE id = @id
    IF @name_like IS NULL
    BEGIN
      DECLARE @stringfromclsid NVARCHAR(200)
      SELECT @stringfromclsid = CONVERT(NVARCHAR(50), @id)
      RAISERROR(14262, 16, 1, '@id', @stringfromclsid)
      RETURN(1) -- Failure
    END
    INSERT @recurse (id) VALUES (@id)
  END

  --// We now have a unique category.

  --// Cannot drop the predefined categories (or the root, which already failed above as IID_NULL
  --// is not an id in sysdtscategories).  These will be at top level.
  IF @id IN (
    'B8C30000-A282-11d1-B7D9-00C04FB6EFD5'
    , 'B8C30001-A282-11d1-B7D9-00C04FB6EFD5'
    , 'B8C30002-A282-11d1-B7D9-00C04FB6EFD5'
  ) BEGIN
      RAISERROR(14598, 16, 1)
      RETURN(1) -- Failure
  END

  --// Check for subcategories or packages.
  IF EXISTS (SELECT * FROM sysdtspackages WHERE categoryid = @id)
             OR EXISTS (SELECT * FROM sysdtscategories WHERE parentid = @id)
  BEGIN
    --// It does.  Make sure recursion was requested.
    IF (@flags & 0x01 = 0)
    BEGIN
      RAISERROR(14593, 16, -1, @name_like)
      RETURN(1) -- Failure
    END

    --// Fill up @recurse.
    UPDATE @recurse SET passcount = 0
    WHILE (1 = 1)
    BEGIN
      UPDATE @recurse SET passcount = passcount + 1
      INSERT @recurse (id, passcount)
        SELECT c.id, 0 FROM sysdtscategories c INNER JOIN @recurse r ON c.parentid = r.id
        WHERE passcount = 1
      IF @@rowcount = 0
        BREAK
    END
  END

  DELETE sysdtspackages FROM sysdtspackages INNER JOIN @recurse r ON sysdtspackages.categoryid = r.id
  DELETE sysdtscategories FROM sysdtscategories INNER JOIN @recurse r ON sysdtscategories.id = r.id

  RETURN(0) -- SUCCESS
go

/**************************************************************/
/* SP_MODIFY_DTSCATEGORY                                      */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_modify_dtscategory...'
go
IF OBJECT_ID(N'sp_modify_dtscategory') IS NOT NULL
  DROP PROCEDURE sp_modify_dtscategory
go
CREATE PROCEDURE sp_modify_dtscategory
  @id UNIQUEIDENTIFIER,
  @name sysname,
  @description NVARCHAR(1024),
  @parentid UNIQUEIDENTIFIER
AS
  SET NOCOUNT ON

  --// Validate.
  DECLARE @stringfromclsid NVARCHAR(200)
  IF NOT EXISTS (SELECT * FROM sysdtscategories WHERE id = @id)
  BEGIN
    SELECT @stringfromclsid = CONVERT(NVARCHAR(50), @id)
    RAISERROR(14262, 16, 1, '@id', @stringfromclsid)
    RETURN(1) -- Failure
  END

  IF NOT EXISTS (SELECT * FROM sysdtscategories WHERE id = @parentid)
  BEGIN
    SELECT @stringfromclsid = CONVERT(NVARCHAR(50), @parentid)
    RAISERROR(14262, 16, 1, '@parentid', @stringfromclsid)
    RETURN(1) -- Failure
  END

  --// Check the name uniqueness within parent, but make sure the id is different (we may just be renaming
  --// without reassigning parentage).
  IF EXISTS (SELECT * FROM sysdtscategories WHERE name = @name AND parentid = @parentid and id <> @id)
  BEGIN
    RAISERROR(14591, 16, -1, @name)
    RETURN(1) -- Failure
  END

  UPDATE sysdtscategories SET name = @name, description = @description, parentid = @parentid
    WHERE id = @id
  RETURN(0) -- SUCCESS
go

/**************************************************************/
/* SP_ENUM_DTSCATEGORIES                                      */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_enum_dtscategories...'
go
IF OBJECT_ID(N'sp_enum_dtscategories') IS NOT NULL
  DROP PROCEDURE sp_enum_dtscategories
go
CREATE PROCEDURE sp_enum_dtscategories
  @parentid UNIQUEIDENTIFIER = NULL,
  @flags INT = 0           --// Bitmask:  0x01 == recursive (enum all subcategories; names only)
AS
  IF (@flags & 0x01) <> 0
    GOTO DO_RECURSE

  --// Go to the root if no parentid specified
  IF @parentid IS NULL
    SELECT @parentid = '00000000-0000-0000-0000-000000000000'

  --// 'No results' is valid here.
  SELECT name, description, id FROM sysdtscategories WHERE parentid = @parentid
    ORDER BY name
  RETURN 0

  DO_RECURSE:

  --// Identify the category.
  IF @@nestlevel <> 0
    SELECT 'Level' = @@nestlevel, name FROM sysdtscategories WHERE id = @parentid

  --// List its subcategories
  DECLARE @childid UNIQUEIDENTIFIER
  DECLARE hC CURSOR LOCAL FOR SELECT id FROM sysdtscategories c WHERE parentid = @parentid ORDER BY c.name FOR READ ONLY
  OPEN hC
  FETCH NEXT FROM hC INTO @childid
  WHILE @@FETCH_STATUS = 0
  BEGIN
    EXECUTE sp_enum_dtscategories @childid, @flags
    FETCH NEXT FROM hC INTO @childid
  END
  CLOSE hC
  DEALLOCATE hC
  RETURN 0
go

/**************************************************************/
/* Drop Beta1 DTS Logging objects                             */
/**************************************************************/

if OBJECT_ID('sysdtspackagestepslog') IS NOT NULL
BEGIN
   PRINT ''
   PRINT 'Dropping Beta1 logging tables and stored procedures...'
   DROP TABLE sysdtspackagestepslog
   IF OBJECT_ID('sysdtspackagelog') IS NOT NULL
      DROP TABLE sysdtspackagelog
   IF OBJECT_ID('sp_log_dtspackage') IS NOT NULL
      DROP PROCEDURE sp_log_dtspackage
   IF OBJECT_ID('sp_log_dtspackagesteps') IS NOT NULL
      DROP PROCEDURE sp_log_dtspackagesteps
END

/**************************************************************/
/* SYSDTSPACKAGELOG                                           */
/**************************************************************/
if OBJECT_ID('sysdtspackagelog') IS NULL
BEGIN
  PRINT ''
  PRINT 'Creating table sysdtspackagelog...'
  CREATE TABLE sysdtspackagelog
  (
    name          sysname        NOT NULL,
    description            NVARCHAR(1000)    NULL,
    id               UNIQUEIDENTIFIER  NOT NULL,
    versionid           UNIQUEIDENTIFIER  NOT NULL,
    lineagefull            UNIQUEIDENTIFIER  NOT NULL PRIMARY KEY,
    lineageshort        INT         NOT NULL,
    starttime           DATETIME    NOT NULL,
    endtime          DATETIME    NULL,
    elapsedtime            double precision  NULL,
    computer            sysname        NOT NULL,
    operator            sysname        NOT NULL,
    logdate          datetime    NOT NULL DEFAULT GETDATE(),
    errorcode           INT         NULL,
    errordescription       NVARCHAR(2000)    NULL
  )
END

/**************************************************************/
/* SYSDTSSTEPLOG                                              */
/**************************************************************/
if OBJECT_ID('sysdtssteplog') IS NULL
BEGIN
  PRINT ''
  PRINT 'Creating table sysdtssteplog...'
  CREATE TABLE sysdtssteplog
  (
    stepexecutionid        BIGINT IDENTITY (1, 1)  NOT NULL PRIMARY KEY,
    lineagefull            UNIQUEIDENTIFIER  NOT NULL 
               REFERENCES sysdtspackagelog(lineagefull)
               ON DELETE CASCADE,
    stepname            sysname        NOT NULL,
    stepexecstatus         int            NULL,
    stepexecresult         int            NULL,
    starttime           DATETIME    NOT NULL,
    endtime          DATETIME    NULL,
    elapsedtime            double precision  NULL,
    errorcode           INT         NULL,
    errordescription       NVARCHAR(2000)    NULL,
    progresscount       BIGINT         NULL
  )
END ELSE BEGIN
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.syscolumns
                  WHERE name = N'stepexecresult' AND id = OBJECT_ID(N'sysdtssteplog')))
  BEGIN
    PRINT ''
    PRINT 'Altering table sysdtssteplog...'
    ALTER TABLE sysdtssteplog ADD stepexecresult INT NULL DEFAULT 0
  END
END

/**************************************************************/
/* SYSDTSTASKLOG                                              */
/**************************************************************/
if OBJECT_ID('sysdtstasklog') IS NULL
BEGIN
  PRINT ''
  PRINT 'Creating table sysdtstasklog...'
  CREATE TABLE sysdtstasklog
  (
    stepexecutionid        BIGINT         NOT NULL
               REFERENCES sysdtssteplog (stepexecutionid)
               ON DELETE CASCADE,
    sequenceid          INT         NOT NULL,
    errorcode           INT         NOT NULL,
    description            NVARCHAR(2000)    NULL,
    PRIMARY KEY            (stepexecutionid, sequenceid)
  )
END

/**************************************************************/
/* SP_LOG_DTSPACKAGE_BEGIN                                    */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_log_dtspackage_begin...'
GO
IF OBJECT_ID(N'sp_log_dtspackage_begin') IS NOT NULL
  DROP PROCEDURE sp_log_dtspackage_begin
GO
CREATE PROCEDURE sp_log_dtspackage_begin
  @name        sysname,
  @description    NVARCHAR(1000),
  @id       UNIQUEIDENTIFIER,
  @versionid      UNIQUEIDENTIFIER,
  @lineagefull    UNIQUEIDENTIFIER,
  @lineageshort      INT,
  @starttime      DATETIME,
  @computer    sysname,
  @operator    sysname
AS
  SET NOCOUNT ON

  INSERT sysdtspackagelog (
    name,
    description,
    id,
    versionid,
    lineagefull,
    lineageshort,
    starttime,
    computer,
    operator
  ) VALUES (
    @name,
    @description,
    @id,
    @versionid,
    @lineagefull,
    @lineageshort,
    @starttime,
    @computer,
    @operator
  )
  RETURN 0    -- SUCCESS
GO
GRANT  EXECUTE  ON sp_log_dtspackage_begin	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_log_dtspackage_begin	TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_log_dtspackage_begin	TO [db_ssisoperator]
GO

/**************************************************************/
/* SP_LOG_DTSPACKAGE_END                                      */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_log_dtspackage_end...'
GO
IF OBJECT_ID(N'sp_log_dtspackage_end') IS NOT NULL
  DROP PROCEDURE sp_log_dtspackage_end
GO
CREATE PROCEDURE sp_log_dtspackage_end
  @lineagefull    UNIQUEIDENTIFIER,
  @endtime     DATETIME,
  @elapsedtime    double precision,
  @errorcode      INT,
  @errordescription  NVARCHAR(2000)
AS
  SET NOCOUNT ON

  --// Validate lineage.
  DECLARE @stringfromclsid NVARCHAR(200)
  IF NOT EXISTS (SELECT * FROM sysdtspackagelog WHERE lineagefull = @lineagefull)
  BEGIN
    SELECT @stringfromclsid = CONVERT(NVARCHAR(50), @lineagefull)
    RAISERROR(14262, 16, 1, '@lineagefull', @stringfromclsid)
    RETURN(1) -- Failure
  END

  UPDATE sysdtspackagelog
    SET 
        endtime = @endtime,
        elapsedtime = @elapsedtime,
        errorcode = @errorcode,
        errordescription = @errordescription
    WHERE lineagefull = @lineagefull

  RETURN 0    -- SUCCESS
GO
GRANT  EXECUTE  ON sp_log_dtspackage_end	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_log_dtspackage_end	TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_log_dtspackage_end	TO [db_ssisoperator]
GO

/**************************************************************/
/* SP_LOG_DTSSTEP_BEGIN                                       */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_log_dtsstep_begin...'
GO
IF OBJECT_ID(N'sp_log_dtsstep_begin') IS NOT NULL
  DROP PROCEDURE sp_log_dtsstep_begin
GO
CREATE PROCEDURE sp_log_dtsstep_begin
  @lineagefull    UNIQUEIDENTIFIER,
  @stepname    sysname,
  @starttime      DATETIME
AS
  SET NOCOUNT ON

  --// Validate lineage.
  DECLARE @stringfromclsid NVARCHAR(200)
  IF NOT EXISTS (SELECT * FROM sysdtspackagelog WHERE lineagefull = @lineagefull)
  BEGIN
    SELECT @stringfromclsid = CONVERT(NVARCHAR(50), @lineagefull)
    RAISERROR(14262, 16, 1, '@lineagefull', @stringfromclsid)
    RETURN(1) -- Failure
  END

  INSERT sysdtssteplog (
    lineagefull,
    stepname,
    starttime
  ) VALUES (
    @lineagefull,
    @stepname,
    @starttime
  )

  --// Return the @@identity for sp_log_dtstask and sp_logdtsstep_end
  SELECT @@IDENTITY

  RETURN 0    -- SUCCESS
GO
GRANT  EXECUTE  ON sp_log_dtsstep_begin	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_log_dtsstep_begin	TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_log_dtsstep_begin	TO [db_ssisoperator]
GO

/**************************************************************/
/* SP_LOG_DTSSTEP_END                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_log_dtsstep_end...'
GO
IF OBJECT_ID(N'sp_log_dtsstep_end') IS NOT NULL
  DROP PROCEDURE sp_log_dtsstep_end
GO
CREATE PROCEDURE sp_log_dtsstep_end
  @stepexecutionid   BIGINT,
  @stepexecstatus int,
  @stepexecresult int,
  @endtime     DATETIME,
  @elapsedtime    double precision,
  @errorcode      INT,
  @errordescription  NVARCHAR(2000),
  @progresscount  BIGINT
AS
  SET NOCOUNT ON

  --// Validate @stepexecutionid.
  DECLARE @stringfromclsid NVARCHAR(200)
  IF NOT EXISTS (SELECT * FROM sysdtssteplog WHERE stepexecutionid = @stepexecutionid)
  BEGIN
    SELECT @stringfromclsid = CONVERT(NVARCHAR(50), @stepexecutionid)
    RAISERROR(14262, 16, 1, '@stepexecutionid', @stringfromclsid)
    RETURN(1) -- Failure
  END

  UPDATE sysdtssteplog
    SET 
        stepexecstatus = @stepexecstatus,
        stepexecresult = @stepexecresult,
        endtime = @endtime,
        elapsedtime = @elapsedtime,
        errorcode = @errorcode,
        errordescription = @errordescription,
        progresscount = @progresscount
    WHERE stepexecutionid = @stepexecutionid

  RETURN 0    -- SUCCESS
GO
GRANT  EXECUTE  ON sp_log_dtsstep_end	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_log_dtsstep_end	TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_log_dtsstep_end	TO [db_ssisoperator]
GO

/**************************************************************/
/* SP_LOG_DTSTASK                                             */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_log_dtstask...'
GO
IF OBJECT_ID(N'sp_log_dtstask') IS NOT NULL
  DROP PROCEDURE sp_log_dtstask
GO
CREATE PROCEDURE sp_log_dtstask
  @stepexecutionid   BIGINT,
  @sequenceid     INT,
  @errorcode      INT,
  @description    NVARCHAR(2000)
AS
  SET NOCOUNT ON

  --// Validate @stepexecutionid.
  DECLARE @stringfromclsid NVARCHAR(200)
  IF NOT EXISTS (SELECT * FROM sysdtssteplog WHERE stepexecutionid = @stepexecutionid)
  BEGIN
    SELECT @stringfromclsid = CONVERT(NVARCHAR(50), @stepexecutionid)
    RAISERROR(14262, 16, 1, '@stepexecutionid', @stringfromclsid)
    RETURN(1) -- Failure
  END

  INSERT sysdtstasklog (
    stepexecutionid,
    sequenceid,
    errorcode,
    description
  ) VALUES (
    @stepexecutionid,
    @sequenceid,
    @errorcode,
    @description
  )

  RETURN 0    -- SUCCESS
GO
GRANT  EXECUTE  ON sp_log_dtstask	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_log_dtstask	TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_log_dtstask	TO [db_ssisoperator]
GO

/**************************************************************/
/* SP_ENUM_DTSPACKAGELOG                                      */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_enum_dtspackagelog...'
GO
IF OBJECT_ID(N'sp_enum_dtspackagelog') IS NOT NULL
  DROP PROCEDURE sp_enum_dtspackagelog
GO
CREATE PROCEDURE sp_enum_dtspackagelog
  @name sysname,
  @flags INT = 0,                --// Bitmask:  0x01 == return only latest
  @id UNIQUEIDENTIFIER = NULL,      --// If non-NULL, use instead of @name.
  @versionid UNIQUEIDENTIFIER = NULL,  --// If non-NULL, use instead of @id or @name
  @lineagefull UNIQUEIDENTIFIER = NULL --// If non-NULL, use instead of @versionid or @id or @name
AS
  SET NOCOUNT ON

  --// This is used for realtime viewing of package logs, so don't error if no entries
  --// found, simply return an empty result set.
  SELECT
    p.name,
    p.description,
    p.id,
    p.versionid,
    p.lineagefull,
    p.lineageshort,
    p.starttime,
    p.endtime,
    p.elapsedtime,
    p.computer,
    p.operator,
    p.logdate,
    p.errorcode,
    p.errordescription
  FROM sysdtspackagelog p
  WHERE ((@lineagefull IS NULL OR p.lineagefull = @lineagefull)
      AND  (@versionid IS NULL OR p.versionid = @versionid)
      AND (@id IS NULL OR p.id = @id)
      AND (@name IS NULL OR p.name = @name))
    AND ((@flags & 0x01) = 0
      OR p.logdate = 
      (
        SELECT MAX(logdate) 
        FROM sysdtspackagelog d
        WHERE (d.id = p.id)
      )
     )
  ORDER BY logdate 

  RETURN 0    -- SUCCESS
GO
GRANT  EXECUTE  ON sp_enum_dtspackagelog	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_enum_dtspackagelog	TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_enum_dtspackagelog	TO [db_ssisoperator]
GO

/**************************************************************/
/* SP_ENUM_DTSSTEPLOG                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_enum_dtssteplog...'
GO
IF OBJECT_ID(N'sp_enum_dtssteplog') IS NOT NULL
  DROP PROCEDURE sp_enum_dtssteplog
GO
CREATE PROCEDURE sp_enum_dtssteplog
  @lineagefull    UNIQUEIDENTIFIER = NULL,   -- all steps in this package execution
  @stepexecutionid   BIGINT = NULL
AS
  SET NOCOUNT ON

  --// This is used for realtime viewing of package logs, so don't error if no entries
  --// found, simply return an empty result set.
  --// This query must be restricted within a single package execution (lineage); it may
  --// be further restricted by stepexecutionid to a single step within that package execution.
  SELECT
    stepexecutionid,
    lineagefull,
    stepname,
    stepexecstatus,
    stepexecresult,
    starttime,
    endtime,
    elapsedtime,
    errorcode,
    errordescription,
    progresscount
  FROM sysdtssteplog
  WHERE (@lineagefull IS NULL OR lineagefull = @lineagefull)
    AND (@stepexecutionid IS NULL OR stepexecutionid = @stepexecutionid)
  ORDER BY stepexecutionid

  RETURN 0    -- SUCCESS
GO
GRANT  EXECUTE  ON sp_enum_dtssteplog	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_enum_dtssteplog	TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_enum_dtssteplog	TO [db_ssisoperator]
GO

/**************************************************************/
/* SP_ENUM_DTSTASKLOG                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_enum_dtstasklog...'
GO
IF OBJECT_ID(N'sp_enum_dtstasklog') IS NOT NULL
  DROP PROCEDURE sp_enum_dtstasklog
GO
CREATE PROCEDURE sp_enum_dtstasklog
  @stepexecutionid   BIGINT,
  @sequenceid     INT = NULL
AS
  SET NOCOUNT ON

  --// This is used for realtime viewing of package logs, so don't error if no entries
  --// found, simply return an empty result set.
  --// This query must be restricted within a single step execution; it may
  --// be further restricted by stepexecutionid to a single record within that step execution.
  SELECT
    -- stepexecutionid,  -- this is always passed in so we don't need to return it.
    sequenceid,
    errorcode,
    description
  FROM sysdtstasklog
  WHERE (stepexecutionid IS NULL or stepexecutionid = @stepexecutionid)
    AND (@sequenceid IS NULL OR sequenceid = @sequenceid)
  ORDER BY sequenceid

  RETURN 0    -- SUCCESS
GO
GRANT  EXECUTE  ON sp_enum_dtstasklog	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_enum_dtstasklog	TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_enum_dtstasklog	TO [db_ssisoperator]
GO

/**************************************************************/
/* SP_DUMP_DTSLOG_ALL                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_dump_dtslog_all...'
GO
IF OBJECT_ID(N'sp_dump_dtslog_all') IS NOT NULL
  DROP PROCEDURE sp_dump_dtslog_all
GO
CREATE PROCEDURE sp_dump_dtslog_all
AS
  SET NOCOUNT ON

  --// sysadmin only.
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  DELETE sysdtspackagelog
  RETURN 0    -- SUCCESS
GO
GRANT  EXECUTE  ON sp_dump_dtslog_all		TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_dump_dtslog_all		TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_dump_dtslog_all		TO [db_ssisoperator]
GO

/**************************************************************/
/* SP_DUMP_DTSPACKAGELOG                                      */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_dump_dtspackagelog...'
GO
IF OBJECT_ID(N'sp_dump_dtspackagelog') IS NOT NULL
  DROP PROCEDURE sp_dump_dtspackagelog
GO
CREATE PROCEDURE sp_dump_dtspackagelog
  @name sysname,
  @flags INT = 0,                --// Bitmask:  0x01 == preserve latest
  @id UNIQUEIDENTIFIER = NULL,      --// If non-NULL, use instead of @name.
  @versionid UNIQUEIDENTIFIER = NULL,  --// If non-NULL, use instead of @id or @name
  @lineagefull UNIQUEIDENTIFIER = NULL --// If non-NULL, use instead of @versionid or @id or @name
AS
  SET NOCOUNT ON

  --// sysadmin only.
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  --// Don't error if no entries found, as the desired result will be met.
  --// DELETE will CASCADE
  DELETE sysdtspackagelog
  FROM sysdtspackagelog p
  WHERE ((@lineagefull IS NULL OR p.lineagefull = @lineagefull)
      AND  (@versionid IS NULL OR p.versionid = @versionid)
      AND (@id IS NULL OR p.id = @id)
      AND (@name IS NULL OR p.name = @name))
    AND ((@flags & 0x01) = 0
      OR p.logdate < 
      (
        SELECT MAX(logdate) 
        FROM sysdtspackagelog d
        WHERE (d.id = p.id)
      )
     )

  RETURN 0    -- SUCCESS
GO
GRANT  EXECUTE  ON sp_dump_dtspackagelog	TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_dump_dtspackagelog    TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_dump_dtspackagelog    TO [db_ssisoperator]
GO

/**************************************************************/
/* SP_DUMP_DTSSTEPLOG                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_dump_dtssteplog...'
GO
IF OBJECT_ID(N'sp_dump_dtssteplog') IS NOT NULL
  DROP PROCEDURE sp_dump_dtssteplog
GO
CREATE PROCEDURE sp_dump_dtssteplog
  @lineagefull    UNIQUEIDENTIFIER = NULL,   -- all steps in this package execution
  @stepexecutionid   BIGINT = NULL
AS
  SET NOCOUNT ON

  --// sysadmin only.
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  --// Don't error if no entries found, as the desired result will be met.
  --// DELETE will CASCADE
  DELETE sysdtssteplog
  WHERE (@lineagefull IS NULL OR lineagefull = @lineagefull)
    AND (@stepexecutionid IS NULL OR stepexecutionid = @stepexecutionid)

  RETURN 0    -- SUCCESS
GO
GRANT  EXECUTE  ON sp_dump_dtssteplog   TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_dump_dtssteplog   TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_dump_dtssteplog   TO [db_ssisoperator]
GO

/**************************************************************/
/* SP_DUMP_DTSTASKLOG                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_dump_dtstasklog...'
GO
IF OBJECT_ID(N'sp_dump_dtstasklog') IS NOT NULL
  DROP PROCEDURE sp_dump_dtstasklog
GO
CREATE PROCEDURE sp_dump_dtstasklog
  @stepexecutionid   BIGINT,
  @sequenceid     INT = NULL
AS
  SET NOCOUNT ON

  --// sysadmin only.
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  --// Don't error if no entries found, as the desired result will be met.
  DELETE sysdtstasklog
  WHERE (stepexecutionid IS NULL or stepexecutionid = @stepexecutionid)
    AND (@sequenceid IS NULL OR sequenceid = @sequenceid)

  RETURN 0    -- SUCCESS
GO
GRANT  EXECUTE  ON sp_dump_dtstasklog   TO [db_ssisadmin]
GRANT  EXECUTE  ON sp_dump_dtstasklog   TO [db_ssisltduser]
GRANT  EXECUTE  ON sp_dump_dtstasklog   TO [db_ssisoperator]
GO


/**************************************************************/
/* SP_DTS_SECURE                                              */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_dts_secure ...'
GO
IF OBJECT_ID(N'sp_dts_secure') IS NOT NULL
	DROP PROCEDURE sp_dts_secure
GO
CREATE PROCEDURE sp_dts_secure
	@flag	INT
AS
	SET NOCOUNT ON

	--// sysadmin only.
	IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
	BEGIN
		RAISERROR(15003, 16, 1, N'sysadmin')
		RETURN(1) -- Failure
	END
	
	IF(@flag <> 0)
	BEGIN
		REVOKE EXECUTE ON sp_get_dtsversion				FROM PUBLIC
		REVOKE EXECUTE ON sp_make_dtspackagename		FROM PUBLIC
		REVOKE EXECUTE ON sp_add_dtspackage				FROM PUBLIC
		REVOKE EXECUTE ON sp_drop_dtspackage			FROM PUBLIC
		REVOKE EXECUTE ON sp_reassign_dtspackageowner	FROM PUBLIC
		REVOKE EXECUTE ON sp_get_dtspackage				FROM PUBLIC
		REVOKE EXECUTE ON sp_enum_dtspackages			FROM PUBLIC
		REVOKE EXECUTE ON sp_log_dtspackage_begin		FROM PUBLIC
		REVOKE EXECUTE ON sp_log_dtspackage_end			FROM PUBLIC
		REVOKE EXECUTE ON sp_log_dtsstep_begin			FROM PUBLIC
		REVOKE EXECUTE ON sp_log_dtsstep_end			FROM PUBLIC
		REVOKE EXECUTE ON sp_log_dtstask				FROM PUBLIC
		REVOKE EXECUTE ON sp_enum_dtspackagelog			FROM PUBLIC
		REVOKE EXECUTE ON sp_enum_dtssteplog			FROM PUBLIC
		REVOKE EXECUTE ON sp_enum_dtstasklog			FROM PUBLIC
		REVOKE EXECUTE ON sp_dump_dtslog_all			FROM PUBLIC
		REVOKE EXECUTE ON sp_dump_dtspackagelog			FROM PUBLIC
		REVOKE EXECUTE ON sp_dump_dtssteplog			FROM PUBLIC
		REVOKE EXECUTE ON sp_dump_dtstasklog			FROM PUBLIC
	END
	ELSE
	BEGIN
		GRANT EXECUTE ON sp_get_dtsversion				TO PUBLIC
		GRANT EXECUTE ON sp_make_dtspackagename			TO PUBLIC
		GRANT EXECUTE ON sp_add_dtspackage				TO PUBLIC
		GRANT EXECUTE ON sp_drop_dtspackage				TO PUBLIC
		GRANT EXECUTE ON sp_reassign_dtspackageowner	TO PUBLIC
		GRANT EXECUTE ON sp_get_dtspackage				TO PUBLIC
		GRANT EXECUTE ON sp_enum_dtspackages			TO PUBLIC
		GRANT EXECUTE ON sp_log_dtspackage_begin		TO PUBLIC
		GRANT EXECUTE ON sp_log_dtspackage_end			TO PUBLIC
		GRANT EXECUTE ON sp_log_dtsstep_begin			TO PUBLIC
		GRANT EXECUTE ON sp_log_dtsstep_end				TO PUBLIC
		GRANT EXECUTE ON sp_log_dtstask					TO PUBLIC
		GRANT EXECUTE ON sp_enum_dtspackagelog			TO PUBLIC
		GRANT EXECUTE ON sp_enum_dtssteplog				TO PUBLIC
		GRANT EXECUTE ON sp_enum_dtstasklog				TO PUBLIC
		GRANT EXECUTE ON sp_dump_dtslog_all				TO PUBLIC
		GRANT EXECUTE ON sp_dump_dtspackagelog			TO PUBLIC
		GRANT EXECUTE ON sp_dump_dtssteplog				TO PUBLIC
		GRANT EXECUTE ON sp_dump_dtstasklog				TO PUBLIC
	END
	
	RETURN 0
GO



/**************************************************************/
/*                                                            */
/*                  D A T A B A S E    M A I L                */
/*                                                            */
/**************************************************************/

/**************************************************************/
/*                                                            */
/*  Database Mail Tables                                      */
/*                                                            */
/**************************************************************/

----------------------------------------------------------------
-- Database Mail: general configuraiton tables
----------------------------------------------------------------

IF (OBJECT_ID(N'dbo.sysmail_profile', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysmail_profile...'
   
   CREATE TABLE dbo.sysmail_profile
   (
      profile_id int identity not null,
      name sysname not null,
      description nvarchar(256) null,
      last_mod_datetime datetime not null default getdate(),
      last_mod_user sysname not null default suser_sname()

      CONSTRAINT [SYSMAIL_PROFILE_IDMustBeUnique] PRIMARY KEY(profile_id),
      CONSTRAINT [SYSMAIL_PROFILE_NameMustBeUnique] UNIQUE (name)
   )
END
ELSE
BEGIN
  ALTER TABLE dbo.sysmail_profile ALTER COLUMN description nvarchar(256) null
END
go

IF (OBJECT_ID(N'dbo.sysmail_principalprofile', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysmail_principalprofile...'
   
   CREATE TABLE dbo.sysmail_principalprofile
   (
      profile_id int not null references sysmail_profile(profile_id) on delete cascade,
      principal_sid varbinary(85) not null, -- sys.database_principals.sid
      is_default bit not null default 0,
      last_mod_datetime datetime not null default getdate(),
      last_mod_user sysname not null default suser_sname()

      CONSTRAINT [SYSMAIL_PRINCIPALPROFILE_ProfilePrincipalMustBeUnique] PRIMARY KEY(profile_id,principal_sid),
   )
END
ELSE
BEGIN
   -- add principal_sid column
   IF NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='principal_sid' and id =
        (SELECT OBJECT_ID(N'dbo.sysmail_principalprofile', 'U')))
   BEGIN
      ALTER TABLE dbo.sysmail_principalprofile ADD principal_sid varbinary(85) not null default 0xFFFF
   END
END
go

IF (OBJECT_ID(N'dbo.sysmail_account', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysmail_account...'
   
   CREATE TABLE dbo.sysmail_account
   (
      account_id int identity not null,
      name sysname not null,
      description nvarchar(256) null,
      email_address nvarchar(128) not null,
      display_name nvarchar(128) null,
      replyto_address nvarchar(128) null,      
      last_mod_datetime datetime not null default getdate(),
      last_mod_user sysname not null default suser_sname()

      CONSTRAINT [SYSMAIL_ACCOUNT_IDMustBeUnique] PRIMARY KEY(account_id),
      CONSTRAINT [SYSMAIL_ACCOUNT_NameMustBeUnique] UNIQUE (name)
   )
END
ELSE
BEGIN
  ALTER TABLE dbo.sysmail_account ALTER COLUMN description nvarchar(256) null
  ALTER TABLE dbo.sysmail_account ALTER COLUMN display_name nvarchar(128) null
  ALTER TABLE dbo.sysmail_account ALTER COLUMN replyto_address nvarchar(128) null
END
go

IF (OBJECT_ID(N'dbo.sysmail_profileaccount', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysmail_profileaccount...'

   CREATE TABLE dbo.sysmail_profileaccount
   (
      profile_id int not null,
      account_id int not null references sysmail_account(account_id) on delete cascade,
      sequence_number int null,
      last_mod_datetime datetime not null default getdate(),
      last_mod_user sysname not null default suser_sname()

      CONSTRAINT [SYSMAIL_ACCOUNT_ProfileAccountMustBeUnique] PRIMARY KEY(profile_id,account_id)
   )  
END
ELSE
BEGIN
  ALTER TABLE dbo.sysmail_profileaccount ALTER COLUMN sequence_number int null
END
go

IF (OBJECT_ID(N'dbo.sysmail_servertype', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysmail_servertype...'

   CREATE TABLE dbo.sysmail_servertype
   (
      servertype sysname not null,
      is_incoming bit not null default 0,
      is_outgoing bit not null default 1,
      last_mod_datetime datetime not null default getdate(),
      last_mod_user sysname not null default suser_sname()

      CONSTRAINT [SYSMAIL_SERVERTYPE_TypeMustBeUnique] PRIMARY KEY(servertype),
   )
END
go

IF (OBJECT_ID(N'dbo.sysmail_server', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysmail_server...'
   
   CREATE TABLE dbo.sysmail_server
   (
      account_id int not null references sysmail_account(account_id) on delete cascade,
      servertype sysname not null references sysmail_servertype(servertype),
      servername sysname not null,
      port int not null default 25,
      username nvarchar(128) null,
      credential_id int null,
      use_default_credentials bit not null default 0,
      enable_ssl bit not null default 0,
      flags int not null default 0,
      last_mod_datetime datetime not null default getdate(),
      last_mod_user sysname not null default suser_sname()

      CONSTRAINT [SYSMAIL_ACCOUNT_AccountServerTypeMustBeUnique] PRIMARY KEY(account_id,servertype)
   )
END
ELSE -- check if we need to add missing columns
BEGIN
   IF NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='use_default_credentials' and id =
        (SELECT OBJECT_ID(N'dbo.sysmail_server', 'U')))
   BEGIN
      ALTER TABLE dbo.sysmail_server ADD use_default_credentials bit not null default 0
   END

   IF NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='enable_ssl' and id =
        (SELECT OBJECT_ID(N'dbo.sysmail_server', 'U')))
   BEGIN
      ALTER TABLE dbo.sysmail_server ADD enable_ssl bit not null default 0
   END

   IF NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='flags' and id =
        (SELECT OBJECT_ID(N'dbo.sysmail_server', 'U')))
   BEGIN
      ALTER TABLE dbo.sysmail_server ADD flags int not null default 0
   END
END
go

IF (OBJECT_ID(N'dbo.sysmail_configuration', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysmail_configuration...'

   CREATE TABLE dbo.sysmail_configuration
   (
      paramname nvarchar(256) not null,
      paramvalue nvarchar(256) null,
      description nvarchar(256) null,
      last_mod_datetime datetime not null default getdate(),
      last_mod_user sysname not null default suser_sname()

      CONSTRAINT [SYSMAIL_CONFIGURATION_ParamnameMustBeUnique] PRIMARY KEY(paramname)
   )
END
ELSE
BEGIN
  ALTER TABLE dbo.sysmail_configuration ALTER COLUMN paramvalue nvarchar(256) null
  ALTER TABLE dbo.sysmail_configuration ALTER COLUMN description nvarchar(256) null
END
go

-- populate default configuration settings
DECLARE @description NVARCHAR(256)

SELECT @description = FORMATMESSAGE(14642)
IF NOT EXISTS(SELECT * FROM dbo.sysmail_configuration WHERE paramname = N'DefaultAttachmentEncoding')
  INSERT INTO dbo.sysmail_configuration (paramname,paramvalue,description) VALUES (N'DefaultAttachmentEncoding', N'MIME', @description)
ELSE
  UPDATE dbo.sysmail_configuration SET description = @description WHERE paramname = N'DefaultAttachmentEncoding'

-- maximum size of an Database Mail atachement 1MB
SELECT @description = FORMATMESSAGE(14644)
IF NOT EXISTS(SELECT * FROM dbo.sysmail_configuration WHERE paramname = N'MaxFileSize')
  INSERT INTO dbo.sysmail_configuration (paramname,paramvalue,description) VALUES (N'MaxFileSize', N'1000000', @description)
ELSE
  UPDATE dbo.sysmail_configuration SET description = @description WHERE paramname = N'MaxFileSize'

SELECT @description = FORMATMESSAGE(14645)
IF NOT EXISTS(SELECT * FROM dbo.sysmail_configuration WHERE paramname = N'ProhibitedExtensions')
  INSERT INTO dbo.sysmail_configuration (paramname,paramvalue,description) VALUES (N'ProhibitedExtensions', N'exe,dll,vbs,js', @description)
ELSE
  UPDATE dbo.sysmail_configuration SET description = @description WHERE paramname = N'ProhibitedExtensions'

SELECT @description = FORMATMESSAGE(14646)
IF NOT EXISTS(SELECT * FROM dbo.sysmail_configuration WHERE paramname = N'AccountRetryAttempts')
  INSERT INTO dbo.sysmail_configuration (paramname,paramvalue,description) VALUES (N'AccountRetryAttempts', N'1', @description)
ELSE
  UPDATE dbo.sysmail_configuration SET description = @description WHERE paramname = N'AccountRetryAttempts'

SELECT @description = FORMATMESSAGE(14647)
IF NOT EXISTS(SELECT * FROM dbo.sysmail_configuration WHERE paramname = N'AccountRetryDelay')
  INSERT INTO dbo.sysmail_configuration (paramname,paramvalue,description) VALUES (N'AccountRetryDelay', N'60', @description)
ELSE
  UPDATE dbo.sysmail_configuration SET description = @description WHERE paramname = N'AccountRetryDelay'

SELECT @description = FORMATMESSAGE(14648)
IF NOT EXISTS(SELECT * FROM dbo.sysmail_configuration WHERE paramname = N'DatabaseMailExeMinimumLifeTime')
  INSERT INTO dbo.sysmail_configuration (paramname,paramvalue,description) VALUES (N'DatabaseMailExeMinimumLifeTime', N'600', @description)
ELSE
  UPDATE dbo.sysmail_configuration SET description = @description WHERE paramname = N'DatabaseMailExeMinimumLifeTime'

-- component logging level: normal - 1, extended - 2 (default), verbose - 3
SELECT @description = FORMATMESSAGE(14664)
IF NOT EXISTS(SELECT * FROM dbo.sysmail_configuration WHERE paramname = N'LoggingLevel')
  INSERT INTO dbo.sysmail_configuration (paramname,paramvalue,description) VALUES (N'LoggingLevel', N'2', @description)
ELSE
  UPDATE dbo.sysmail_configuration SET description = @description WHERE paramname = N'LoggingLevel'

go

----------------------------------------------------------------
-- Database Mail: mail host database specific tables
----------------------------------------------------------------

-----------------------------------------------------------
-- TABLE sysmail_mailitems      
-----------------------------------------------------------
-- sysmail_mailitems      : Contains one row for each mail sent using sp_send_dbmail.
--                          Contains mails that are waiting to be sent and the sent mail.
--                          sent_status determines its status
--
-- mailitem_id            : Id for the row. Auto-generated.
-- profile_id             : ID of profile to use to send the mail.
-- recipients             : People on the To list.
-- copy_recipients        : People on the Cc list.
-- blind_copy_recipients  : People on the Bcc list.
-- subject                : Subject of the email.
-- body                   : Body of the email.
-- body_format         : Body format. 0 (Text), 1(Html)                        
-- importance             : Importance of the email:
--                          0(Low), 1(Normal), 2(High).
-- sensitivity            : Sensitivity of the email:
--                          0(Normal), 1(Personal), 2(Private), 3(Confidential).
-- attachment_encoding    : Encoding to use for mail and attachments: 
--                          0(MIME), 1(UUEncode), 2(BINHEX), 3(S/MIME).
-- query                  : SQL query that was executed in this mail 
-- execute_query_database : The database to execute the query in  
-- attach_query_result_as_file : Option for attaching the query result 
--                               as a file instead of in the mail body
-- query_result_header    : Option for including query result column headers
-- query_result_width     : The query result overall width in characters
-- query_result_separator : The query result column separaror character
-- exclude_query_output   : Option for supressing query output being returned to
--                          the client that is sending the mail
-- append_query_error     : Option for appending query error messages to the mail item
-- send_request_date   : Date this mail item was created
-- send_request_user      : The user that created this mail item
-- sent_account_id        : The account_id that was used to send this mail item 
-- sent_status            : The current status of the mail item. 
--                        : 0(PendingSend), 1(SendSuccessful), 2(SendFailed), 3(AttemptingSendRetry)
-- sent_date              : Date the mail item was sent or failed to be sent
-- from_adress            : Optional override of the from header.
-- reply_to               : Optional setting of the reply-to header.
-----------------------------------------------------------
IF(OBJECT_ID('dbo.sysmail_mailitems', 'U') IS NULL)
BEGIN
  PRINT 'Creating TABLE sysmail_mailitems'

    CREATE TABLE sysmail_mailitems
    (
       mailitem_id                 INT                     IDENTITY(1,1) NOT NULL,
       profile_id                  INT                     NOT NULL,        
       recipients                  VARCHAR(MAX)    NULL, 
       copy_recipients             VARCHAR(MAX)    NULL,
       blind_copy_recipients       VARCHAR(MAX)    NULL,
       subject                     NVARCHAR(255)      NULL,
       from_address                VARCHAR(MAX)    NULL,
       reply_to                    VARCHAR(MAX)    NULL,
       body                        NVARCHAR(MAX)      NULL, 
       body_format                 VARCHAR(20)             NULL, 
       importance                  VARCHAR(6)              NULL,
       sensitivity                 VARCHAR(12)             NULL,
       file_attachments            NVARCHAR(MAX)      NULL,  
       attachment_encoding         VARCHAR(20)             NULL,
       query                       NVARCHAR(MAX)      NULL,
       execute_query_database      sysname                 NULL,  
       attach_query_result_as_file BIT                     NULL,
       query_result_header         BIT        NULL,
       query_result_width          INT                     NULL,  
       query_result_separator      CHAR(1)         NULL,
       exclude_query_output        BIT       NULL,
       append_query_error          BIT       NULL,
       send_request_date           DATETIME     NOT NULL DEFAULT GETDATE(),
       send_request_user           sysname              NOT NULL DEFAULT SUSER_SNAME(),
       sent_account_id             INT       NULL,
       sent_status                 TINYINT         NULL     DEFAULT 0,
       sent_date                   DATETIME     NULL,
       last_mod_date               DATETIME     NOT NULL DEFAULT GETDATE(),
       last_mod_user               sysname              NOT NULL DEFAULT SUSER_SNAME(),

       CONSTRAINT [sysmail_mailitems_id_MustBeUnique] PRIMARY KEY(mailitem_id),
       CONSTRAINT [sysmail_OutMailMustHaveAtleastOneRecipient]
                            CHECK (NOT (recipients IS NULL AND copy_recipients IS NULL AND blind_copy_recipients IS NULL)),
       CONSTRAINT [sysmail_OutMailRecipientCannotBeEmpty]           
                            CHECK (DATALENGTH(ISNULL(recipients, ''))      + 
            DATALENGTH(ISNULL(copy_recipients, '')) +  
            DATALENGTH(ISNULL(blind_copy_recipients, '')) <> 0),
       CONSTRAINT [sysmail_OutMailAttachmentEncodingMustBeValid]
             CHECK (attachment_encoding IN ('MIME', 'S/MIME', 'BINHEX', 'UUENCODE')),       
       CONSTRAINT [sysmail_OutMailImportanceMustBeValid]
             CHECK (importance IN ('LOW', 'NORMAL', 'HIGH')),
       CONSTRAINT [sysmail_OutMailSensitivityMustBeValid]           
             CHECK (sensitivity IN('NORMAL', 'PERSONAL', 'PRIVATE', 'CONFIDENTIAL'))
    )
END
ELSE
BEGIN
    -- handle schema upgrade for sysmail_mailitems table
    IF NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='profile_id' and id =
        (SELECT OBJECT_ID(N'dbo.sysmail_mailitems', 'U')))
    BEGIN
       ALTER TABLE dbo.sysmail_mailitems ADD profile_id INT NOT NULL DEFAULT -1
    END

    IF NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='from_address' and id =
        (SELECT OBJECT_ID(N'dbo.sysmail_mailitems', 'U')))
    BEGIN
       ALTER TABLE dbo.sysmail_mailitems ADD from_address VARCHAR(MAX) NULL
       ALTER TABLE dbo.sysmail_mailitems ADD reply_to VARCHAR(MAX) NULL
    END
END
GO

/**************************************************************/
/* sysmail_allitems                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating view sysmail_allitems...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysmail_allitems')
              AND (type = 'V')))
  DROP VIEW sysmail_allitems
go

CREATE VIEW sysmail_allitems
AS
SELECT mailitem_id,
       profile_id,
       recipients,
       copy_recipients,
       blind_copy_recipients,
       subject,
       body,
       body_format,
       importance,
       sensitivity,
       file_attachments,
       attachment_encoding,
       query,
       execute_query_database,
       attach_query_result_as_file,
       query_result_header,
       query_result_width,
       query_result_separator,
       exclude_query_output,
       append_query_error,
       send_request_date,
       send_request_user,
       sent_account_id,
       CASE sent_status 
          WHEN 0 THEN 'unsent' 
          WHEN 1 THEN 'sent' 
          WHEN 3 THEN 'retrying' 
          ELSE 'failed' 
       END as sent_status,
       sent_date,
       last_mod_date,
       last_mod_user
FROM msdb.dbo.sysmail_mailitems
WHERE (send_request_user = SUSER_SNAME()) OR (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)

GO

/**************************************************************/
/* sysmail_sentitems                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating view sysmail_sentitems...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysmail_sentitems')
              AND (type = 'V')))
  DROP VIEW sysmail_sentitems
go

CREATE VIEW sysmail_sentitems
AS
SELECT * FROM msdb.dbo.sysmail_allitems WHERE sent_status = 'sent'

go

/**************************************************************/
/* sysmail_unsentitems                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating view sysmail_unsentitems...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysmail_unsentitems')
              AND (type = 'V')))
  DROP VIEW sysmail_unsentitems
go

CREATE VIEW sysmail_unsentitems
AS
SELECT * FROM msdb.dbo.sysmail_allitems WHERE (sent_status = 'unsent' OR sent_status = 'retrying')

go

/**************************************************************/
/* sysmail_faileditems                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating view sysmail_faileditems...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysmail_faileditems')
              AND (type = 'V')))
  DROP VIEW sysmail_faileditems
go

CREATE VIEW sysmail_faileditems
AS
SELECT * FROM msdb.dbo.sysmail_allitems WHERE sent_status = 'failed'

go

-----------------------------------------------------------
-- procedure sysmail_delete_mailitems_sp
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sysmail_delete_mailitems_sp', 'P') IS NULL
    DROP PROCEDURE dbo.sysmail_delete_mailitems_sp
GO
-----
PRINT 'Creating sysmail_delete_mailitems_sp'
-----
GO
CREATE PROCEDURE sysmail_delete_mailitems_sp
   @sent_before DATETIME   = NULL, -- sent before
   @sent_status varchar(8)   = NULL -- sent status
AS
BEGIN

   SET @sent_status       = LTRIM(RTRIM(@sent_status))
   IF @sent_status           = '' SET @sent_status = NULL

   IF ( (@sent_status IS NOT NULL) AND
         (LOWER(@sent_status collate SQL_Latin1_General_CP1_CS_AS) NOT IN ( 'unsent', 'sent', 'failed', 'retrying') ) )
   BEGIN
      RAISERROR(14266, -1, -1, '@sent_status', 'unsent, sent, failed, retrying')
      RETURN(1) -- Failure
   END

   IF ( @sent_before IS NULL AND @sent_status IS NULL )
   BEGIN
      RAISERROR(14608, -1, -1, '@sent_before', '@sent_status')  
      RETURN(1) -- Failure
   END

   DELETE FROM msdb.dbo.sysmail_allitems 
   WHERE 
        ((@sent_before IS NULL) OR ( send_request_date < @sent_before))
   AND ((@sent_status IS NULL) OR (sent_status = @sent_status))

   DECLARE @localmessage nvarchar(255)
    SET @localmessage = FORMATMESSAGE(14665, SUSER_SNAME(), @@ROWCOUNT)
    exec msdb.dbo.sysmail_logmailevent_sp @event_type=1, @description=@localmessage

END
GO

-----------------------------------------------------------
-- TABLE sysmail_attachments
-----------------------------------------------------------
-- sysmail_attachments  : Contains mail item attachments
--
-- attachment_id        : Id for the row. Auto-generated
-- mailitem_id          : Optional key to the mail items that this entry is a about
-- filename             : The filename of the attachment
-- filesize             : Size of the file
-- encoded_attachment   : The file data encoded in base64
----------------------------------------------------------------
IF (OBJECT_ID('dbo.sysmail_attachments', 'U') IS NULL)
BEGIN
  PRINT 'Creating TABLE sysmail_attachments'

    CREATE TABLE sysmail_attachments
    (
        attachment_id       INT   IDENTITY(1, 1) NOT NULL,
        mailitem_id      INT  NOT NULL CONSTRAINT 
         FK_sysmail_mailitems_mailitem_id 
                    FOREIGN KEY (mailitem_id) REFERENCES sysmail_mailitems(mailitem_id)  ON DELETE CASCADE,
        filename            NVARCHAR(260)       NOT NULL,
        filesize            INT                 NOT NULL,
        attachment          VARBINARY(MAX)      NULL,
        last_mod_date       DATETIME            NOT NULL DEFAULT GETDATE(),
        last_mod_user       sysname             NOT NULL DEFAULT SUSER_SNAME()
    )
END
ELSE
BEGIN
   BEGIN TRAN

   -- remove any old constraint
   declare @fkName sysname
   DECLARE @sql NVARCHAR(4000)
   SELECT @fkName = cstr.name
      FROM
         sys.tables AS tbl
      INNER JOIN sys.foreign_keys AS cstr ON cstr.parent_object_id=tbl.object_id
      LEFT OUTER JOIN sys.indexes AS ki ON ki.index_id = cstr.key_index_id and ki.object_id = cstr.referenced_object_id
      INNER JOIN sys.tables rtbl ON rtbl.object_id = cstr.referenced_object_id
      WHERE
      tbl.name=N'sysmail_attachments' 

   IF (@fkName IS NOT NULL)
   BEGIN
      select @sql = N'ALTER TABLE sysmail_attachments DROP CONSTRAINT ' + @fkName
      EXEC (@sql)
   END
   
   ALTER TABLE sysmail_attachments ADD CONSTRAINT FK_sysmail_mailitems_mailitem_id 
                    FOREIGN KEY (mailitem_id) REFERENCES sysmail_mailitems(mailitem_id) ON DELETE CASCADE
   COMMIT
END
GO


/**************************************************************/
/* sysmail_mailattachments                                */
/**************************************************************/

PRINT ''
PRINT 'Creating view sysmail_mailattachments...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysmail_mailattachments')
              AND (type = 'V')))
  DROP VIEW sysmail_mailattachments
go

CREATE VIEW sysmail_mailattachments
AS
SELECT attachment_id,
       sa.mailitem_id,
       filename,
       filesize,
       attachment,
       sa.last_mod_date,
       sa.last_mod_user
  FROM msdb.dbo.sysmail_attachments sa
  JOIN msdb.dbo.sysmail_mailitems sm ON sa.mailitem_id = sm.mailitem_id
  WHERE (sm.send_request_user = SUSER_SNAME()) OR (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
GO

-----------------------------------------------------------
-- TABLE sysmail_send_retries
-----------------------------------------------------------
-- sysmail_send_retries : Contains send mail retry history
--
-- conversation_handle  : The conversation handle that initiated the retry
-- mailitem_id          : Optional key to the mail items that this entry is a about
-- send_attempts        : The current number of send attempts
-- last_send_attempt_date : date of the last send attempt
----------------------------------------------------------------
IF (OBJECT_ID('dbo.sysmail_send_retries', 'U') IS NULL)
BEGIN
    PRINT 'Creating TABLE sysmail_send_retries'
    CREATE TABLE sysmail_send_retries
    (
        conversation_handle     uniqueidentifier PRIMARY KEY NOT NULL,
        mailitem_id             INT              NOT NULL CONSTRAINT FK_mailitems_mailitem_id 
                    FOREIGN KEY (mailitem_id) REFERENCES sysmail_mailitems(mailitem_id) ON DELETE CASCADE,
        send_attempts           INT              NOT NULL DEFAULT 1,
        last_send_attempt_date  DATETIME         NOT NULL DEFAULT GETDATE()
    )
END
ELSE
BEGIN
   BEGIN TRAN

   -- remove any old constraint
   declare @fkName sysname
   DECLARE @sql NVARCHAR(4000)
   SELECT @fkName = cstr.name
      FROM
         sys.tables AS tbl
      INNER JOIN sys.foreign_keys AS cstr ON cstr.parent_object_id=tbl.object_id
      LEFT OUTER JOIN sys.indexes AS ki ON ki.index_id = cstr.key_index_id and ki.object_id = cstr.referenced_object_id
      INNER JOIN sys.tables rtbl ON rtbl.object_id = cstr.referenced_object_id
      WHERE
      tbl.name=N'sysmail_send_retries' 

   IF (@fkName IS NOT NULL)
   BEGIN
      SET @sql = N'ALTER TABLE sysmail_send_retries DROP CONSTRAINT ' + @fkName
      EXECUTE (@sql)
   END

   ALTER TABLE sysmail_send_retries ADD CONSTRAINT FK_mailitems_mailitem_id 
                    FOREIGN KEY (mailitem_id) REFERENCES sysmail_mailitems(mailitem_id) ON DELETE CASCADE

   COMMIT
END
GO

-----------------------------------------------------------
-- TABLE sysmail_log
-----------------------------------------------------------
-- sysmail_log      : Contains error and event logging 
--
-- log_id           : Id for the row. Auto-generated.
-- event_type       : The event type for this record
--                    0(Success), 1(information), 2(Warning), 3(error)

-- log_date         : Create date of this log entry
-- description      : The text description of this entry
-- process_id       : The DatabaseMail (exe) process id that added this entry 
-- mailitem_id      : Optional key to the mail items that this entry is a about
-- account_id       : Optional account_id hat this entry is a about
----------------------------------------------------------------
IF (OBJECT_ID('dbo.sysmail_log', 'U') IS NULL)
BEGIN
  PRINT 'Creating TABLE sysmail_log'

    CREATE TABLE sysmail_log
    (
        log_id         INT             IDENTITY(1, 1) NOT NULL,
        event_type      INT             NOT NULL,
        log_date        DATETIME        NOT NULL    DEFAULT GETDATE(),
        description     NVARCHAR(max)   NULL,
        process_id      INT             NULL,
        mailitem_id     INT             NULL,
        account_id      INT             NULL,
        last_mod_date   DATETIME        NOT NULL DEFAULT GETDATE(),
        last_mod_user   sysname     NOT NULL DEFAULT SUSER_SNAME(),

        CONSTRAINT [sysmail_log_id_MustBeUnique] PRIMARY KEY(log_id),
    )
END
ELSE
BEGIN
   -- remove any old constraint
   declare @fkName sysname
   DECLARE @sql NVARCHAR(4000)
   SELECT @fkName = cstr.name
      FROM
         sys.tables AS tbl
      INNER JOIN sys.foreign_keys AS cstr ON cstr.parent_object_id=tbl.object_id
      LEFT OUTER JOIN sys.indexes AS ki ON ki.index_id = cstr.key_index_id and ki.object_id = cstr.referenced_object_id
      INNER JOIN sys.tables rtbl ON rtbl.object_id = cstr.referenced_object_id
      WHERE
      tbl.name=N'sysmail_log' 

   IF (@fkName IS NOT NULL)
   begin
      select @sql = N'ALTER TABLE sysmail_log DROP CONSTRAINT ' + @fkName
      EXEC (@sql)
   end
END
GO

/**************************************************************/
/* sysmail_event_log                                         */
/**************************************************************/
use msdb
go

PRINT ''
PRINT 'Creating view sysmail_event_log...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysmail_event_log')
              AND (type = 'V')))
  DROP VIEW sysmail_event_log
go

CREATE VIEW sysmail_event_log
AS
SELECT log_id,
       CASE event_type 
          WHEN 0 THEN 'success' 
          WHEN 1 THEN 'information' 
          WHEN 2 THEN 'warning' 
          ELSE 'error' 
       END as event_type,
       log_date,
       description,
       process_id,
       sl.mailitem_id,
       account_id,
       sl.last_mod_date,
       sl.last_mod_user
FROM [dbo].[sysmail_log]  sl
WHERE (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) OR 
      (EXISTS ( SELECT mailitem_id FROM [dbo].[sysmail_allitems] ai WHERE sl.mailitem_id = ai.mailitem_id ))

GO

-----------------------------------------------------------
-- procedure sysmail_delete_log_sp
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sysmail_delete_log_sp', 'P') IS NULL
    DROP PROCEDURE dbo.sysmail_delete_log_sp
GO
-----
PRINT 'Creating sysmail_delete_log_sp'
-----
GO
CREATE PROCEDURE sysmail_delete_log_sp
   @logged_before DATETIME   = NULL, 
   @event_type varchar(15)   = NULL
AS
BEGIN

   SET @event_type       = LTRIM(RTRIM(@event_type))
   IF @event_type        = '' SET @event_type = NULL
   DECLARE @event_type_numeric INT

   IF ( (@event_type IS NOT NULL) AND
         (LOWER(@event_type collate SQL_Latin1_General_CP1_CS_AS) NOT IN ( 'success', 'warning', 'error', 'information' ) ) )
   BEGIN
        RAISERROR(14266, -1, -1, '@event_type', 'success, warning, error, information')
      RETURN(1) -- Failure
   END   
   
   IF ( @event_type IS NOT NULL)
   BEGIN
      SET @event_type_numeric = ( SELECT CASE 
                           WHEN @event_type = 'success' THEN 0
                           WHEN @event_type = 'information' THEN 1
                           WHEN @event_type = 'warning' THEN 2
                           ELSE 3 END 
                        )
   END
   ELSE
      SET @event_type_numeric = NULL

   DELETE FROM msdb.dbo.sysmail_log 
   WHERE 
        ((@logged_before IS NULL) OR ( log_date < @logged_before))
   AND ((@event_type_numeric IS NULL) OR (@event_type_numeric = event_type))
END
GO

-----------------------------------------------------------
-- sysmail_query_transfer : Table used to transfer data between a helper xp's and the calling sp's.
--                   Rows are created and deleted in the context of each call
--
-- uid              : guid for the row. Generated by the user  
-- text_data        : Attachment data in binary form
----------------------------------------------------------------
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysmail_query_transfer')
                  AND (type = 'U')))
BEGIN
PRINT 'Creating TABLE sysmail_query_transfer'
CREATE TABLE sysmail_query_transfer
(
    uid             uniqueidentifier    NOT NULL PRIMARY KEY,
    text_data       NVARCHAR(max)       NULL,
    create_date     DATETIME            NOT NULL DEFAULT GETDATE()
)
END
GO

-----------------------------------------------------------
-- sysmail_attachments_transfer : Table used to transfer data between a helper xp's
--                        and the calling sp's. Rows are created and deleted 
--                        in the context of each call
--
-- uid              : guid for the row. Generated by the user  
-- filename         : Attachment file name
-- filesize         : Attachment file size in bytes
-- attachment       : Attachment data in binary form
----------------------------------------------------------------
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysmail_attachments_transfer')
                  AND (type = 'U')))
BEGIN
PRINT 'Creating TABLE sysmail_attachments_transfer'
CREATE TABLE sysmail_attachments_transfer
(
    transfer_id       INT                 IDENTITY(1, 1) NOT NULL PRIMARY KEY,
    uid         uniqueidentifier    NOT NULL,
    filename        NVARCHAR(260)       NOT NULL,
    filesize        INT                 NOT NULL,
    attachment      VARBINARY(MAX)      NULL,
    create_date     DATETIME            NOT NULL DEFAULT GETDATE()
)
END
GO

/*************************************************************************/
/*                                                                       */
/*  Database Mail Triggers                                               */
/*                                                                       */
/*************************************************************************/

------------------------------------------------------------
-- Database Mail: triggers on general configuration tables
------------------------------------------------------------
PRINT ''
PRINT 'Creating trigger trig_sysmail_profile...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_sysmail_profile')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_sysmail_profile
go

CREATE TRIGGER trig_sysmail_profile
ON msdb.dbo.sysmail_profile
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_profile'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_profile 
      SET last_mod_datetime = getdate(),last_mod_user = suser_sname() 
      FROM sysmail_profile p, inserted i
      WHERE p.profile_id = i.profile_id
   END
END
go

PRINT ''
PRINT 'Creating trigger trig_principalprofile...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_principalprofile')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_principalprofile
go

CREATE TRIGGER trig_principalprofile
ON msdb.dbo.sysmail_principalprofile
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_principalprofile'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_principalprofile 
      SET last_mod_datetime = getdate(),last_mod_user = suser_sname() 
      FROM sysmail_principalprofile p, inserted i
      WHERE p.profile_id = i.profile_id and p.principal_sid = i.principal_sid
   END
END
go

PRINT ''
PRINT 'Creating trigger trig_sysmail_account...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_sysmail_account')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_sysmail_account
go

CREATE TRIGGER trig_sysmail_account
ON msdb.dbo.sysmail_account
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_account'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_account 
      SET last_mod_datetime = getdate(),last_mod_user = suser_sname() 
      FROM sysmail_account a, inserted i
      WHERE a.account_id = i.account_id
   END
END
go

PRINT ''
PRINT 'Creating trigger trig_sysmail_profileaccount...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_sysmail_profileaccount')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_sysmail_profileaccount
go

CREATE TRIGGER trig_sysmail_profileaccount
ON msdb.dbo.sysmail_profileaccount
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_profileaccount'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_profileaccount 
      SET last_mod_datetime = getdate(),last_mod_user = suser_sname() 
      FROM sysmail_profileaccount p, inserted i
      WHERE p.profile_id = i.profile_id and p.account_id = i.account_id
   END
END
go

PRINT ''
PRINT 'Creating trigger trig_sysmail_profile_delete...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_sysmail_profile_delete')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_sysmail_profile_delete
go

CREATE TRIGGER trig_sysmail_profile_delete
ON msdb.dbo.sysmail_profile
FOR DELETE
AS
BEGIN
   DELETE FROM msdb.dbo.sysmail_profileaccount
   WHERE profile_id IN (SELECT profile_id FROM deleted)
END
go

PRINT ''
PRINT 'Creating trigger trig_sysmail_servertype...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_sysmail_servertype')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_sysmail_servertype
go

CREATE TRIGGER trig_sysmail_servertype
ON msdb.dbo.sysmail_servertype
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_servertype'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_servertype 
      SET last_mod_datetime = getdate(),last_mod_user = suser_sname() 
      FROM sysmail_servertype s, inserted i
      where s.servertype = i.servertype
   END
END
go

SET NOCOUNT ON
IF NOT EXISTS(SELECT * FROM dbo.sysmail_servertype WHERE servertype = N'SMTP')
BEGIN
    INSERT INTO dbo.sysmail_servertype (servertype) VALUES (N'SMTP')
END
SET NOCOUNT OFF
go

PRINT ''
PRINT 'Creating trigger trig_sysmail_server...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_sysmail_server')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_sysmail_server
go

CREATE TRIGGER trig_sysmail_server
ON msdb.dbo.sysmail_server
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_server'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_server 
      SET last_mod_datetime = getdate(),last_mod_user = suser_sname() 
      FROM sysmail_server s, inserted i
      WHERE s.account_id = i.account_id and s.servertype = i.servertype
   END
END
go

PRINT ''
PRINT 'Creating trigger trig_sysmail_configuration...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_sysmail_configuration')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_sysmail_configuration
go

CREATE TRIGGER trig_sysmail_configuration
ON msdb.dbo.sysmail_configuration
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_configuration'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_configuration 
      SET last_mod_datetime = getdate(),last_mod_user = suser_sname() 
      FROM sysmail_configuration c, inserted i
      WHERE c.paramname = i.paramname
   END
END
go

-------------------------------------------------------------------------
-- Database Mail: triggers on general mail host database specific tables
-------------------------------------------------------------------------
IF (OBJECT_ID('dbo.trig_sysmail_mailitems', 'TR') IS NOT NULL)
    DROP TRIGGER dbo.trig_sysmail_mailitems
GO

CREATE TRIGGER trig_sysmail_mailitems
ON msdb.dbo.sysmail_mailitems
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_mailitems'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_mailitems 
      SET last_mod_date = GETDATE(), last_mod_user = SUSER_SNAME() 
      FROM sysmail_mailitems m, inserted i
      WHERE m.mailitem_id = i.mailitem_id
   END
END
GO

IF (OBJECT_ID('dbo.trig_sysmail_attachments', 'TR') IS NOT NULL)
    DROP TRIGGER dbo.trig_sysmail_attachments
GO

CREATE TRIGGER trig_sysmail_attachments
ON msdb.dbo.sysmail_attachments
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_attachments'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_attachments 
      SET last_mod_date = GETDATE(), last_mod_user = SUSER_SNAME() 
      FROM sysmail_attachments a, inserted i
      WHERE a.attachment_id = i.attachment_id
   END
END
GO

IF (OBJECT_ID('dbo.trig_sysmail_log', 'TR') IS NOT NULL)
    DROP TRIGGER dbo.trig_sysmail_log
GO

CREATE TRIGGER trig_sysmail_log
ON msdb.dbo.sysmail_log
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_log'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_log 
      SET last_mod_date = GETDATE(), last_mod_user = SUSER_SNAME() 
      FROM sysmail_log l, inserted i
      WHERE l.log_id = i.log_id
   END
END
GO

/*********************************************************************************/
/*                                                                               */
/*  Database Mail Utility Functions                                              */
/*                                                                               */
/*********************************************************************************/
-----------------------------------------------------------
-- ConvertToInt : Converts a string to integer. Returns null
--                if the input string is not a valid int.
--
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.ConvertToInt', 'FN') IS NULL
    DROP FUNCTION dbo.ConvertToInt
GO

CREATE FUNCTION dbo.ConvertToInt(@string nvarchar(255), @maxValue int, @defValue int) RETURNS int
AS
BEGIN
    DECLARE @value bigint   
    SET @value = @defValue 
    SET @string = LTRIM(RTRIM(@string))

    -- Check if there is any character other than 0-9 in the string.
    IF ((@string IS NOT NULL AND @string <> N'') AND (@string NOT LIKE '%[^0-9]%'))
    BEGIN
        --INT's have a max of 10 digits
        IF(LEN(@string) <= 10)
        BEGIN
        -- Try converting to bigint. Return default if the value is bigger than @maxValue
        SET @value = CONVERT(bigint, @string)
        IF(@value > CONVERT(bigint, @maxValue))
            SET @value = @defValue
        END
    END

    RETURN CONVERT(int, @value)
END
GO

/*********************************************************************************/
/*                                                                               */
/*  Database Mail Stored Procedures                                              */
/*                                                                               */
/*********************************************************************************/

-------------------------------------------------------
-- Database Mail: configuration stored procedures
-------------------------------------------------------
PRINT ''
PRINT 'Creating procedure sysmail_verify_accountparams_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_verify_accountparams_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_verify_accountparams_sp
go

CREATE PROCEDURE dbo.sysmail_verify_accountparams_sp
   @use_default_credentials bit,
   @mailserver_type sysname      OUTPUT,  -- @mailserver_type must be provided. Usually SMTP
   @username      nvarchar(128)  OUTPUT, -- returns trimmed value, NULL if empty
   @password      nvarchar(128)  OUTPUT  -- returns trimmed value,  NULL if empty
AS
   SET @username = LTRIM(RTRIM(@username))
   SET @password = LTRIM(RTRIM(@password))
   SET @mailserver_type = LTRIM(RTRIM(@mailserver_type))

    IF(@username = N'')         SET @username = NULL
    IF(@password = N'')         SET @password = NULL
    IF(@mailserver_type = N'')  SET @mailserver_type = NULL

   IF(@mailserver_type IS NULL)
   BEGIN
      RAISERROR(14614, -1, -1, @mailserver_type)   
      RETURN (1)  
   END

   -- default credentials should supercede any explicit credentials passed in
   IF((@use_default_credentials = 1) AND (@username IS NOT NULL))
   BEGIN
      RAISERROR(14666, -1, -1)   
      RETURN (1)
   END  

   --If a password is specified then @username must be a non empty string
   IF((@password IS NOT NULL) AND (@username IS NULL))
   BEGIN
      RAISERROR(14615, -1, -1)   
      RETURN (1)
   END  

   RETURN(0) -- SUCCESS
go

PRINT ''
PRINT 'Creating procedure sysmail_verify_principal_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_verify_principal_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_verify_principal_sp
go

CREATE PROCEDURE dbo.sysmail_verify_principal_sp
   @principal_id int,
   @principal_name sysname,
   @allow_both_nulls bit,
   @principal_sid varbinary(85) OUTPUT
AS
   IF @allow_both_nulls = 0
   BEGIN
      -- at least one parameter must be supplied
      IF (@principal_id IS NULL AND @principal_name IS NULL)
      BEGIN
         RAISERROR(14604, -1, -1, 'principal')  
         RETURN(1)
      END
   END

   DECLARE @principalid int

   IF (@principal_id IS NOT NULL AND @principal_name IS NOT NULL) -- both parameters supplied
   BEGIN
     SELECT @principalid=principal_id FROM msdb.sys.database_principals 
            WHERE type in ('U','S','G') AND principal_id = @principal_id AND name = @principal_name

      IF (@principalid IS NULL)
      BEGIN
         RAISERROR(14605, -1, -1, 'principal')  
         RETURN(2)
      END
   END
   ELSE IF (@principal_id IS NOT NULL) -- use id
   BEGIN
     SELECT @principalid=principal_id FROM msdb.sys.database_principals 
            WHERE type in ('U','S','G') AND principal_id = @principal_id

      IF (@principalid IS NULL)
      BEGIN
         RAISERROR(14606, -1, -1, 'principal')
         RETURN(3)
      END      
   END
   ELSE IF (@principal_name IS NOT NULL)  -- use name
   BEGIN
     SELECT @principalid=principal_id FROM msdb.sys.database_principals 
            WHERE type in ('U','S','G') AND name = @principal_name

      IF (@principalid IS NULL)
      BEGIN
         RAISERROR(14607, -1, -1, 'principal')
         RETURN(4)
      END      
   END

   -- populate return variable
   SELECT @principal_sid = dbo.get_principal_sid(@principalid)

   RETURN(0) -- SUCCESS
go

PRINT ''
PRINT 'Creating procedure sysmail_verify_profile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_verify_profile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_verify_profile_sp
go

CREATE PROCEDURE dbo.sysmail_verify_profile_sp
   @profile_id int,
   @profile_name sysname,
   @allow_both_nulls bit,
   @allow_id_name_mismatch bit,
   @profileid int OUTPUT
AS
   IF @allow_both_nulls = 0
   BEGIN
      -- at least one parameter must be supplied
      IF (@profile_id IS NULL AND @profile_name IS NULL)
      BEGIN
         RAISERROR(14604, -1, -1, 'profile') 
         RETURN(1)
      END
   END
   
   IF ((@allow_id_name_mismatch = 0) AND (@profile_id IS NOT NULL AND @profile_name IS NOT NULL)) -- use both parameters
   BEGIN
      SELECT @profileid = profile_id FROM msdb.dbo.sysmail_profile WHERE profile_id=@profile_id AND name=@profile_name
      IF (@profileid IS NULL) -- id and name do not match
      BEGIN
         RAISERROR(14605, -1, -1, 'profile')
         RETURN(2)
      END      
   END
   ELSE IF (@profile_id IS NOT NULL) -- use id
   BEGIN
      SELECT @profileid = profile_id FROM msdb.dbo.sysmail_profile WHERE profile_id=@profile_id
      IF (@profileid IS NULL) -- id is invalid
      BEGIN
         RAISERROR(14606, -1, -1, 'profile')
         RETURN(3)
      END      
   END
   ELSE IF (@profile_name IS NOT NULL) -- use name
   BEGIN
      SELECT @profileid = profile_id FROM msdb.dbo.sysmail_profile WHERE name=@profile_name
      IF (@profileid IS NULL) -- name is invalid
      BEGIN
         RAISERROR(14607, -1, -1, 'profile')
         RETURN(4)
      END      
   END
   RETURN(0) -- SUCCESS
go

PRINT ''
PRINT 'Creating procedure sysmail_verify_account_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_verify_account_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_verify_account_sp
go

CREATE PROCEDURE dbo.sysmail_verify_account_sp
   @account_id int,
   @account_name sysname,
   @allow_both_nulls bit,
   @allow_id_name_mismatch bit,
   @accountid int OUTPUT
AS
   IF @allow_both_nulls = 0
   BEGIN
      -- at least one parameter must be supplied
      IF (@account_id IS NULL AND @account_name IS NULL)
      BEGIN
         RAISERROR(14604, -1, -1, 'account') 
         RETURN(1)
      END
   END
   
   IF ((@allow_id_name_mismatch = 0) AND (@account_id IS NOT NULL AND @account_name IS NOT NULL)) -- use both parameters
   BEGIN
      SELECT @accountid = account_id FROM msdb.dbo.sysmail_account WHERE account_id=@account_id AND name=@account_name
      IF (@accountid IS NULL) -- id and name do not match
      BEGIN
         RAISERROR(14605, -1, -1, 'account')
         RETURN(2)
      END      
   END
   ELSE IF (@account_id IS NOT NULL) -- use id
   BEGIN
      SELECT @accountid = account_id FROM msdb.dbo.sysmail_account WHERE account_id=@account_id
      IF (@accountid IS NULL) -- id is invalid
      BEGIN
         RAISERROR(14606, -1, -1, 'account')
         RETURN(3)
      END      
   END
   ELSE IF (@account_name IS NOT NULL) -- use name
   BEGIN
      SELECT @accountid = account_id FROM msdb.dbo.sysmail_account WHERE name=@account_name
      IF (@accountid IS NULL) -- name is invalid
      BEGIN
         RAISERROR(14607, -1, -1, 'account')
         RETURN(4)
      END      
   END
   RETURN(0) -- SUCCESS
go

PRINT ''
PRINT 'Creating procedure sysmail_add_profile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_add_profile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_add_profile_sp
go

CREATE PROCEDURE dbo.sysmail_add_profile_sp
   @profile_name sysname,
   @description nvarchar(256) = NULL,
   @profile_id int = NULL OUTPUT 
AS
   SET NOCOUNT ON

   -- insert new profile record, rely on primary key constraint to error out
   INSERT INTO msdb.dbo.sysmail_profile (name,description) VALUES (@profile_name, @description)
   
   -- fetch back profile_id
   SELECT @profile_id = profile_id FROM msdb.dbo.sysmail_profile WHERE name = @profile_name

   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_update_profile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_update_profile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_update_profile_sp
go

CREATE PROCEDURE dbo.sysmail_update_profile_sp
   @profile_id int = NULL, -- must provide either id or name
   @profile_name sysname = NULL,
   @description nvarchar(256) = NULL
AS
   SET NOCOUNT ON
  
   DECLARE @rc int
   DECLARE @profileid int
   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 0, 1, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)
   
   IF (@profile_name IS NOT NULL AND @description IS NOT NULL)
      UPDATE msdb.dbo.sysmail_profile 
      SET name=@profile_name, description = @description
      WHERE profile_id = @profileid
      
   ELSE IF (@profile_name IS NOT NULL)
      UPDATE msdb.dbo.sysmail_profile 
      SET name=@profile_name
      WHERE profile_id = @profileid

   ELSE IF (@description IS NOT NULL)
      UPDATE msdb.dbo.sysmail_profile 
      SET description = @description
      WHERE profile_id = @profileid
      
   ELSE
   BEGIN
      RAISERROR(14610, -1, -1)   
      RETURN(1)
   END

   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_delete_profile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_delete_profile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_delete_profile_sp
go

USE [msdb]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO


CREATE PROCEDURE [dbo].[sysmail_delete_profile_sp]
   @profile_id int = NULL, -- must provide either id or name
   @profile_name sysname = NULL,
   @force_delete BIT = 1
AS
   SET NOCOUNT ON
  
   DECLARE @rc int
   DECLARE @profileid int
   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 0, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)

IF(EXISTS (select * from sysmail_unsentitems WHERE 
   sysmail_unsentitems.profile_id = @profileid) AND @force_delete <> 1)
BEGIN
    IF(@profile_name IS NULL)
    BEGIN
        select @profile_name = name from dbo.sysmail_profile WHERE profile_id = @profileid
    END
    RAISERROR(14668, -1, -1, @profile_name)
    RETURN (1)   
END

UPDATE [msdb].[dbo].[sysmail_mailitems]
SET [sent_status] = 2, [sent_date] = getdate()
WHERE profile_id = @profileid AND sent_status <> 1
     
   DELETE FROM msdb.dbo.sysmail_profile 
   WHERE profile_id = @profileid
   RETURN(0)
GO

PRINT ''
PRINT 'Creating procedure sysmail_help_profile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_help_profile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_help_profile_sp
go

CREATE PROCEDURE dbo.sysmail_help_profile_sp
   @profile_id int = NULL,
   @profile_name sysname = NULL
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @profileid int
   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 1, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)

   IF (@profileid IS NOT NULL)
      SELECT profile_id, name, description 
      FROM msdb.dbo.sysmail_profile 
      WHERE profile_id = @profileid
      
   ELSE -- don't filter the output
      SELECT profile_id, name, description      
      FROM msdb.dbo.sysmail_profile 

   RETURN(0)
go


PRINT ''
PRINT 'Creating procedure sysmail_create_user_credential_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_create_user_credential_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_create_user_credential_sp
go

CREATE PROCEDURE dbo.sysmail_create_user_credential_sp
   @username      nvarchar(128),
   @password      nvarchar(128),
   @credential_id int            OUTPUT
AS
   SET NOCOUNT ON
   DECLARE @rc int
   DECLARE @credential_name UNIQUEIDENTIFIER
   DECLARE @credential_name_as_str varchar(40)
   DECLARE @sql NVARCHAR(max)

   -- create a GUID as the name for the credential
   SET @credential_name = newid()
   SET @credential_name_as_str = convert(varchar(40), @credential_name)
   SET @sql = N'CREATE CREDENTIAL [' + @credential_name_as_str
            + N'] WITH IDENTITY = ' + QUOTENAME(@username, '''')
            + N', SECRET = ' + QUOTENAME(ISNULL(@password, N''), '''')

   EXEC @rc = sp_executesql @statement = @sql
   IF(@rc <> 0)
      RETURN @rc

   SELECT @credential_id = credential_id 
   FROM sys.credentials
   WHERE name = convert(sysname, @credential_name)
    IF(@credential_id IS NULL)
   BEGIN
      RAISERROR(14616, -1, -1, @credential_name_as_str)
      RETURN 1
   END

   RETURN(0)
go



PRINT ''
PRINT 'Creating procedure sysmail_alter_user_credential_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_alter_user_credential_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_alter_user_credential_sp
go

CREATE PROCEDURE dbo.sysmail_alter_user_credential_sp
   @credential_name sysname,
   @username      nvarchar(128),
   @password      nvarchar(128)
AS
   SET NOCOUNT ON
   DECLARE @rc int
   DECLARE @sql NVARCHAR(max)

   -- alter credential DDL
   SET @sql = N'ALTER CREDENTIAL ' + QUOTENAME(@credential_name)
         + N' WITH IDENTITY = ' + QUOTENAME(@username, '''')
         + N', SECRET = ' + QUOTENAME(ISNULL(@password, N''), '''')

   EXEC @rc = sp_executesql @statement = @sql
   IF(@rc <> 0)
      RETURN @rc

   RETURN(0)
go


PRINT ''
PRINT 'Creating procedure sysmail_drop_user_credential_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_drop_user_credential_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_drop_user_credential_sp
go

CREATE PROCEDURE dbo.sysmail_drop_user_credential_sp
   @credential_name sysname
AS
   SET NOCOUNT ON
   DECLARE @rc int
   DECLARE @sql NVARCHAR(max)

   -- Drop credential DDL
   SET @sql = N'DROP CREDENTIAL ' + QUOTENAME(@credential_name)

   EXEC @rc = sp_executesql @statement = @sql
   IF(@rc <> 0)
      RETURN @rc

   RETURN(0)
go

         
PRINT ''
PRINT 'Creating procedure sysmail_add_account_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_add_account_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_add_account_sp
go

CREATE PROCEDURE dbo.sysmail_add_account_sp
   @account_name sysname,
   @email_address nvarchar(128),
   @display_name nvarchar(128) = NULL,
   @replyto_address nvarchar(128) = NULL,
   @description nvarchar(256) = NULL,  
   @mailserver_name sysname = NULL, -- the following fields are part of server definition
   @mailserver_type sysname = N'SMTP',
   @port int = 25,
   @username nvarchar(128) = NULL,
   @password nvarchar(128) = NULL,
   @use_default_credentials bit = 0,
   @enable_ssl bit = 0,
   @account_id int = NULL OUTPUT
AS
   SET NOCOUNT ON
   DECLARE @rc int
   DECLARE @credential_id int

   EXEC @rc = msdb.dbo.sysmail_verify_accountparams_sp
            @use_default_credentials = @use_default_credentials,
            @mailserver_type = @mailserver_type OUTPUT, -- validates and returns trimmed value
            @username = @username OUTPUT, -- returns trimmed value, NULL if empty
            @password = @password OUTPUT  -- returns trimmed value, NULL if empty
   IF(@rc <> 0)
      RETURN (1)

   --transact this in case sysmail_create_user_credential_sp fails
   BEGIN TRANSACTION

   -- insert new account record, rely on primary key constraint to error out
   INSERT INTO msdb.dbo.sysmail_account (name,description,email_address,display_name,replyto_address) 
   VALUES (@account_name,@description,@email_address,@display_name,@replyto_address)
   IF (@@ERROR <> 0)
   BEGIN
      ROLLBACK TRANSACTION
      RETURN (2)
   END
   
   -- fetch back account_id
   SELECT @account_id = account_id FROM msdb.dbo.sysmail_account WHERE name = @account_name

   IF (@mailserver_name IS NULL) -- use local server as default
      SELECT @mailserver_name=@@SERVERNAME

   --create a credential in the credential store if a password needs to be stored
   IF(@username IS NOT NULL)
   BEGIN
      EXEC @rc = msdb.dbo.sysmail_create_user_credential_sp
                     @username,
                     @password,
                     @credential_id OUTPUT
      IF(@rc <> 0)
      BEGIN 
         ROLLBACK TRANSACTION
         RETURN (3)
      END
   END
   
   INSERT INTO msdb.dbo.sysmail_server (account_id,servertype,servername,port,username,credential_id,use_default_credentials,enable_ssl) 
   VALUES (@account_id,@mailserver_type,@mailserver_name,@port,@username,@credential_id,@use_default_credentials,@enable_ssl)
   IF (@@ERROR <> 0)
   BEGIN
      ROLLBACK TRANSACTION
      RETURN (4)
   END

   COMMIT TRANSACTION
   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_update_account_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_update_account_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_update_account_sp
go

USE [msdb]
GO
/****** Object:  StoredProcedure [dbo].[sysmail_update_account_sp]    Script Date: 06/26/2006 10:45:40 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO

CREATE PROCEDURE [dbo].[sysmail_update_account_sp]
   @account_id int = NULL, -- must provide either id or name
   @account_name sysname = NULL,
   @email_address nvarchar(128) = NULL,
   @display_name nvarchar(128) = NULL,
   @replyto_address nvarchar(128) = NULL,
   @description nvarchar(256) = NULL,
   @mailserver_name sysname = NULL,  
   @mailserver_type sysname = NULL,  
   @port int = NULL,
   @username sysname = NULL,
   @password sysname = NULL,
   @use_default_credentials bit = NULL,
   @enable_ssl bit = NULL
  -- WITH EXECUTE AS OWNER --Allows access to sys.credentials
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @accountid int
   DECLARE @credential_id int
   DECLARE @credential_name sysname

   exec @rc = msdb.dbo.sysmail_verify_account_sp @account_id, @account_name, 0, 1, @accountid OUTPUT
   IF @rc <> 0
      RETURN(1)


   IF(@email_address IS NULL)
   BEGIN
   SELECT @email_address = email_address FROM msdb.dbo.sysmail_account WHERE account_id=@accountid
   END


   IF(@display_name IS NULL)
   BEGIN
   SELECT @display_name = display_name FROM msdb.dbo.sysmail_account WHERE account_id=@accountid
   END

   IF(@replyto_address IS NULL)
   BEGIN
   SELECT @replyto_address = replyto_address FROM msdb.dbo.sysmail_account WHERE account_id=@accountid
   END

   IF(@description IS NULL)
   BEGIN
   SELECT @description = description FROM msdb.dbo.sysmail_account WHERE account_id=@accountid
   END


   IF(@port IS NULL)
   BEGIN
   SELECT @port = port FROM msdb.dbo.sysmail_server WHERE account_id=@accountid
   END

   IF(@use_default_credentials IS NULL)
   BEGIN
   SELECT @use_default_credentials = use_default_credentials FROM msdb.dbo.sysmail_server WHERE account_id=@accountid
   END

   IF(@enable_ssl IS NULL)
   BEGIN
   SELECT @enable_ssl = enable_ssl FROM msdb.dbo.sysmail_server WHERE account_id=@accountid
   END

   IF(@mailserver_type IS NULL)
   BEGIN
   SELECT @mailserver_type = servertype FROM msdb.dbo.sysmail_server WHERE account_id=@accountid
   END

 
   EXEC @rc = msdb.dbo.sysmail_verify_accountparams_sp 
            @use_default_credentials = @use_default_credentials,
            @mailserver_type = @mailserver_type OUTPUT, -- validates and returns trimmed value
            @username = @username OUTPUT, -- returns trimmed value
            @password = @password OUTPUT  -- returns empty string if @username is given and @password is null 
   IF(@rc <> 0)
      RETURN (1)

   --transact this in case credential updates fail
   BEGIN TRAN
   -- update account table
   IF (@account_name IS NOT NULL)
      IF (@email_address IS NOT NULL)
         UPDATE msdb.dbo.sysmail_account
         SET name=@account_name, description=@description, email_address=@email_address, display_name=@display_name, replyto_address=@replyto_address
         WHERE account_id=@accountid
      ELSE
         UPDATE msdb.dbo.sysmail_account
         SET name=@account_name, description=@description, display_name=@display_name, replyto_address=@replyto_address
         WHERE account_id=@accountid

   ELSE
      IF (@email_address IS NOT NULL)
         UPDATE msdb.dbo.sysmail_account
         SET description=@description, email_address=@email_address, display_name=@display_name, replyto_address=@replyto_address
         WHERE account_id=@accountid
      ELSE
         UPDATE msdb.dbo.sysmail_account
         SET description=@description, display_name=@display_name, replyto_address=@replyto_address
         WHERE account_id=@accountid
      
   -- see if a credential has been stored for this account
   SELECT @credential_name = name,
         @credential_id = c.credential_id
   FROM sys.credentials as c
     JOIN msdb.dbo.sysmail_server as ms
       ON c.credential_id = ms.credential_id
   WHERE account_id = @accountid 
     AND servertype = @mailserver_type

   --update the credential store
   IF(@credential_name IS NOT NULL)
   BEGIN
      --Remove the unneed credential
      IF(@username IS NULL)
      BEGIN
         SET @credential_id = NULL
         EXEC @rc = msdb.dbo.sysmail_drop_user_credential_sp 
                        @credential_name = @credential_name
      END
      -- Update the credential
      ELSE
      BEGIN
         EXEC @rc = msdb.dbo.sysmail_alter_user_credential_sp
                        @credential_name = @credential_name,
                        @username = @username,
                        @password = @password
      END

      IF(@rc <> 0)
      BEGIN 
         ROLLBACK TRAN
         RETURN (1)
      END
   END
   -- create a new credential if one doesn't exist
   ELSE IF(@credential_name IS NULL AND @username IS NOT NULL)
   BEGIN
      EXEC @rc = msdb.dbo.sysmail_create_user_credential_sp
                     @username = @username,
                     @password = @password,
                     @credential_id = @credential_id  OUTPUT
      IF(@rc <> 0)
      BEGIN 
         ROLLBACK TRAN
         RETURN (1)
      END
   END

   -- update server table
   IF (@mailserver_name IS NOT NULL)
      UPDATE msdb.dbo.sysmail_server 
      SET servername=@mailserver_name, port=@port, username=@username, credential_id = @credential_id, use_default_credentials = @use_default_credentials, enable_ssl = @enable_ssl
      WHERE account_id=@accountid AND servertype=@mailserver_type
   
   ELSE
      UPDATE msdb.dbo.sysmail_server 
      SET port=@port, username=@username, credential_id = @credential_id, use_default_credentials = @use_default_credentials, enable_ssl = @enable_ssl
      WHERE account_id=@accountid AND servertype=@mailserver_type
      
   COMMIT TRAN
   RETURN(0)
go


PRINT ''
PRINT 'Creating procedure sysmail_delete_account_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_delete_account_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_delete_account_sp
go

CREATE PROCEDURE dbo.sysmail_delete_account_sp
   @account_id int = NULL, -- must provide either id or name
   @account_name sysname = NULL
AS
   SET NOCOUNT ON
  
   DECLARE @rc int
   DECLARE @accountid int
   DECLARE @credential_name sysname

   exec @rc = msdb.dbo.sysmail_verify_account_sp @account_id, @account_name, 0, 0, @accountid OUTPUT
   IF @rc <> 0
      RETURN(1)

   -- Get all the credentials has been stored for this account
   DECLARE cur CURSOR FOR
      SELECT c.name
      FROM sys.credentials as c
      JOIN msdb.dbo.sysmail_server as ms
         ON c.credential_id = ms.credential_id
      WHERE account_id = @accountid

   OPEN cur
   FETCH NEXT FROM cur INTO @credential_name
   WHILE @@FETCH_STATUS = 0
   BEGIN
      -- drop the credential
      EXEC msdb.dbo.sysmail_drop_user_credential_sp @credential_name = @credential_name

      FETCH NEXT FROM cur INTO @credential_name
   END

   CLOSE cur
   DEALLOCATE cur
     
   DELETE FROM msdb.dbo.sysmail_account
   WHERE account_id = @accountid
   
   RETURN(0)
go


PRINT ''
PRINT 'Creating procedure sysmail_help_account_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_help_account_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_help_account_sp
go

CREATE PROCEDURE dbo.sysmail_help_account_sp
   @account_id int = NULL,
   @account_name sysname = NULL
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @accountid int
   exec @rc = msdb.dbo.sysmail_verify_account_sp @account_id, @account_name, 1, 0, @accountid OUTPUT
   IF @rc <> 0
      RETURN(1)

   IF (@accountid IS NOT NULL)
      SELECT a.account_id, a.name, a.description, a.email_address, a.display_name, a.replyto_address, s.servertype, s.servername, s.port, s.username, s.use_default_credentials, s.enable_ssl 
      FROM msdb.dbo.sysmail_account a, msdb.dbo.sysmail_server s
      WHERE a.account_id = s.account_id AND a.account_id = @accountid
      
   ELSE
      SELECT a.account_id, a.name, a.description, a.email_address, a.display_name, a.replyto_address, s.servertype, s.servername, s.port, s.username, s.use_default_credentials, s.enable_ssl
      FROM msdb.dbo.sysmail_account a, msdb.dbo.sysmail_server s
      WHERE a.account_id = s.account_id

   RETURN(0)
go

-- Access to the complete account info. Required by databasemail.exe
PRINT ''
PRINT 'Creating procedure sysmail_help_admin_account_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_help_admin_account_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_help_admin_account_sp
go

CREATE PROCEDURE dbo.sysmail_help_admin_account_sp
   @account_id int
AS
   SET NOCOUNT ON

   DECLARE @rc         int,
      @acc_id         int,
      @name           sysname,
      @description    nvarchar(256),
      @email_address  nvarchar(128),
      @display_name   nvarchar(128),
      @replyto_address nvarchar(128),  
      @servertype     sysname,
      @servername     sysname,
      @port           int,
      @username       nvarchar(128),
      @passwordsize   int,  
      @cryptpassword  varbinary(1024),
      @credential_id  int,
      @use_default_credentials bit,
      @enable_ssl     bit

    SET @passwordsize = 0

   EXEC @rc = msdb.dbo.sysmail_verify_account_sp @account_id, NULL, 1, 0, NULL
   IF @rc <> 0
      RETURN(1)

   SELECT 
      @acc_id         = a.account_id,
      @name           = a.name, 
      @description    = a.description, 
      @email_address  = a.email_address, 
      @display_name   = a.display_name, 
      @replyto_address= a.replyto_address,
      @servertype     = s.servertype, 
      @servername     = s.servername, 
      @port           = s.port, 
      @username       = s.username,
      @credential_id  = s.credential_id,
      @use_default_credentials = s.use_default_credentials,
      @enable_ssl     = s.enable_ssl
   FROM msdb.dbo.sysmail_account a, msdb.dbo.sysmail_server s
   WHERE (a.account_id = s.account_id) AND 
      (a.account_id = @account_id)
    
    --get the encrypted password if required 
    IF(@username IS NOT NULL)
    BEGIN
        DECLARE @cred TABLE([size] INT, blob VARBINARY(1024));

        INSERT @cred
        EXEC @rc = master.dbo.sp_PostAgentInfo @credential_id
        IF @rc <> 0
        BEGIN
          RETURN(1)
        END
        
        SELECT @passwordsize = [size], @cryptpassword = [blob] 
        FROM @cred
    END
    
    --All done return result
    SELECT
        @acc_id         as 'account_id',
        @name           as 'name',  
        @description    as 'description',
        @email_address  as 'email_address',
        @display_name   as 'display_name',
        @replyto_address as 'replyto_address',
        @servertype     as 'servertype',
        @servername     as 'servername',
        @port           as 'port',
        @username       as 'username',
        @passwordsize   as 'password_size',
        @cryptpassword  as 'password_crypt',
        @use_default_credentials as 'use_default_credentials',
        @enable_ssl     as 'enable_ssl'

   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_add_profileaccount_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_add_profileaccount_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_add_profileaccount_sp
go

CREATE PROCEDURE dbo.sysmail_add_profileaccount_sp
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL,
   @account_id int = NULL, -- must provide id or name
   @account_name sysname = NULL,
   @sequence_number int -- account with the lowest sequence number is picked as default
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @profileid int
   DECLARE @accountid int

   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 0, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)

   exec @rc = msdb.dbo.sysmail_verify_account_sp @account_id, @account_name, 0, 0, @accountid OUTPUT
   IF @rc <> 0
      RETURN(2)

   -- insert new account record, rely on primary key constraint to error out
   INSERT INTO msdb.dbo.sysmail_profileaccount (profile_id,account_id,sequence_number)
   VALUES (@profileid,@accountid,@sequence_number)
   
   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_update_profileaccount_sp...'
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysmail_update_profileaccount_sp')
              AND (type = 'P')))
  DROP PROCEDURE dbo.sysmail_update_profileaccount_sp
go

CREATE PROCEDURE dbo.sysmail_update_profileaccount_sp
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL,
   @account_id int = NULL, -- must provide id or name
   @account_name sysname = NULL,
   @sequence_number int -- account with the lowest sequence number is picked as default
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @profileid int
   DECLARE @accountid int

   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 0, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)

   exec @rc = msdb.dbo.sysmail_verify_account_sp @account_id, @account_name, 0, 0, @accountid OUTPUT
   IF @rc <> 0
      RETURN(2)

   IF (@sequence_number IS NULL)
   BEGIN
      RAISERROR(14611, -1, -1)   
      RETURN(3)
   END
   
   UPDATE msdb.dbo.sysmail_profileaccount
   SET sequence_number=@sequence_number
   WHERE profile_id=@profileid AND account_id=@accountid
   
   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_delete_profileaccount_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_delete_profileaccount_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_delete_profileaccount_sp
go

CREATE PROCEDURE dbo.sysmail_delete_profileaccount_sp
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL,
   @account_id int = NULL, -- must provide id or name
   @account_name sysname = NULL
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @profileid int
   DECLARE @accountid int

   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 1, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)

   exec @rc = msdb.dbo.sysmail_verify_account_sp @account_id, @account_name, 1, 0, @accountid OUTPUT
   IF @rc <> 0
      RETURN(2)

   IF (@profileid IS NOT NULL AND @accountid IS NOT NULL) -- both parameters supplied for deletion
      DELETE FROM msdb.dbo.sysmail_profileaccount
      WHERE profile_id=@profileid AND account_id=@accountid

   ELSE IF (@profileid IS NOT NULL) -- profile id is supplied
      DELETE FROM msdb.dbo.sysmail_profileaccount
      WHERE profile_id=@profileid

   ELSE IF (@accountid IS NOT NULL) -- account id is supplied
      DELETE FROM msdb.dbo.sysmail_profileaccount
      WHERE account_id=@accountid

   ELSE -- no parameters are supplied for deletion
   BEGIN
      RAISERROR(14608, -1, -1, 'profile', 'account')  
      RETURN(3)   
   END

   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_help_profileaccount_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_help_profileaccount_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_help_profileaccount_sp
go

CREATE PROCEDURE dbo.sysmail_help_profileaccount_sp
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL,
   @account_id int = NULL, -- must provide id or name
   @account_name sysname = NULL
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @profileid int
   DECLARE @accountid int

   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 1, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)

   exec @rc = msdb.dbo.sysmail_verify_account_sp @account_id, @account_name, 1, 0, @accountid OUTPUT
   IF @rc <> 0
      RETURN(2)

   IF (@profileid IS NOT NULL AND @accountid IS NOT NULL)
      SELECT p.profile_id,profile_name=p.name,a.account_id,account_name=a.name,c.sequence_number
      FROM msdb.dbo.sysmail_profile p, msdb.dbo.sysmail_account a, msdb.dbo.sysmail_profileaccount c
      WHERE p.profile_id=c.profile_id AND a.account_id=c.account_id AND c.profile_id=@profileid AND c.account_id=@accountid
   
   ELSE IF (@profileid IS NOT NULL)
      SELECT p.profile_id,profile_name=p.name,a.account_id,account_name=a.name,c.sequence_number
      FROM msdb.dbo.sysmail_profile p, msdb.dbo.sysmail_account a, msdb.dbo.sysmail_profileaccount c
      WHERE p.profile_id=c.profile_id AND a.account_id=c.account_id AND c.profile_id=@profileid

   ELSE IF (@accountid IS NOT NULL)
      SELECT p.profile_id,profile_name=p.name,a.account_id,account_name=a.name,c.sequence_number
      FROM msdb.dbo.sysmail_profile p, msdb.dbo.sysmail_account a, msdb.dbo.sysmail_profileaccount c
      WHERE p.profile_id=c.profile_id AND a.account_id=c.account_id AND c.account_id=@accountid

   ELSE
      SELECT p.profile_id,profile_name=p.name,a.account_id,account_name=a.name,c.sequence_number
      FROM msdb.dbo.sysmail_profile p, msdb.dbo.sysmail_account a, msdb.dbo.sysmail_profileaccount c
      WHERE p.profile_id=c.profile_id AND a.account_id=c.account_id
      
   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_configure_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_configure_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_configure_sp
go

CREATE PROCEDURE dbo.sysmail_configure_sp
   @parameter_name nvarchar(256),
   @parameter_value nvarchar(256),
   @description nvarchar(256) = NULL
AS
   SET NOCOUNT ON
   
   IF (@description IS NOT NULL)
      UPDATE msdb.dbo.sysmail_configuration
      SET paramvalue=@parameter_value, description=@description
      WHERE paramname=@parameter_name
   ELSE
      UPDATE msdb.dbo.sysmail_configuration
      SET paramvalue=@parameter_value
      WHERE paramname=@parameter_name

   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_help_configure_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_help_configure_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_help_configure_sp
go

CREATE PROCEDURE dbo.sysmail_help_configure_sp
   @parameter_name nvarchar(256) = NULL
AS
   SET NOCOUNT ON

    SELECT paramname, paramvalue, description
    FROM msdb.dbo.sysmail_configuration
    WHERE paramname = ISNULL(@parameter_name, paramname)

    RETURN(0)
go


PRINT ''
PRINT 'Creating procedure sysmail_help_configure_value_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_help_configure_value_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_help_configure_value_sp
go

CREATE PROCEDURE dbo.sysmail_help_configure_value_sp
   @parameter_name nvarchar(256),
   @parameter_value nvarchar(256) OUTPUT
AS
   SET NOCOUNT ON
   SET @parameter_value = NULL
    
   IF (@parameter_name IS NULL)
   BEGIN
      RAISERROR(14618, 16, 1, '@parameter_name')
      RETURN(1)   
   END

    SELECT @parameter_value = paramvalue
    FROM msdb.dbo.sysmail_configuration
    WHERE paramname = @parameter_name

   RETURN(0)
go


PRINT ''
PRINT 'Creating procedure sysmail_add_principalprofile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_add_principalprofile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_add_principalprofile_sp
go

CREATE PROCEDURE dbo.sysmail_add_principalprofile_sp
   @principal_id int = NULL, -- must provide id or name
   @principal_name sysname = NULL,
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL,
   @is_default bit
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @principal_sid varbinary(85)
   DECLARE @profileid int

   IF (@principal_id IS NOT NULL AND @principal_id = 0) OR (@principal_name IS NOT NULL AND @principal_name = N'public')
   BEGIN
      IF (@principal_id IS NOT NULL AND @principal_id <> 0) OR (@principal_name IS NOT NULL AND @principal_name <> N'public')
      BEGIN
         RAISERROR(14605, -1, -1, 'principal')  -- id and name do not match
      END
      SET @principal_sid = 0x00 -- public
   END
   ELSE
   BEGIN
      exec @rc = msdb.dbo.sysmail_verify_principal_sp @principal_id, @principal_name, 0, @principal_sid OUTPUT
      IF @rc <> 0
         RETURN(2)
   END
   
   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 0, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(3)

   -- insert new account record, rely on primary key constraint to error out
   INSERT INTO msdb.dbo.sysmail_principalprofile (principal_sid,profile_id,is_default)
   VALUES (@principal_sid,@profileid,@is_default)

   IF (@is_default IS NOT NULL AND @is_default = 1 )
   BEGIN
      -- a principal can only have one default profile so reset other, if there are any
      UPDATE msdb.dbo.sysmail_principalprofile
      SET is_default=0
      WHERE principal_sid = @principal_sid AND profile_id <> @profileid
   END
   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_update_principalprofile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_update_principalprofile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_update_principalprofile_sp
go

CREATE PROCEDURE dbo.sysmail_update_principalprofile_sp
   @principal_id int = NULL, -- must provide id or name
   @principal_name sysname = NULL,
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL,
   @is_default bit
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @principal_sid varbinary(85)
   DECLARE @profileid int

   IF (@principal_id IS NOT NULL AND @principal_id = 0) OR (@principal_name IS NOT NULL AND @principal_name = N'public')
   BEGIN
      IF (@principal_id IS NOT NULL AND @principal_id <> 0) OR (@principal_name IS NOT NULL AND @principal_name <> N'public')
      BEGIN
         RAISERROR(14605, -1, -1, 'principal')  -- id and name do not match
      END
      SET @principal_sid = 0x00 -- public
   END
   ELSE
   BEGIN
      exec @rc = msdb.dbo.sysmail_verify_principal_sp @principal_id, @principal_name, 0, @principal_sid OUTPUT
      IF @rc <> 0
         RETURN(1)
   END
   
   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 0, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(2)

   UPDATE msdb.dbo.sysmail_principalprofile
   SET is_default=@is_default
   WHERE principal_sid = @principal_sid AND profile_id = @profileid

   IF (@is_default IS NOT NULL AND @is_default = 1)
   BEGIN
      -- a principal can only have one default profile so reset others (if there are any)
      UPDATE msdb.dbo.sysmail_principalprofile
      SET is_default=0
      WHERE principal_sid = @principal_sid AND profile_id <> @profileid
   END

   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_delete_principalprofile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_delete_principalprofile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_delete_principalprofile_sp
go

CREATE PROCEDURE dbo.sysmail_delete_principalprofile_sp
   @principal_id int = NULL, -- must provide id or name
   @principal_name sysname = NULL,
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @principal_sid varbinary(85)
   DECLARE @profileid int

   IF (@principal_id IS NOT NULL AND @principal_id = 0) OR (@principal_name IS NOT NULL AND @principal_name = N'public')
   BEGIN
      IF (@principal_id IS NOT NULL AND @principal_id <> 0) OR (@principal_name IS NOT NULL AND @principal_name <> N'public')
      BEGIN
         RAISERROR(14605, -1, -1, 'principal')  -- id and name do not match
      END
      SET @principal_sid = 0x00 -- public
   END
   ELSE
   BEGIN
      IF (@principal_id IS NOT NULL OR @principal_name IS NOT NULL) 
      BEGIN
         exec @rc = msdb.dbo.sysmail_verify_principal_sp @principal_id, @principal_name, 1, @principal_sid OUTPUT
         IF @rc <> 0
            RETURN(1)
      END
   END
   
   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 1, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(2)

   IF ((@principal_id IS NOT NULL OR @principal_name IS NOT NULL) AND @profileid IS NOT NULL)
   BEGIN
      DELETE FROM msdb.dbo.sysmail_principalprofile WHERE principal_sid=@principal_sid AND profile_id = @profileid
   END
   ELSE IF (@principal_id IS NOT NULL OR @principal_name IS NOT NULL)
   BEGIN
      DELETE FROM msdb.dbo.sysmail_principalprofile WHERE principal_sid=@principal_sid
   END
   ELSE IF (@profileid IS NOT NULL)
   BEGIN
      DELETE FROM msdb.dbo.sysmail_principalprofile WHERE profile_id = @profileid
   END
   ELSE -- no parameters are supplied for deletion
   BEGIN
      RAISERROR(14608, -1, -1, 'principal', 'profile') 
      RETURN(6)
   END
   
   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_help_principalprofile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_help_principalprofile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_help_principalprofile_sp
go

CREATE PROCEDURE dbo.sysmail_help_principalprofile_sp
   @principal_id int = NULL, -- must provide id or name
   @principal_name sysname = NULL,
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @principal_sid varbinary(85)
   DECLARE @profileid int

   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 1, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)

   IF (@principal_id IS NOT NULL AND @principal_id = 0) OR (@principal_name IS NOT NULL AND @principal_name = N'public')
   BEGIN
      IF (@principal_id IS NOT NULL AND @principal_id <> 0) OR (@principal_name IS NOT NULL AND @principal_name <> N'public')
      BEGIN
         RAISERROR(14605, -1, -1, 'principal')  -- id and name do not match
      END
      SET @principal_sid = 0x00 -- public

      IF (@profileid IS NOT NULL)
      BEGIN
         SELECT principal_id=0, 
                principal_name = N'public', 
                prof.profile_id, 
                profile_name=prof.name, 
                prin.is_default
         FROM msdb.dbo.sysmail_principalprofile prin, msdb.dbo.sysmail_profile prof 
         WHERE prin.profile_id=prof.profile_id AND 
               prin.principal_sid = @principal_sid AND 
               prof.profile_id=@profileid
      END
      ELSE
      BEGIN
         SELECT principal_id=0, 
                principal_name = N'public', 
                prof.profile_id, 
                profile_name=prof.name, 
                prin.is_default
         FROM msdb.dbo.sysmail_principalprofile prin, msdb.dbo.sysmail_profile prof 
         WHERE prin.profile_id=prof.profile_id AND 
               prin.principal_sid = @principal_sid
      END
   END
   ELSE -- non-public profiles
   BEGIN
      IF (@principal_id IS NOT NULL OR @principal_name IS NOT NULL)      
      BEGIN
            exec @rc = msdb.dbo.sysmail_verify_principal_sp @principal_id, @principal_name, 1, @principal_sid OUTPUT
            IF @rc <> 0
               RETURN(2)
      END

      IF ((@principal_id IS NOT NULL OR @principal_name IS NOT NULL) AND @profileid IS NOT NULL)
      BEGIN
            SELECT principal_id=dbprin.principal_id,
                   principal_name=dbprin.name,
                   prof.profile_id,
                   profile_name=prof.name,
                   prinprof.is_default
            FROM sys.database_principals dbprin, msdb.dbo.sysmail_principalprofile prinprof, msdb.dbo.sysmail_profile prof
            WHERE dbprin.principal_id = dbo.get_principal_id(prinprof.principal_sid) AND
                  (prinprof.principal_sid = @principal_sid OR prinprof.principal_sid = 0x00) AND
                  prof.profile_id = prinprof.profile_id AND
                  prinprof.profile_id = @profileid
      END
      ELSE IF (@principal_id IS NOT NULL OR @principal_name IS NOT NULL)
      BEGIN
            SELECT principal_id=dbprin.principal_id,
                   principal_name=dbprin.name,
                   prof.profile_id,
                   profile_name=prof.name,
                   prinprof.is_default
            FROM sys.database_principals dbprin, msdb.dbo.sysmail_principalprofile prinprof, msdb.dbo.sysmail_profile prof
            WHERE dbprin.principal_id = dbo.get_principal_id(prinprof.principal_sid) AND
                  (prinprof.principal_sid = @principal_sid OR prinprof.principal_sid = 0x00) AND
                  prof.profile_id = prinprof.profile_id
      END
      ELSE IF (@profileid IS NOT NULL)
      BEGIN
            SELECT principal_id=dbprin.principal_id,
                   principal_name=dbprin.name,
                   prof.profile_id,
                   profile_name=prof.name,
                   prinprof.is_default
            FROM sys.database_principals dbprin, msdb.dbo.sysmail_principalprofile prinprof, msdb.dbo.sysmail_profile prof
            WHERE dbprin.principal_id = dbo.get_principal_id(prinprof.principal_sid) AND
                  prof.profile_id = prinprof.profile_id AND
                  prinprof.profile_id = @profileid
      END
      ELSE -- no parameters are supplied for filtering
      BEGIN
            SELECT principal_id=dbprin.principal_id,
                   principal_name=dbprin.name,
                   prof.profile_id,
                   profile_name=prof.name,
                   prinprof.is_default
            FROM sys.database_principals dbprin, msdb.dbo.sysmail_principalprofile prinprof, msdb.dbo.sysmail_profile prof
            WHERE dbprin.principal_id = dbo.get_principal_id(prinprof.principal_sid) AND
                  prof.profile_id = prinprof.profile_id
      END
   END
   RETURN(0)
go

-----------------------------------------------------------
-- Database Mail: mail host database stored procedures
-----------------------------------------------------------

-----------------------------------------------------------
-- procedure sysmail_logmailevent_sp
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sysmail_logmailevent_sp', 'P') IS NULL
    DROP PROCEDURE dbo.sysmail_logmailevent_sp
GO
-----
PRINT 'Creating sysmail_logmailevent_sp'
-----
GO
-- sysmail_logmailevent_sp : inserts an entry in the sysmail_log table
CREATE PROCEDURE sysmail_logmailevent_sp
    @event_type     INT,
    @description    NVARCHAR(max)   = NULL, 
    @process_id     INT             = NULL,
    @mailitem_id    INT             = NULL,
    @account_id     INT             = NULL
AS
    SET NOCOUNT ON

    --Try and get the optional logging level for the DatabaseMail process
    DECLARE @loggingLevel nvarchar(256)
    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'LoggingLevel', 
                                                  @parameter_value = @loggingLevel OUTPUT

    DECLARE @loggingLevelInt int   
    SET @loggingLevelInt = dbo.ConvertToInt(@loggingLevel, 3, 2) 

    IF (@event_type = 3) OR                           -- error
       (@event_type = 2 AND @loggingLevelInt >= 2) OR -- warning with extended logging
       (@event_type = 1 AND @loggingLevelInt >= 2) OR -- info with extended logging
       (@event_type = 0 AND @loggingLevelInt >= 3)    -- success with verbose logging
    BEGIN
       INSERT sysmail_log(event_type, description, process_id, mailitem_id, account_id) 
       VALUES(@event_type, @description, @process_id , @mailitem_id, @account_id)
    END
RETURN 0
GO

-----------------------------------------------------------
-- procedure sysmail_start_sp
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sysmail_start_sp', 'P') IS NULL
    DROP PROCEDURE dbo.sysmail_start_sp
GO
-----
PRINT 'Creating sysmail_start_sp'
-----
GO
-- sysmail_start_sp : allows databasemail to process mail from the queue 
CREATE PROCEDURE sysmail_start_sp
AS
    SET NOCOUNT ON
    DECLARE @rc INT 
   DECLARE @localmessage nvarchar(255)

    ALTER QUEUE ExternalMailQueue WITH STATUS = ON
    SELECT @rc = @@ERROR
    IF(@rc = 0)
    BEGIN
      ALTER QUEUE ExternalMailQueue WITH ACTIVATION (STATUS = ON);
       SET @localmessage = FORMATMESSAGE(14639, SUSER_SNAME())
       exec msdb.dbo.sysmail_logmailevent_sp @event_type=1, @description=@localmessage
    END
RETURN @rc
GO

-----------------------------------------------------------
-- procedure sysmail_stop_sp
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sysmail_stop_sp', 'P') IS NULL
    DROP PROCEDURE dbo.sysmail_stop_sp
GO
-----
PRINT 'Creating sysmail_stop_sp'
-----
GO
-- sysmail_stop_sp : stops the DatabaseMail process. Mail items remain in the queue until sqlmail started 
CREATE PROCEDURE sysmail_stop_sp
AS
    SET NOCOUNT ON
    DECLARE @rc INT
   DECLARE @localmessage nvarchar(255)
  
    ALTER QUEUE ExternalMailQueue WITH ACTIVATION (STATUS = OFF);
    SELECT @rc = @@ERROR
    IF(@rc = 0)
    BEGIN
       ALTER QUEUE ExternalMailQueue WITH STATUS = OFF;
       SELECT @rc = @@ERROR
       IF(@rc = 0)
       BEGIN
          SET @localmessage = FORMATMESSAGE(14640, SUSER_SNAME())
          exec msdb.dbo.sysmail_logmailevent_sp @event_type=1, @description=@localmessage
       END
    END
RETURN @rc
GO


-----------------------------------------------------------
-- procedure sysmail_help_status_sp
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sysmail_help_status_sp', 'P') IS NULL
    DROP PROCEDURE dbo.sysmail_help_status_sp
GO
-----
PRINT 'Creating sysmail_help_status_sp'
-----
GO
CREATE PROCEDURE sysmail_help_status_sp
  WITH EXECUTE AS 'dbo'
AS
BEGIN
    IF NOT EXISTS (SELECT * FROM sys.service_queues WHERE name = N'ExternalMailQueue' AND is_receive_enabled = 1)
       SELECT 'STOPPED' AS Status
    ELSE
       SELECT 'STARTED' AS Status
END
GO


-----------------------------------------------------------
-- procedure sysmail_help_queue_sp
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sysmail_help_queue_sp', 'P') IS NULL
    DROP PROCEDURE dbo.sysmail_help_queue_sp
GO
-----
PRINT 'Creating sysmail_help_queue_sp'
-----
GO
CREATE PROCEDURE sysmail_help_queue_sp
   @queue_type nvarchar(6) = NULL -- Type of queue
AS
BEGIN

   SELECT @queue_type       = LTRIM(RTRIM(@queue_type))
   IF @queue_type           = '' SELECT @queue_type = NULL

   IF ( (@queue_type IS NOT NULL) AND
         (LOWER(@queue_type collate SQL_Latin1_General_CP1_CS_AS) NOT IN ( N'mail', N'status') ) )
   BEGIN
        RAISERROR(14266, -1, -1, '@queue_type', 'mail, status')
      RETURN(1) -- Failure
   END   

   DECLARE @depth      int
   DECLARE @temp TABLE (
                        queue_type nvarchar(6),
                        length INT NOT NULL, 
                        state nvarchar(64), 
                        last_empty_rowset_time DATETIME,
                        last_activated_time DATETIME
                       )
  
   IF ( (@queue_type IS NULL) OR (LOWER(@queue_type collate SQL_Latin1_General_CP1_CS_AS) = N'mail' ) )
   BEGIN
      SET @depth = (SELECT COUNT(*) FROM ExternalMailQueue WITH (NOWAIT NOLOCK READUNCOMMITTED))

      INSERT INTO @temp
         SELECT 
                N'mail',
                @depth, 
            qm.state as state,
            qm.last_empty_rowset_time as last_empty_rowset_time,
            qm.last_activated_time as last_activated_time
         FROM sys.dm_broker_queue_monitors qm
         JOIN sys.service_queues sq ON sq.object_id = qm.queue_id
         WHERE sq.name = 'ExternalMailQueue'
   END

   IF ( (@queue_type IS NULL) OR (LOWER(@queue_type collate SQL_Latin1_General_CP1_CS_AS) = N'status' ) )
   BEGIN
      SET @depth = (SELECT COUNT(*) FROM InternalMailQueue WITH (NOWAIT NOLOCK READUNCOMMITTED))

      INSERT INTO @temp
         SELECT 
                N'status',
                @depth, 
            qm.state as state,
            qm.last_empty_rowset_time as last_empty_rowset_time,
            qm.last_activated_time as last_activated_time
         FROM sys.dm_broker_queue_monitors qm
         JOIN sys.service_queues sq ON sq.object_id = qm.queue_id
         WHERE sq.name = 'InternalMailQueue'
   END

   SELECT * from @temp
END
GO

-----------------------------------------------------------
-- procedure sp_SendMailMessage
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_SendMailMessage', 'P') IS NULL
    DROP PROCEDURE dbo.sp_SendMailMessage
GO
-----
PRINT 'Creating sp_SendMailMessage'
-----
GO
-- sp_SendMailMessage : Sends a request on the mail items SSB queue
CREATE PROCEDURE sp_SendMailMessage
    @contract_name sysname, -- Name of contract
    @message_type sysname,  -- Type of message
    @request varchar(max) -- XML message to send
  WITH EXECUTE AS 'dbo'
AS

SET NOCOUNT ON

DECLARE @conversationHandle uniqueidentifier;
DECLARE @error int

-- Start a conversation with the remote service
BEGIN DIALOG  @conversationHandle
    FROM SERVICE    [InternalMailService]
    TO SERVICE      'ExternalMailService'
    ON CONTRACT     @contract_name
    WITH ENCRYPTION=OFF

-- Check error
SET @error = @@ERROR
IF @error <> 0
BEGIN
    RETURN @error
END

-- Send message
;SEND ON CONVERSATION @conversationHandle MESSAGE TYPE @message_type (@request)

-- Check error
SET @error = @@ERROR
IF @error <> 0
BEGIN
    RETURN @error
END

RETURN 0
GO

-----------------------------------------------------------
-- procedure sp_isprohibited
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_isprohibited', 'P') IS NULL
    DROP PROCEDURE dbo.sp_isprohibited
GO
-----
PRINT 'Creating sp_isprohibited'
-----
GO
-- sp_isprohibited : To test if the attachment is prohibited or not.
--
CREATE PROCEDURE sp_isprohibited
   @attachment nvarchar(max),
   @prohibitedextensions nvarchar(1000)
AS

DECLARE @extensionIndex int
DECLARE @extensionName nvarchar(255)

IF (@attachment IS NOT NULL AND LEN(@attachment) > 0) 
BEGIN
    SET @prohibitedextensions = UPPER(@prohibitedextensions)

   -- find @extensionName: the substring between the last '.' and the end of the string
   SET @extensionIndex = 0
   WHILE (1=1)
   BEGIN
      DECLARE @lastExtensionIndex int
      SET @lastExtensionIndex = CHARINDEX('.', @attachment, @extensionIndex+1)
      IF (@lastExtensionIndex = 0)
         BREAK
      SET @extensionIndex = @lastExtensionIndex
   END

   IF (@extensionIndex > 0)
   BEGIN
      SET @extensionName = SUBSTRING(@attachment, @extensionIndex + 1, (LEN(@attachment) - @extensionIndex)) 
      SET @extensionName = UPPER(@extensionName)

      -- compare @extensionName with each extension in the comma-separated @prohibitedextensions list
      DECLARE @currentExtensionStart int
      DECLARE @currentExtensionEnd int

      SET @currentExtensionStart = 0
      SET @currentExtensionEnd = 0
      WHILE (@currentExtensionEnd < LEN(@prohibitedextensions))
      BEGIN
         SET @currentExtensionEnd = CHARINDEX(',', @prohibitedextensions, @currentExtensionStart)

         IF (@currentExtensionEnd = 0) -- we have reached the last extension of the list, or the list was empty
            SET @currentExtensionEnd = LEN(@prohibitedextensions)+1

         DECLARE @prohibitedExtension nvarchar(1000)
         SET @prohibitedExtension = SUBSTRING(@prohibitedextensions, @currentExtensionStart, @currentExtensionEnd - @currentExtensionStart) 
         SET @prohibitedExtension = RTRIM(LTRIM(@prohibitedExtension))

         IF( @extensionName = @prohibitedExtension )
            RETURN 1

         SET @currentExtensionStart = @currentExtensionEnd + 1
      END
   END

   RETURN 0
END
GO

-----------------------------------------------------------
-- procedure sp_SendMailQueues
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_SendMailQueues', 'P') IS NULL
    DROP PROCEDURE dbo.sp_SendMailQueues
GO
-----
PRINT 'Creating sp_SendMailQueues'
-----
GO
-- sp_SendMailQueues : Writes a send mail request to the queue.
--
CREATE  PROCEDURE sp_SendMailQueues
    @message_data      varchar(max) -- The request in XML
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @contract_name nvarchar(128)
    DECLARE @message_type nvarchar(128)
    DECLARE @retValue int

    SET @message_type = '{//www.microsoft.com/databasemail/messages}SendMail'
    SET @contract_name = '//www.microsoft.com/databasemail/contracts/SendMail/v1.0'

    --Writes the message to the queue
    EXEC @retValue = sp_SendMailMessage @contract_name, @message_type, @message_data

    RETURN @retValue
END
GO


-----------------------------------------------------------
-- procedure sp_ProcessResponse
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_ProcessResponse', 'P') IS NULL
    DROP PROCEDURE dbo.sp_ProcessResponse
GO
-----
PRINT 'Creating sp_ProcessResponse'
-----
USE [msdb]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF


GO
-- Processes responses from dbmail 
--
CREATE PROCEDURE [dbo].[sp_ProcessResponse]
    @conv_handle        uniqueidentifier,
    @message_type_name  NVARCHAR(256),
    @xml_message_body   NVARCHAR(max)
AS
BEGIN
    DECLARE 
        @idoc               INT,
        @mailitem_id        INT,
        @sent_status        INT,
        @rc                 INT,
        @index              INT,
        @processId          INT,
        @sent_date          DATETIME,
        @localmessage       NVARCHAR(max),
        @LogMessage         NVARCHAR(max),
        @retry_hconv        uniqueidentifier,
        @paramStr           NVARCHAR(256),
        @accRetryDelay      INT

    --------------------------
    --Always send the response 
    ;SEND ON CONVERSATION @conv_handle MESSAGE TYPE @message_type_name (@xml_message_body)


    --
    -- Need to handle the case where a sent retry is requested. 
    -- This is done by setting a conversation timer, The timer with go off in the external queue

    -- Get the handle to the xml document
    EXEC @rc = sp_xml_preparedocument 
                    @idoc OUTPUT, 
                    @xml_message_body, 
                    N'<ns xmlns:responses="http://schemas.microsoft.com/databasemail/responses" />'
    IF(@rc <> 0)
    BEGIN
        --Log the error. The response has already sent to the Internal queue. 
        -- This will update the mail with the latest staus
        SET @localmessage = FORMATMESSAGE(14655, CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage

        GOTO ErrorHandler;
    END

    -- Execute a SELECT statement that uses the OPENXML rowset provider to get the MailItemId and sent status.
    SELECT @mailitem_id     = MailItemId, 
           @sent_status     = SentStatus
    FROM OPENXML (@idoc, '/responses:SendMail', 1)
        WITH (MailItemId    INT      './MailItemId/@Id', 
              SentStatus    INT      './SentStatus/@Status')

    --Close the handle to the xml document
    EXEC sp_xml_removedocument @idoc

    IF(@mailitem_id IS NULL OR @sent_status IS NULL)
    BEGIN  
        --Log error and continue.
        SET @localmessage = FORMATMESSAGE(14652, CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage

        GOTO ErrorHandler;
    END      

    -- Update the status of the email item
    UPDATE msdb.dbo.sysmail_mailitems
    SET sent_status = @sent_status
    WHERE mailitem_id = @mailitem_id

    --
    -- A send retry has been requested. Set a conversation timer
    IF(@sent_status = 3)
    BEGIN
        -- Get the associated mail item data for the given @conversation_handle (if it exists)
       SELECT @retry_hconv = conversation_handle
       FROM sysmail_send_retries as sr
            RIGHT JOIN sysmail_mailitems as mi
            ON sr.mailitem_id = mi.mailitem_id
       WHERE mi.mailitem_id = @mailitem_id

        --Must be the first retry attempt. Create a sysmail_send_retries record to track retries
        IF(@retry_hconv IS NULL)
        BEGIN
            INSERT sysmail_send_retries(conversation_handle, mailitem_id) --last_send_attempt_date
            VALUES(@conv_handle, @mailitem_id)
        END
        ELSE
        BEGIN
            --Update existing retry record
            UPDATE sysmail_send_retries
            SET last_send_attempt_date = GETDATE(),
                send_attempts = send_attempts + 1
            WHERE mailitem_id = @mailitem_id

        END

        --Get the global retry delay time
        EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'AccountRetryDelay', 
                                                    @parameter_value = @paramStr OUTPUT
        --ConvertToInt will return the default if @paramStr is null
        SET @accRetryDelay = dbo.ConvertToInt(@paramStr, 0x7fffffff, 300) -- 5 min default


        --Now set the dialog timer. This triggers the send retry
        ;BEGIN CONVERSATION TIMER (@conv_handle) TIMEOUT = @accRetryDelay 

    END
    ELSE
    BEGIN
        --Only end theconversation if a retry isn't being attempted
        END CONVERSATION @conv_handle
    END


    -- All done OK
    goto ExitProc;

    -----------------
    -- Error Handler
    -----------------
ErrorHandler:

    ------------------
    -- Exit Procedure
    ------------------
ExitProc:
    RETURN (@rc);

END
GO


-----------------------------------------------------------
-- procedure sp_MailItemResultSets
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_MailItemResultSets', 'P') IS NULL
    DROP PROCEDURE dbo.sp_MailItemResultSets
GO
-----
PRINT 'Creating sp_MailItemResultSets'
-----
GO
-- sp_MailItemResultSets : 
--  Sends back multiple rowsets with the mail items data
CREATE PROCEDURE sp_MailItemResultSets
    @mailitem_id            INT,
    @profile_id             INT,
    @conversation_handle    uniqueidentifier,
   @service_contract_name  NVARCHAR(256),
   @message_type_name      NVARCHAR(256)
AS
BEGIN
    SET NOCOUNT ON
   --
   -- Send back multiple rowsets with the mail items data

   ----
   -- 1) MessageTypeName
   SELECT @message_type_name  as 'message_type_name',
      @service_contract_name as 'service_contract_name',
      @conversation_handle   as 'conversation_handle',
      @mailitem_id           as 'mailitem_id'

   -----
   -- 2) The mail item record from sysmail_mailitems.
   SELECT 
      mi.mailitem_id,
      mi.profile_id,
      (SELECT name FROM msdb.dbo.sysmail_profile p WHERE p.profile_id = mi.profile_id) as 'profile_name',
      mi.recipients,
      mi.copy_recipients,
      mi.blind_copy_recipients,
      mi.subject,
      mi.body, 
      mi.body_format, 
      mi.importance,
      mi.sensitivity,
      ISNULL(sr.send_attempts, 0) as retry_attempt,
      ISNULL(mi.from_address, '') as from_address,
      ISNULL(mi.reply_to, '')     as reply_to
   FROM sysmail_mailitems as mi
      LEFT JOIN sysmail_send_retries as sr
         ON sr.mailitem_id = mi.mailitem_id 
   WHERE mi.mailitem_id = @mailitem_id

   -----
   -- 3) Account information 
   SELECT a.account_id,
         a.name
   FROM msdb.dbo.sysmail_profileaccount as pa
   JOIN msdb.dbo.sysmail_account as a
      ON pa.account_id = a.account_id
   WHERE pa.profile_id = @profile_id
   ORDER BY pa.sequence_number 

   -----
   -- 4) Attachments if any
   SELECT attachment_id,
      mailitem_id,
      filename,
      filesize,
      attachment
   FROM sysmail_attachments
   WHERE mailitem_id = @mailitem_id
   

   RETURN 0
END
GO

-----------------------------------------------------------
-- procedure sp_process_DialogTimer
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_process_DialogTimer', 'P') IS NULL
    DROP PROCEDURE dbo.sp_process_DialogTimer
GO
-----
PRINT 'Creating sp_process_DialogTimer'
-----
GO
-- Processes a DialogTimer message from the the queue. This is used for send mail retries.
-- Returns the mail to be send if a retry is required or logs a failure if max retry count has been reached 
CREATE PROCEDURE sp_process_DialogTimer
    @conversation_handle    uniqueidentifier,
   @service_contract_name  NVARCHAR(256),
   @message_type_name      NVARCHAR(256)
AS
BEGIN
    SET NOCOUNT ON

    -- Declare all variables
    DECLARE 
        @mailitem_id        INT,
        @profile_id         INT,
        @send_attempts      INT,
        @mail_request_date  DATETIME,
        @localmessage       NVARCHAR(255),
        @paramStr           NVARCHAR(256),
        @accRetryAttempts   INT

    -- Get the associated mail item data for the given @conversation_handle
   SELECT @mailitem_id     = mi.mailitem_id,
      @profile_id         = mi.profile_id,
        @mail_request_date  = mi.send_request_date,
      @send_attempts      = sr.send_attempts
   FROM sysmail_send_retries as sr
      JOIN sysmail_mailitems as mi
        ON sr.mailitem_id = mi.mailitem_id
   WHERE sr.conversation_handle = @conversation_handle

   -- If not able to find a mailitem_id return and move to the next message.
    -- This could happen if the mail items table was cleared before the retry was fired
   IF(@mailitem_id IS NULL)
   BEGIN
        --Log warning and continue
        -- "mailitem_id on conversation %s was not found in the sysmail_send_retries table. This mail item will not be sent."
        SET @localmessage = FORMATMESSAGE(14662, convert(NVARCHAR(50), @conversation_handle))
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=2, @description=@localmessage

        -- Resource clean-up
        IF(@conversation_handle IS NOT NULL)
        BEGIN
           END CONVERSATION @conversation_handle;
        END

        -- return code has special meaning and will be propagated to the calling process
        RETURN 2;
    END


    --Get the retry attempt count from sysmailconfig.
    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'AccountRetryAttempts', 
                                                 @parameter_value = @paramStr OUTPUT
    --ConvertToInt will return the default if @paramStr is null
    SET @accRetryAttempts = dbo.ConvertToInt(@paramStr, 0x7fffffff, 1)


   --Check the send attempts and log and error if send_attempts >= retry count.
    --This shouldn't happen unless the retry configuration was changed
   IF(@send_attempts > @accRetryAttempts)
   BEGIN
        --Log warning and continue
        -- "Mail Id %d has exceeded the retry count. This mail item will not be sent."
        SET @localmessage = FORMATMESSAGE(14663, @mailitem_id)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=2, @description=@localmessage, @mailitem_id=@mailitem_id

        -- Resource clean-up
        IF(@conversation_handle IS NOT NULL)
        BEGIN
           END CONVERSATION @conversation_handle;
        END

        -- return code has special meaning and will be propagated to the calling process
        RETURN 3;
    END

    -- This returns the mail item to the client as multiple result sets
    EXEC sp_MailItemResultSets
            @mailitem_id            = @mailitem_id,
            @profile_id             = @profile_id,
            @conversation_handle    = @conversation_handle,
           @service_contract_name  = @service_contract_name,
           @message_type_name      = @message_type_name

   
   RETURN 0

END
GO

-----------------------------------------------------------
-- procedure sp_readrequest
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_readrequest', 'P') IS NULL
    DROP PROCEDURE dbo.sp_readrequest
GO
-----
PRINT 'Creating sp_readrequest'
-----
GO
-- sp_readrequest : Reads a request from the the queue and returns its
--                  contents.
CREATE PROCEDURE sp_readrequest
    @receive_timeout    INT     -- the max time this read will wait for new message
AS
BEGIN
    SET NOCOUNT ON

    -- Table to store message information.
    DECLARE @msgs TABLE
    (
       [conversation_handle] uniqueidentifier,
       [service_contract_name] nvarchar(256),
       [message_type_name] nvarchar(256),
       [message_body] varbinary(max)
    )

    -- Declare variables to store row values fetched from the cursor
    DECLARE 
        @exit                   INT,
        @idoc                   INT,
        @mailitem_id            INT,
        @profile_id             INT,
        @conversation_handle    uniqueidentifier,
        @service_contract_name  NVARCHAR(256),
        @message_type_name      NVARCHAR(256),
        @xml_message_body       VARCHAR(max),
        @timediff               INT,
        @rec_timeout            INT,
        @start_time             DATETIME,
        @localmessage           NVARCHAR(256),
        @rc                     INT

    --Init variables
    SELECT @start_time = GETDATE(),
           @timediff = 0,
           @exit = 0,
           @rc = 0

    WHILE (@timediff <= @receive_timeout)
    BEGIN
        -- Delete all messages from @msgs table
        DELETE FROM @msgs

        -- Pick all message from queue
        SET @rec_timeout = @receive_timeout - @timediff
        WAITFOR(RECEIVE conversation_handle, service_contract_name, message_type_name, message_body 
                FROM ExternalMailQueue INTO @msgs), TIMEOUT @rec_timeout

        -- Check if there was some error in reading from queue
        SET @rc = @@ERROR
        IF (@rc <> 0)
        BEGIN
           IF(@rc < 4) -- make sure return code is not in reserved range (1-3)
               SET @rc = 4

           --Note: we will get error no. 9617 if the service queue 'ExternalMailQueue' is currently disabled.
           BREAK
        END

       --If there is no message in the queue return 1 to indicate a timeout
        IF NOT EXISTS(SELECT * FROM @msgs)
        BEGIN
          SET @rc = 1
          BREAK
        END

        -- Create a cursor to iterate through the messages.
        DECLARE msgs_cursor CURSOR FOR
        SELECT conversation_handle, 
            service_contract_name, 
            message_type_name,
            CONVERT(VARCHAR(MAX), message_body)
        FROM @msgs;

        -- Open the cursor
        OPEN msgs_cursor;

        -- Perform the first fetch and store the values in the variables.
        FETCH NEXT FROM msgs_cursor
        INTO 
            @conversation_handle,
            @service_contract_name,
            @message_type_name,
            @xml_message_body

        -- Check @@FETCH_STATUS to see if there are any more rows to fetch.
        WHILE (@@FETCH_STATUS = 0)
        BEGIN
            -- Check if the message is a send mail message
            IF(@message_type_name = N'{//www.microsoft.com/databasemail/messages}SendMail')
            BEGIN
                -- Get the handle to the xml document
                EXEC @rc = sp_xml_preparedocument 
                                @idoc OUTPUT, 
                                @xml_message_body, 
                                N'<ns xmlns:requests="http://schemas.microsoft.com/databasemail/requests" />'
                IF(@rc <> 0)
                BEGIN
                   IF(@rc < 4) -- make sure return code is not in reserved rang (1-3)
                      SET @rc = 4
                END
                ELSE -- parse the document and process its contents
                BEGIN
                   -- Execute a SELECT statement that uses the OPENXML rowset provider to get the MailItemId.
                   SELECT @mailitem_id = MailItemId
                   FROM OPENXML (@idoc, '/requests:SendMail', 1)
                        WITH (MailItemId INT './MailItemId')

                   --Close the handle to the xml document
                   EXEC sp_xml_removedocument @idoc

                   -- get account information 
                   SELECT @profile_id = profile_id
                   FROM sysmail_mailitems 
                   WHERE mailitem_id = @mailitem_id

                   IF(@profile_id IS NULL) -- mail item has been deleted from the database
                   BEGIN
                      -- log warning
                      SET @localmessage = FORMATMESSAGE(14667, convert(NVARCHAR(50), @mailitem_id))
                      exec msdb.dbo.sysmail_logmailevent_sp @event_type=2, @description=@localmessage

                      -- Resource clean-up
                      IF(@conversation_handle IS NOT NULL)
                         END CONVERSATION @conversation_handle;
                   
                      -- return code has special meaning and will be propagated to the calling process
                      SET @rc = 2
                   END
                   ELSE
                   BEGIN
                      -- This returns the mail item to the client as multiple result sets
                      EXEC sp_MailItemResultSets
                           @mailitem_id            = @mailitem_id,
                           @profile_id             = @profile_id,
                           @conversation_handle    = @conversation_handle,
                           @service_contract_name  = @service_contract_name,
                           @message_type_name      = @message_type_name
                
                      SET @exit = 1 -- make sure we exit outer loop
                   END
                END

                -- always break from the loop upon processing SendMail message
                BREAK
            END
            -- Check if the message is a dialog timer. This is used for account retries
            ELSE IF(@message_type_name = N'http://schemas.microsoft.com/SQL/ServiceBroker/DialogTimer')
            BEGIN
                -- Handle the retry case. - DialogTimer is used for send mail reties
                EXEC @rc = sp_process_DialogTimer
                            @conversation_handle    = @conversation_handle,
                            @service_contract_name  = @service_contract_name,
                            @message_type_name      = N'{//www.microsoft.com/databasemail/messages}SendMail'

                -- Always break from the loop upon processing DialogTimer message
                -- In case of failure return code from procedure call will simply be propagated to the calling process
                SET @exit = 1 -- make sure we exit outer loop
                BREAK
            END
            -- Error case
            ELSE IF (@message_type_name = 'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
            -- Error in the conversation, hence ignore all the messages of this conversation.
                BREAK

            -- This is executed as long as fetch succeeds.
            FETCH NEXT FROM msgs_cursor
            INTO 
                @conversation_handle,
                @service_contract_name,
                @message_type_name,
                @xml_message_body
        END

        CLOSE msgs_cursor;
        DEALLOCATE msgs_cursor;

        -- Check if we read any request or only SSB generated messages
        -- If a valid request is read with or without an error break out of loop
        IF (@exit = 1 OR @rc <> 0)
            BREAK

       --Keep track of how long this sp has been running
        select @timediff = DATEDIFF(ms, @start_time, getdate()) 
    END

    -- propagate return code to the calling process
    RETURN @rc
END
GO


-----------------------------------------------------------
-- procedure sp_GetAttachmentData
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_GetAttachmentData', 'P') IS NULL
    DROP PROCEDURE dbo.sp_GetAttachmentData
GO
-----
PRINT 'Creating sp_GetAttachmentData'
-----
GO

CREATE PROCEDURE sp_GetAttachmentData
   @attachments           nvarchar(max),
   @temp_table_uid        uniqueidentifier,
   @exclude_query_output  BIT        = 0
AS
BEGIN
    SET NOCOUNT ON
    SET QUOTED_IDENTIFIER ON

    DECLARE @rc             INT,
            @prohibitedExts NVARCHAR(1000),
            @attachFilePath NVARCHAR(260),
            @scIndex        INT,
            @startLocation  INT,
            @fileSizeStr    NVARCHAR(256),
            @fileSize       INT,
            @mailDbName     sysname,
            @uidStr         VARCHAR(36)

    --Get the maximum file size allowed for attachments from sysmailconfig.
    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'MaxFileSize', 
                                                @parameter_value = @fileSizeStr OUTPUT
    --ConvertToInt will return the default if @fileSizeStr is null
    SET @fileSize = dbo.ConvertToInt(@fileSizeStr, 0x7fffffff, 100000)

    --May need this if attaching files
    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'ProhibitedExtensions', 
                                                @parameter_value = @prohibitedExts OUTPUT

    SET @mailDbName = DB_NAME()
    SET @uidStr = CONVERT(VARCHAR(36), @temp_table_uid)

   SET @attachments = @attachments + ';'
   SET @startLocation = 0
   SET @scIndex = CHARINDEX(';', @attachments, @startLocation)

   WHILE (@scIndex <> 0)
   BEGIN
      SET @attachFilePath = SUBSTRING(@attachments, @startLocation, (@scIndex - @startLocation))
      
      -- Make sure we have an attachment file name to work with, and that it hasn't been truncated
      IF (@scIndex - @startLocation > 260 )
      BEGIN
            RAISERROR(14628, 16, 1)
          RETURN 1 
      END

        IF ((@attachFilePath IS NULL) OR (LEN(@attachFilePath) = 0))
        BEGIN
            RAISERROR(14628, 16, 1)
          RETURN 1 
        END

      --Check if attachment ext is allowed 
      EXEC @rc = sp_isprohibited @attachFilePath, @prohibitedExts
      IF (@rc <> 0)
      BEGIN
          RAISERROR(14630, 16, 1, @attachFilePath, @prohibitedExts)
          RETURN 2
      END

        DECLARE  @no_output_int  INT
        SET @no_output_int         = CONVERT(int, @exclude_query_output)

        -- return code checked after select and delete calls
        EXEC @rc = master..xp_sysmail_attachment_load @message       = @mailDbName, 
                                                      @attachments      = @attachFilePath,
                                                      @subject       = @uidStr,
                                                      @max_attachment_size = @fileSize,
                                                      @no_output = @no_output_int
      IF (@rc <> 0)
            RETURN (@rc)
               
        --Get next substring index
      SET @startLocation = @scIndex + 1
      SET @scIndex = CHARINDEX(';', @attachments, @startLocation)

      IF (@scIndex = 0)
         BREAK
   END

    RETURN 0
END
GO


-----------------------------------------------------------
-- procedure sp_RunMailQuery
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_RunMailQuery', 'P') IS NULL
    DROP PROCEDURE dbo.sp_RunMailQuery
GO
-----
PRINT 'Creating sp_RunMailQuery'
-----
USE msdb
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO

CREATE PROCEDURE [dbo].[sp_RunMailQuery]
   @query                      NVARCHAR(max),
   @attach_results             BIT,
   @query_attachment_filename  NVARCHAR(260) = NULL,
   @no_output                  BIT,
   @query_result_header        BIT,
   @separator                  VARCHAR(1),
   @echo_error                 BIT,
   @dbuse                      sysname,
   @width                      INT,
   @temp_table_uid             uniqueidentifier,
   @query_no_truncate          BIT,
   @query_result_no_padding    BIT
AS
BEGIN
    SET NOCOUNT ON
    SET QUOTED_IDENTIFIER ON

    DECLARE @rc             INT,
            @prohibitedExts NVARCHAR(1000),
            @fileSizeStr    NVARCHAR(256),
            @fileSize       INT,
            @attach_res_int INT,
            @no_output_int  INT,
            @no_header_int  INT,
            @echo_error_int INT,
         @query_no_truncate_int INT,
         @query_result_no_padding_int   INT,
            @mailDbName     sysname,
            @uid            uniqueidentifier,
            @uidStr         VARCHAR(36)

    --
    --Get config settings and verify parameters
    --
    SET @query_attachment_filename = LTRIM(RTRIM(@query_attachment_filename))

    --Get the maximum file size allowed for attachments from sysmailconfig.
    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'MaxFileSize', 
                                                @parameter_value = @fileSizeStr OUTPUT
    --ConvertToInt will return the default if @fileSizeStr is null
    SET @fileSize = dbo.ConvertToInt(@fileSizeStr, 0x7fffffff, 100000)

    IF (@attach_results = 1)
    BEGIN
        --Need this if attaching the query
        EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'ProhibitedExtensions', 
                                                    @parameter_value = @prohibitedExts OUTPUT

        -- If attaching query results to a file and a filename isn't given create one
        IF ((@query_attachment_filename IS NOT NULL) AND (LEN(@query_attachment_filename) > 0))
        BEGIN 
          EXEC @rc = sp_isprohibited @query_attachment_filename, @prohibitedExts
          IF (@rc <> 0)
          BEGIN
              RAISERROR(14630, 16, 1, @query_attachment_filename, @prohibitedExts)
              RETURN 2
          END
        END
        ELSE
        BEGIN
            --If queryfilename is not specified, generate a random name (doesn't have to be unique)
           SET @query_attachment_filename = 'QueryResults' + CONVERT(varchar, ROUND(RAND() * 1000000, 0)) + '.txt'
        END
    END

    --Init variables used in the query execution
    SET @mailDbName = db_name()
    SET @uidStr = convert(varchar(36), @temp_table_uid)

    SET @attach_res_int        = CONVERT(int, @attach_results)
    SET @no_output_int         = CONVERT(int, @no_output)
    IF(@query_result_header = 0) SET @no_header_int  = 1 ELSE SET @no_header_int  = 0
    SET @echo_error_int        = CONVERT(int, @echo_error)
    SET @query_no_truncate_int = CONVERT(int, @query_no_truncate)
    SET @query_result_no_padding_int = CONVERT(int, @query_result_no_padding )

    EXEC @rc = master..xp_sysmail_format_query  
                @query        = @query,
                @message      = @mailDbName,
                    @subject     = @uidStr,
                    @dbuse       = @dbuse, 
                    @attachments = @query_attachment_filename,
                    @attach_results = @attach_res_int,
                    -- format params
                    @separator      = @separator,
                    @no_header      = @no_header_int,
                    @no_output      = @no_output_int,
                    @echo_error     = @echo_error_int,
                @max_attachment_size = @fileSize,
                    @width       = @width, 
                    @query_no_truncate = @query_no_truncate_int,
                    @query_result_no_padding    = @query_result_no_padding_int
   RETURN @rc
END
GO

-----------------------------------------------------------
-- procedure sp_validate_user, used by sp_send_dbmail
-----------------------------------------------------------
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_validate_user')))
  DROP PROCEDURE sp_validate_user
go
use msdb
GO
/****** Object:  StoredProcedure [dbo].sp_validate_user ********/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
CREATE PROCEDURE [dbo].[sp_validate_user]
    @send_request_user sysname,
    @user_sid varbinary(85) OUTPUT
  WITH EXECUTE AS 'dbo'
AS
BEGIN
    SET NOCOUNT ON
    -- And make sure ARITHABORT is on. This is the default for yukon DB's
    SET ARITHABORT ON

    declare @groupSid varbinary(85)
    declare @temp table
    ([account name] sysname, 
    [type] char(8),
    [privilege] char(9),
    [mapped login name] sysname,
    [permission path] sysname)

    SET @user_sid = NULL
    SET @groupSid = NULL

    -- Lookup the Windows Group membership, if any, that grants this
    -- user access to SQL Server. xp_logininfo may fail if the sql
    -- server service account cannot talk to the domain controller to
    -- validate the windows username, or it may fail if the
    -- @send_request_user is not a valid windows user or group name.
    BEGIN TRY 
        insert @temp exec master.dbo.xp_logininfo @send_request_user
        select @groupSid = suser_sid([permission path]) from @temp
    END TRY
    BEGIN CATCH
        RETURN 2
    END CATCH

    -- Lookup a default profile for the Group. If there is one,
    -- then use the group's mail profile.
    IF ((@groupSid IS NOT NULL) AND (EXISTS(SELECT * 
                                            FROM msdb.dbo.sysmail_principalprofile as pp
                                            WHERE (pp.is_default = 1) AND
                                            (pp.principal_sid = @groupSid))))
    BEGIN
        SET @user_sid = @groupSid
        RETURN 0
    END
    RETURN 1
END
GO



-----------------------------------------------------------
-- procedure sp_send_dbmail
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_send_dbmail', 'P') IS NULL
    DROP PROCEDURE dbo.sp_send_dbmail
GO
-----
PRINT 'Creating sp_send_dbmail'
-----
USE msdb
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
-- sp_send_dbmail : Sends a mail from Yukon outbox.
--
CREATE PROCEDURE [dbo].[sp_send_dbmail]
   @profile_name               sysname    = NULL,        
   @recipients                 VARCHAR(MAX)  = NULL, 
   @copy_recipients            VARCHAR(MAX)  = NULL,
   @blind_copy_recipients      VARCHAR(MAX)  = NULL,
   @subject                    NVARCHAR(255) = NULL,
   @body                       NVARCHAR(MAX) = NULL, 
   @body_format                VARCHAR(20)   = NULL, 
   @importance                 VARCHAR(6)    = 'NORMAL',
   @sensitivity                VARCHAR(12)   = 'NORMAL',
   @file_attachments           NVARCHAR(MAX) = NULL,  
   @query                      NVARCHAR(MAX) = NULL,
   @execute_query_database     sysname       = NULL,  
   @attach_query_result_as_file BIT          = 0,
   @query_attachment_filename  NVARCHAR(260) = NULL,  
   @query_result_header        BIT           = 1,
   @query_result_width         INT           = 256,            
   @query_result_separator     CHAR(1)       = ' ',
   @exclude_query_output       BIT           = 0,
   @append_query_error         BIT           = 0,
   @query_no_truncate          BIT           = 0,
   @query_result_no_padding    BIT           = 0,
   @mailitem_id               INT            = NULL OUTPUT,
   @from_address               VARCHAR(max)  = NULL,
   @reply_to                   VARCHAR(max)  = NULL
  WITH EXECUTE AS 'dbo'
AS
BEGIN
    SET NOCOUNT ON

    -- And make sure ARITHABORT is on. This is the default for yukon DB's
    SET ARITHABORT ON

    --Declare variables used by the procedure internally
    DECLARE @profile_id         INT,
            @temp_table_uid     uniqueidentifier,
            @sendmailxml        VARCHAR(max),
            @CR_str             NVARCHAR(2),
            @localmessage       NVARCHAR(255),
            @QueryResultsExist  INT,
            @AttachmentsExist   INT,
            @RetErrorMsg        NVARCHAR(4000), --Impose a limit on the error message length to avoid memory abuse 
            @rc                 INT,
            @procName           sysname,
            @trancountSave      INT,
            @tranStartedBool    INT,
            @is_sysadmin        BIT,
            @send_request_user  sysname,
            @database_user_id   INT,
            @sid                varbinary(85)

    -- Initialize 
    SELECT  @rc                 = 0,
            @QueryResultsExist  = 0,
            @AttachmentsExist   = 0,
            @temp_table_uid     = NEWID(),
            @procName           = OBJECT_NAME(@@PROCID),
            @tranStartedBool    = 0,
            @trancountSave      = @@TRANCOUNT,
            @sid                = NULL

    EXECUTE AS CALLER
       SELECT @is_sysadmin       = IS_SRVROLEMEMBER('sysadmin'),
              @send_request_user = SUSER_SNAME(),
              @database_user_id  = USER_ID()
    REVERT

    --Check if SSB is enabled in this database
    IF (ISNULL(DATABASEPROPERTYEX(DB_NAME(), N'IsBrokerEnabled'), 0) <> 1)
    BEGIN
       RAISERROR(14650, 16, 1)
       RETURN 1
    END

    --Report error if the mail queue has been stopped. 
    --sysmail_stop_sp/sysmail_start_sp changes the receive status of the SSB queue
    IF NOT EXISTS (SELECT * FROM sys.service_queues WHERE name = N'ExternalMailQueue' AND is_receive_enabled = 1)
    BEGIN
       RAISERROR(14641, 16, 1)
       RETURN 1
    END

    -- Get the relevant profile_id 
    --
    IF (@profile_name IS NULL)
    BEGIN
        -- Use the global or users default if profile name is not supplied
        SELECT TOP (1) @profile_id = pp.profile_id
        FROM msdb.dbo.sysmail_principalprofile as pp
        WHERE (pp.is_default = 1) AND
            (dbo.get_principal_id(pp.principal_sid) = @database_user_id OR pp.principal_sid = 0x00)
        ORDER BY dbo.get_principal_id(pp.principal_sid) DESC

        --Was a profile found
        IF(@profile_id IS NULL)
        BEGIN
            -- Try a profile lookup based on Windows Group membership, if any
            EXEC @rc = msdb.dbo.sp_validate_user @send_request_user, @sid OUTPUT
            IF (@rc = 0)
            BEGIN
                SELECT TOP (1) @profile_id = pp.profile_id
                FROM msdb.dbo.sysmail_principalprofile as pp
                WHERE (pp.is_default = 1) AND
                      (pp.principal_sid = @sid)
                ORDER BY dbo.get_principal_id(pp.principal_sid) DESC
            END

            IF(@profile_id IS NULL)
            BEGIN
                RAISERROR(14636, 16, 1)
                RETURN 1
            END
        END
    END
    ELSE
    BEGIN
        --Get primary account if profile name is supplied
        EXEC @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id = NULL, 
                         @profile_name = @profile_name, 
                         @allow_both_nulls = 0, 
                         @allow_id_name_mismatch = 0,
                         @profileid = @profile_id OUTPUT
        IF (@rc <> 0)
            RETURN @rc

        --Make sure this user has access to the specified profile.
        --sysadmins can send on any profiles
        IF ( @is_sysadmin <> 1)
        BEGIN
            --Not a sysadmin so check users access to profile
            iF NOT EXISTS(SELECT * 
                        FROM msdb.dbo.sysmail_principalprofile 
                        WHERE ((profile_id = @profile_id) AND
                                (dbo.get_principal_id(principal_sid) = @database_user_id OR principal_sid = 0x00)))
            BEGIN
                EXEC msdb.dbo.sp_validate_user @send_request_user, @sid OUTPUT
                IF(@sid IS NULL)
                BEGIN
                    RAISERROR(14607, -1, -1, 'profile')
                    RETURN 1
                END
            END
        END
    END

    --Attach results must be specified
    IF @attach_query_result_as_file IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'attach_query_result_as_file')
       RETURN 2
    END

    --No output must be specified
    IF @exclude_query_output IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'exclude_query_output')
       RETURN 3
    END

    --No header must be specified
    IF @query_result_header IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'query_result_header')
       RETURN 4
    END

    -- Check if query_result_separator is specifed
    IF @query_result_separator IS NULL OR DATALENGTH(@query_result_separator) = 0
    BEGIN
       RAISERROR(14618, 16, 1, 'query_result_separator')
       RETURN 5
    END

    --Echo error must be specified
    IF @append_query_error IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'append_query_error')
       RETURN 6
    END

    --@body_format can be TEXT (default) or HTML
    IF (@body_format IS NULL)
    BEGIN
       SET @body_format = 'TEXT'
    END
    ELSE
    BEGIN
       SET @body_format = UPPER(@body_format)

       IF @body_format NOT IN ('TEXT', 'HTML') 
       BEGIN
          RAISERROR(14626, 16, 1, @body_format)
          RETURN 13
       END
    END

    --Importance must be specified
    IF @importance IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'importance')
       RETURN 15
    END

    SET @importance = UPPER(@importance)

    --Importance must be one of the predefined values
    IF @importance NOT IN ('LOW', 'NORMAL', 'HIGH')
    BEGIN
       RAISERROR(14622, 16, 1, @importance)
       RETURN 16
    END

    --Sensitivity must be specified
    IF @sensitivity IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'sensitivity')
       RETURN 17
    END

    SET @sensitivity = UPPER(@sensitivity)

    --Sensitivity must be one of predefined values
    IF @sensitivity NOT IN ('NORMAL', 'PERSONAL', 'PRIVATE', 'CONFIDENTIAL')
    BEGIN
       RAISERROR(14623, 16, 1, @sensitivity)
       RETURN 18
    END

    --Message body cannot be null. Atleast one of message, subject, query,
    --attachments must be specified.
    IF( (@body IS NULL AND @query IS NULL AND @file_attachments IS NULL AND @subject IS NULL)
       OR
    ( (LEN(@body) IS NULL OR LEN(@body) <= 0)  
       AND (LEN(@query) IS NULL  OR  LEN(@query) <= 0)
       AND (LEN(@file_attachments) IS NULL OR LEN(@file_attachments) <= 0)
       AND (LEN(@subject) IS NULL OR LEN(@subject) <= 0)
    )
    )
    BEGIN
       RAISERROR(14624, 16, 1, '@body, @query, @file_attachments, @subject')
       RETURN 19
    END   
    ELSE
       IF @subject IS NULL OR LEN(@subject) <= 0
          SET @subject='SQL Server Message'

    --Recipients cannot be empty. Atleast one of the To, Cc, Bcc must be specified
    IF ( (@recipients IS NULL AND @copy_recipients IS NULL AND 
       @blind_copy_recipients IS NULL
        )     
       OR
        ( (LEN(@recipients) IS NULL OR LEN(@recipients) <= 0)
       AND (LEN(@copy_recipients) IS NULL OR LEN(@copy_recipients) <= 0)
       AND (LEN(@blind_copy_recipients) IS NULL OR LEN(@blind_copy_recipients) <= 0)
        )
    )
    BEGIN
       RAISERROR(14624, 16, 1, '@recipients, @copy_recipients, @blind_copy_recipients')
       RETURN 20
    END

    --If query is not specified, attach results and no header cannot be true.
    IF ( (@query IS NULL OR LEN(@query) <= 0) AND @attach_query_result_as_file = 1)
    BEGIN
       RAISERROR(14625, 16, 1)
       RETURN 21
    END

    --
    -- Execute Query if query is specified
    IF ((@query IS NOT NULL) AND (LEN(@query) > 0))
    BEGIN
        EXECUTE AS CALLER
        EXEC @rc = sp_RunMailQuery 
                    @query                     = @query,
               @attach_results            = @attach_query_result_as_file,
                    @query_attachment_filename = @query_attachment_filename,
               @no_output                 = @exclude_query_output,
               @query_result_header       = @query_result_header,
               @separator                 = @query_result_separator,
               @echo_error                = @append_query_error,
               @dbuse                     = @execute_query_database,
               @width                     = @query_result_width,
                @temp_table_uid            = @temp_table_uid,
            @query_no_truncate         = @query_no_truncate,
            @query_result_no_padding           = @query_result_no_padding
      -- This error indicates that query results size was over the configured MaxFileSize.
      -- Note, an error has already beed raised in this case
      IF(@rc = 101)
         GOTO ErrorHandler;
         REVERT
 
         -- Always check the transfer tables for data. They may also contain error messages
         -- Only one of the tables receives data in the call to sp_RunMailQuery
         IF(@attach_query_result_as_file = 1)
         BEGIN
             IF EXISTS(SELECT * FROM sysmail_attachments_transfer WHERE uid = @temp_table_uid)
            SET @AttachmentsExist = 1
         END
         ELSE
         BEGIN
             IF EXISTS(SELECT * FROM sysmail_query_transfer WHERE uid = @temp_table_uid AND uid IS NOT NULL)
            SET @QueryResultsExist = 1
         END

         -- Exit if there was an error and caller doesn't want the error appended to the mail
         IF (@rc <> 0 AND @append_query_error = 0)
         BEGIN
            --Error msg with be in either the attachment table or the query table 
            --depending on the setting of @attach_query_result_as_file
            IF(@attach_query_result_as_file = 1)
            BEGIN
               --Copy query results from the attachments table to mail body
               SELECT @RetErrorMsg = CONVERT(NVARCHAR(4000), attachment)
               FROM sysmail_attachments_transfer 
               WHERE uid = @temp_table_uid
            END
            ELSE
            BEGIN
               --Copy query results from the query table to mail body
               SELECT @RetErrorMsg = text_data 
               FROM sysmail_query_transfer 
               WHERE uid = @temp_table_uid
            END

            GOTO ErrorHandler;
         END
         SET @AttachmentsExist = @attach_query_result_as_file
    END
    ELSE
    BEGIN
        --If query is not specified, attach results cannot be true.
        IF (@attach_query_result_as_file = 1)
        BEGIN
           RAISERROR(14625, 16, 1)
           RETURN 21
        END
    END

    --Get the prohibited extensions for attachments from sysmailconfig.
    IF ((@file_attachments IS NOT NULL) AND (LEN(@file_attachments) > 0)) 
    BEGIN
        EXECUTE AS CALLER
        EXEC @rc = sp_GetAttachmentData 
                        @attachments = @file_attachments, 
                        @temp_table_uid = @temp_table_uid,
                        @exclude_query_output = @exclude_query_output
        REVERT
        IF (@rc <> 0)
            GOTO ErrorHandler;
        
        IF EXISTS(SELECT * FROM sysmail_attachments_transfer WHERE uid = @temp_table_uid)
            SET @AttachmentsExist = 1
    END

    -- Start a transaction if not already in one. 
    -- Note: For rest of proc use GOTO ErrorHandler for falures  
    if (@trancountSave = 0) 
       BEGIN TRAN @procName

    SET @tranStartedBool = 1

    -- Store complete mail message for history/status purposes  
    INSERT sysmail_mailitems
    (
       profile_id,   
       recipients,
       copy_recipients,
       blind_copy_recipients,
       subject,
       body, 
       body_format, 
       importance,
       sensitivity,
       file_attachments,  
       attachment_encoding,
       query,
       execute_query_database,
       attach_query_result_as_file,
       query_result_header,
       query_result_width,          
       query_result_separator,
       exclude_query_output,
       append_query_error,
       send_request_user,
       from_address,
       reply_to
    )
    VALUES
    (
       @profile_id,        
       @recipients, 
       @copy_recipients,
       @blind_copy_recipients,
       @subject,
       @body, 
       @body_format, 
       @importance,
       @sensitivity,
       @file_attachments,  
       'MIME',
       @query,
       @execute_query_database,  
       @attach_query_result_as_file,
       @query_result_header,
       @query_result_width,            
       @query_result_separator,
       @exclude_query_output,
       @append_query_error,
       @send_request_user,
       @from_address,
       @reply_to
    )

    SELECT @rc          = @@ERROR,
           @mailitem_id = SCOPE_IDENTITY()

    IF(@rc <> 0)
        GOTO ErrorHandler;

    --Copy query into the message body
    IF(@QueryResultsExist = 1)
    BEGIN
      -- if the body is null initialize it
        UPDATE sysmail_mailitems
        SET body = N''
        WHERE mailitem_id = @mailitem_id
        AND body is null

        --Add CR, a \r followed by \n, which is 0xd and then 0xa
        SET @CR_str = CHAR(13) + CHAR(10)
        UPDATE sysmail_mailitems
        SET body.WRITE(@CR_str, NULL, NULL)
        WHERE mailitem_id = @mailitem_id

   --Copy query results to mail body
        UPDATE sysmail_mailitems
        SET body.WRITE( (SELECT text_data from sysmail_query_transfer WHERE uid = @temp_table_uid), NULL, NULL )
        WHERE mailitem_id = @mailitem_id

    END

    --Copy into the attachments table
    IF(@AttachmentsExist = 1)
    BEGIN
        --Copy temp attachments to sysmail_attachments      
        INSERT INTO sysmail_attachments(mailitem_id, filename, filesize, attachment)
        SELECT @mailitem_id, filename, filesize, attachment
        FROM sysmail_attachments_transfer
        WHERE uid = @temp_table_uid
    END

    -- Create the primary SSB xml maessage
    SET @sendmailxml = '<requests:SendMail xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.microsoft.com/databasemail/requests RequestTypes.xsd" xmlns:requests="http://schemas.microsoft.com/databasemail/requests"><MailItemId>'
                        + CONVERT(NVARCHAR(20), @mailitem_id) + N'</MailItemId></requests:SendMail>'

    -- Send the send request on queue.
    EXEC @rc = sp_SendMailQueues @sendmailxml
    IF @rc <> 0
    BEGIN
       RAISERROR(14627, 16, 1, @rc, 'send mail')
       GOTO ErrorHandler;
    END

    -- Print success message if required
    IF (@exclude_query_output = 0)
    BEGIN
       SET @localmessage = FORMATMESSAGE(14635)
       PRINT @localmessage
    END  

    --
    -- See if the transaction needs to be commited
    --
    IF (@trancountSave = 0 and @tranStartedBool = 1)
       COMMIT TRAN @procName

    -- All done OK
    goto ExitProc;

    -----------------
    -- Error Handler
    -----------------
ErrorHandler:
    IF (@tranStartedBool = 1) 
       ROLLBACK TRAN @procName

    ------------------
    -- Exit Procedure
    ------------------
ExitProc:
   
    --Always delete query and attactment transfer records. 
   --Note: Query results can also be returned in the sysmail_attachments_transfer table
    DELETE sysmail_attachments_transfer WHERE uid = @temp_table_uid
    DELETE sysmail_query_transfer WHERE uid = @temp_table_uid

   --Raise an error it the query execution fails
   -- This will only be the case when @append_query_error is set to 0 (false)
   IF( (@RetErrorMsg IS NOT NULL) AND (@exclude_query_output=0) )
   BEGIN
      RAISERROR(14661, -1, -1, @RetErrorMsg)
   END

    RETURN (@rc)
END
GO

-----------------------------------------------------------
-- procedure sp_ExternalMailQueueListener
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_ExternalMailQueueListener', 'P') IS NULL
    DROP PROCEDURE dbo.sp_ExternalMailQueueListener
GO
-----
PRINT 'Creating sp_ExternalMailQueueListener'
-----
USE [msdb]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF

GO
-- Processes messages from the external mail queue
--
CREATE PROCEDURE [dbo].[sp_ExternalMailQueueListener]
AS
BEGIN
    DECLARE 
        @idoc               INT,
        @mailitem_id        INT,
        @sent_status        INT,
        @sent_account_id    INT,
        @rc                 INT,
        @processId          INT,
        @sent_date          DATETIME,
        @localmessage       NVARCHAR(max),
        @conv_handle        uniqueidentifier,
       @message_type_name  NVARCHAR(256),
       @xml_message_body   NVARCHAR(max),
        @LogMessage         NVARCHAR(max)

    -- Table to store message information.
    DECLARE @msgs TABLE
    (
        [conversation_handle]   uniqueidentifier,
       [message_type_name]     nvarchar(256),
       [message_body]          varbinary(max)
    )

    --RECEIVE messages from the external queue. 
    --MailItem status messages are sent from the external sql mail process along with other SSB notifications and errors
    ;RECEIVE conversation_handle, message_type_name, message_body FROM InternalMailQueue INTO @msgs
    -- Check if there was some error in reading from queue
    SET @rc = @@ERROR
    IF (@rc <> 0)
    BEGIN
        --Log error and continue. Don't want to block the following messages on the queue
        SET @localmessage = FORMATMESSAGE(@@ERROR)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage

        GOTO ErrorHandler;
    END
   
    -----------------------------------
    --Process sendmail status messages
    SELECT 
        @conv_handle        = conversation_handle,
        @message_type_name  = message_type_name, 
        @xml_message_body   = CAST(message_body AS NVARCHAR(MAX))
    FROM @msgs 
    WHERE [message_type_name] = N'{//www.microsoft.com/databasemail/messages}SendMailStatus'

    IF(@message_type_name IS NOT NULL)
    BEGIN
        --
        --Expecting the xml body to be n the following form:
        --
        --<?xml version="1.0" encoding="utf-16"?>
        --<responses:SendMail xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.microsoft.com/databasemail/responses ResponseTypes.xsd" xmlns:responses="http://schemas.microsoft.com/databasemail/responses">
        --<Information>
        --    <Failure Message="THe error message...."/>
        --</Information>
        --<MailItemId Id="1" />
        --<SentStatus Status="3" />
        --<SentAccountId Id="0" />
        --<SentDate Date="2005-03-30T14:55:13" />
        --<CallingProcess Id="3012" />
        --</responses:SendMail>

        -- Get the handle to the xml document
        EXEC @rc = sp_xml_preparedocument 
                        @idoc OUTPUT, 
                        @xml_message_body, 
                        N'<ns xmlns:responses="http://schemas.microsoft.com/databasemail/responses" />'
        IF(@rc <> 0)
        BEGIN
            --Log error and continue. Don't want to block the following messages on the queue
            SET @localmessage = FORMATMESSAGE(14655, CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
            exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage

            GOTO ErrorHandler;
        END

        -- Execute a SELECT statement that uses the OPENXML rowset provider to get the MailItemId and sent status.
        SELECT @mailitem_id     = MailItemId, 
               @sent_status     = SentStatus,
               @sent_account_id = SentAccountId,
               @sent_date       = SentDate,
               @processId       = CallingProcess,
               @LogMessage      = LogMessage
        FROM OPENXML (@idoc, '/responses:SendMail', 1)
            WITH (MailItemId    INT      './MailItemId/@Id', 
                  SentStatus    INT      './SentStatus/@Status',
                  SentAccountId INT      './SentAccountId/@Id',
                  SentDate      DATETIME './SentDate/@Date', --The date was formated using ISO8601
                  CallingProcess INT     './CallingProcess/@Id', 
                  LogMessage     NVARCHAR(max) './Information/Failure/@Message')

        --Close the handle to the xml document
        EXEC sp_xml_removedocument @idoc

        IF(@mailitem_id IS NULL)
        BEGIN  
            --Log error and continue. Don't want to block the following messages on the queue by rolling back the tran
            SET @localmessage = FORMATMESSAGE(14652, CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
            exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage
        END      
        ELSE
        BEGIN
            -- check sent_status is valid : 0(PendingSend), 1(SendSuccessful), 2(SendFailed), 3(AttemptingSendRetry)
            IF(@sent_status NOT IN (1, 2, 3))
            BEGIN
                SET @localmessage = FORMATMESSAGE(14653, N'SentStatus', CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
                exec msdb.dbo.sysmail_logmailevent_sp @event_type=2, @description=@localmessage

                --Set value to SendFailed
                SET @sent_status = 2
            END

            --Make the @sent_account_id NULL if it is 0. 
            IF(@sent_account_id IS NOT NULL AND @sent_account_id = 0)
                SET @sent_account_id = NULL

            --
            -- Update the mail status if not a retry. Nothing else needs to be done in this case
            UPDATE sysmail_mailitems
            SET sent_status     = CAST (@sent_status as TINYINT),
                sent_account_id = @sent_account_id,
                sent_date       = @sent_date
            WHERE mailitem_id = @mailitem_id
        
            -- Report a failure if no record is found in the sysmail_mailitems table
            IF (@@ROWCOUNT = 0)
            BEGIN
                SET @localmessage = FORMATMESSAGE(14653, N'MailItemId', CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
                exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage
            END

            IF (@LogMessage IS NOT NULL)
            BEGIN
                exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@LogMessage, @process_id=@processId, @mailitem_id=@mailitem_id, @account_id=@sent_account_id
            END
        END
    END

    -------------------------------------------------------
    --Process all other messages by logging to sysmail_log
    SET @conv_handle = NULL;
    
    --Always end the conversion if this message is received
    SELECT @conv_handle = conversation_handle
    FROM @msgs 
    WHERE [message_type_name] = N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'
    
    IF(@conv_handle IS NOT NULL)
    BEGIN
        END CONVERSATION @conv_handle;
    END

    DECLARE @queuemessage nvarchar(max)
    DECLARE queue_messages_cursor CURSOR LOCAL 
    FOR
        SELECT FORMATMESSAGE(14654, CONVERT(NVARCHAR(50), conversation_handle), message_type_name, message_body)
        FROM @msgs 
        WHERE [message_type_name] 
              NOT IN (N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog',
                      N'{//www.microsoft.com/databasemail/messages}SendMailStatus')
  
    OPEN queue_messages_cursor 
    FETCH NEXT FROM queue_messages_cursor INTO @queuemessage
    WHILE (@@fetch_status = 0)
    BEGIN
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=2, @description=@queuemessage
        FETCH NEXT FROM queue_messages_cursor INTO @queuemessage
    END
    CLOSE queue_messages_cursor 
    DEALLOCATE queue_messages_cursor 

    -- All done OK
    goto ExitProc;

    -----------------
    -- Error Handler
    -----------------
ErrorHandler:

    ------------------
    -- Exit Procedure
    ------------------
ExitProc:
    RETURN (@rc)
END
GO

----------------------------------------------------------
-- procedure sp_sysmail_activate
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_sysmail_activate', 'P') IS NULL
    DROP PROCEDURE dbo.sp_sysmail_activate
GO

-----
PRINT 'Creating sp_sysmail_activate'
-----
USE [msdb]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON

GO
-- sp_sysmail_activate : Starts the DatabaseMail process if it isn't already running
--
CREATE PROCEDURE [dbo].[sp_sysmail_activate]
AS
BEGIN
    DECLARE @mailDbName sysname
    DECLARE @mailDbId INT
    DECLARE @mailEngineLifeMin INT
    DECLARE @loggingLevel nvarchar(256)
    DECLARE @loggingLevelInt int   
    DECLARE @parameter_value nvarchar(256)
    DECLARE @localmessage nvarchar(max)
    DECLARE @readFromConfigFile INT
    DECLARE @rc INT

    SET NOCOUNT ON
    EXEC sp_executesql @statement = N'RECEIVE TOP(0) * FROM msdb.dbo.ExternalMailQueue'

    EXEC @rc = msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'DatabaseMailExeMinimumLifeTime', 
                                                        @parameter_value = @parameter_value OUTPUT
    IF(@rc <> 0)
        RETURN (1)

    --ConvertToInt will return the default if @parameter_value is null or config value can't be converted
    --Setting max exe lifetime is 1 week (604800 secs). Can't see a reason for it to ever run longer that this
    SET @mailEngineLifeMin = dbo.ConvertToInt(@parameter_value, 604800, 600) 

    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'ReadFromConfigurationFile', 
                                                  @parameter_value = @parameter_value OUTPUT
    --Try to read the optional read from configuration file:
    SET @readFromConfigFile = dbo.ConvertToInt(@parameter_value, 1, 0) 

    --Try and get the optional logging level for the DatabaseMail process
    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'LoggingLevel', 
                                                  @parameter_value = @loggingLevel OUTPUT

    --Convert logging level into string value for passing into XP
    SET @loggingLevelInt = dbo.ConvertToInt(@loggingLevel, 3, 2) 
    IF @loggingLevelInt = 1
       SET @loggingLevel = 'Normal'
    ELSE IF @loggingLevelInt = 3
       SET @loggingLevel = 'Verbose'
    ELSE -- default
       SET @loggingLevel = 'Extended'

    SET @mailDbName = DB_NAME()
    SET @mailDbId   = DB_ID()

    EXEC @rc = master..xp_sysmail_activate @mailDbId, @mailDbName, @readFromConfigFile,
    @mailEngineLifeMin, @loggingLevel
    IF(@rc <> 0)
    BEGIN
        SET @localmessage = FORMATMESSAGE(14637)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage
    END
    ELSE
    BEGIN
        SET @localmessage = FORMATMESSAGE(14638)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=0, @description=@localmessage
    END

    RETURN @rc
END
GO

--------------------------------------------------------------
-- Database Mail roles and permissions
--------------------------------------------------------------

-- Create the DatabaseMailUserRole role
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysusers
            WHERE (name = N'DatabaseMailUserRole')
              AND (issqlrole = 1)))
BEGIN
  -- If there are no members in the role, then drop and re-create it
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.sysusers   su,
            msdb.dbo.sysmembers sm
       WHERE (su.uid = sm.groupuid)
         AND (su.name = N'DatabaseMailUserRole')
         AND (su.issqlrole = 1)) = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_droprole @rolename = N'DatabaseMailUserRole'
    EXECUTE msdb.dbo.sp_addrole @rolename = N'DatabaseMailUserRole'
  END
END
ELSE
  EXECUTE msdb.dbo.sp_addrole @rolename = N'DatabaseMailUserRole'  

go

GRANT EXECUTE   ON [dbo].[sp_send_dbmail]                TO DatabaseMailUserRole
                                                    
GRANT EXECUTE   ON [dbo].[sysmail_help_status_sp]        TO DatabaseMailUserRole
GRANT EXECUTE   ON [dbo].[sysmail_delete_mailitems_sp]   TO DatabaseMailUserRole

GRANT SELECT   ON [dbo].[sysmail_allitems]          TO DatabaseMailUserRole
GRANT SELECT   ON [dbo].[sysmail_sentitems]         TO DatabaseMailUserRole
GRANT SELECT   ON [dbo].[sysmail_unsentitems]       TO DatabaseMailUserRole
GRANT SELECT   ON [dbo].[sysmail_faileditems]       TO DatabaseMailUserRole

     
GRANT  SELECT   ON [dbo].[sysmail_mailattachments]  TO DatabaseMailUserRole
GRANT  SELECT   ON [dbo].[sysmail_event_log]        TO DatabaseMailUserRole

go

/*************************************************************************/
/*                                                                       */
/*  Database Mail SSB objects (Messages, Contracts, Queues, Services)    */
/*                                                                       */
/*************************************************************************/

PRINT ''
PRINT 'Dropping Database Mail MESSAGES, CONTRACTS, QUEUES AND SERVICES...'
PRINT ''

-- Drop service InternalMailService if existing.
IF EXISTS (SELECT * FROM sys.services WHERE name ='InternalMailService')
BEGIN
   PRINT 'Dropping SERVICE InternalMailService'
    DROP SERVICE InternalMailService;
END

-- Drop service ExternalMailService if existing.
IF EXISTS (SELECT * FROM sys.services WHERE name ='ExternalMailService')
BEGIN
   PRINT 'Dropping SERVICE ExternalMailService'
    DROP SERVICE ExternalMailService;
END

-- Drop queue InternalMailQueue if existing.
IF EXISTS (SELECT * FROM sys.objects WHERE name = 'InternalMailQueue' AND type = 'SQ')
BEGIN
   PRINT 'Dropping QUEUE InternalMailQueue'
    DROP QUEUE InternalMailQueue;
END    

-- Drop queue ExternalMailQueue if existing.
IF EXISTS (SELECT * FROM sys.objects WHERE name = 'ExternalMailQueue' AND type = 'SQ')
BEGIN
   PRINT 'Dropping QUEUE ExternalMailQueue'
    DROP QUEUE ExternalMailQueue;
END    

--Drop Notification service for activation of DatabaseMail.exe
IF EXISTS (SELECT * FROM sys.services WHERE name ='SQL/Notifications/SysMailNotification/v1.0')
BEGIN
   PRINT 'Dropping SERVICE [SQL/Notifications/SysMailNotification/v1.0]'
    DROP SERVICE [SQL/Notifications/SysMailNotification/v1.0];
END    

--Drop SysMailNotificationQueue if existing
IF EXISTS (SELECT * FROM sys.objects WHERE name = 'SysMailNotificationQueue' AND type = 'SQ')
BEGIN
   PRINT 'Dropping QUEUE SysMailNotificationQueue'
    DROP QUEUE SysMailNotificationQueue;
END    

-- Drop SendMail v1.0 contract if existing.
IF EXISTS(SELECT * FROM sys.service_contracts 
          WHERE name = '//www.microsoft.com/databasemail/contracts/SendMail/v1.0') 
BEGIN
   PRINT 'Dropping CONTRACT [//www.microsoft.com/databasemail/contracts/SendMail/v1.0]'          
   DROP CONTRACT [//www.microsoft.com/databasemail/contracts/SendMail/v1.0];
END

-- Drop SendMail message type if existing.
IF EXISTS(SELECT * FROM sys.service_message_types 
          WHERE name = '{//www.microsoft.com/databasemail/messages}SendMail')
BEGIN
   PRINT 'Dropping MESSAGE TYPE [{//www.microsoft.com/databasemail/messages}SendMail]'           
   DROP MESSAGE TYPE [{//www.microsoft.com/databasemail/messages}SendMail];
END   

-- Drop SendMailStatus message type if existing.
IF EXISTS(SELECT * FROM sys.service_message_types 
          WHERE name = '{//www.microsoft.com/databasemail/messages}SendMailStatus')
BEGIN
   PRINT 'Dropping MESSAGE TYPE [{//www.microsoft.com/databasemail/messages}SendMailStatus]'           
   DROP MESSAGE TYPE [{//www.microsoft.com/databasemail/messages}SendMailStatus];
END 

GO

-------------------------------------------------------------------
-- Create Database Mail MESSAGES, CONTRACTS, QUEUES AND SERVICES 
-------------------------------------------------------------------

PRINT ''
PRINT 'Creating MESSAGES, CONTRACTS, QUEUES AND SERVICES...'
PRINT ''

-- Create SendMail message type.
PRINT 'Creating MESSAGE TYPE [{//www.microsoft.com/databasemail/messages}SendMail]'

CREATE MESSAGE TYPE 
    [{//www.microsoft.com/databasemail/messages}SendMail] 
    VALIDATION = NONE 

CREATE MESSAGE TYPE 
    [{//www.microsoft.com/databasemail/messages}SendMailStatus]
    VALIDATION = NONE 

-- Create SendMail contract.
PRINT 'Creating CONTRACT [//www.microsoft.com/databasemail/contracts/SendMail/v1.0]'

CREATE CONTRACT [//www.microsoft.com/databasemail/contracts/SendMail/v1.0]
(
    [{//www.microsoft.com/databasemail/messages}SendMail]          SENT BY INITIATOR,
    [{//www.microsoft.com/databasemail/messages}SendMailStatus]    SENT BY TARGET
)

-- Create InternalMailQueue queue.
PRINT 'Creating QUEUE InternalMailQueue'

CREATE QUEUE InternalMailQueue
   WITH ACTIVATION (PROCEDURE_NAME = sp_ExternalMailQueueListener,                  
                    MAX_QUEUE_READERS = 1, 
                    EXECUTE AS SELF);

-- Create ExternalMailQueue queue.
PRINT 'Creating QUEUE ExternalMailQueue'

CREATE QUEUE ExternalMailQueue
   WITH ACTIVATION (PROCEDURE_NAME = sp_sysmail_activate,                  
                    MAX_QUEUE_READERS = 1, 
                    EXECUTE AS SELF);

-- Create InternalMailService service.
PRINT 'Creating SERVICE InternalMailService ON QUEUE InternalMailQueue'

CREATE SERVICE InternalMailService ON QUEUE InternalMailQueue
(
  [//www.microsoft.com/databasemail/contracts/SendMail/v1.0] 
 -- ,[//www.microsoft.com/databasemail/contracts/TestProfile/v1.0]
);

-- Create ExternalMailService service.
PRINT 'Creating SERVICE ExternalMailService ON QUEUE ExternalMailQueue'

CREATE SERVICE ExternalMailService ON QUEUE ExternalMailQueue
(
  [//www.microsoft.com/databasemail/contracts/SendMail/v1.0]
 -- ,[//www.microsoft.com/databasemail/contracts/TestProfile/v1.0]
);

GO

/**************************************************************/
/*                                                            */
/*  M  A  I  N  T  E  N  A  N  C  E    P  L  A  N  S          */
/*                                                            */
/**************************************************************/


/**************************************************************/
/* sp_maintplan_delete_log                                    */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_delete_log...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_delete_log')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_delete_log
go
CREATE PROCEDURE sp_maintplan_delete_log
    @plan_id        UNIQUEIDENTIFIER    = NULL,
    @subplan_id     UNIQUEIDENTIFIER    = NULL,
    @oldest_time    DATETIME            = NULL
AS
BEGIN
    -- @plan_id and @subplan_id must be both NULL or only one exclusively set
   IF (@plan_id IS NOT NULL) AND (@subplan_id IS NOT NULL)
   BEGIN
      RAISERROR(12980, -1, -1, '@plan_id', '@subplan_id')
      RETURN(1)
   END

   --Scenario 1: User wants to delete all logs
   --Scenario 2: User wants to delete all logs older than X date
   --Scenario 3: User wants to delete all logs for a given plan
   --Scenario 4: User wants to delete all logs for a specific subplan
   --Scenario 5: User wants to delete all logs for a given plan older than X date
   --Scenario 6: User wants to delete all logs for a specific subplan older than X date

   -- Special case 1: Delete all logs
   IF (@plan_id IS NULL) AND (@subplan_id IS NULL) AND (@oldest_time IS NULL)
   BEGIN
      DELETE msdb.dbo.sysmaintplan_logdetail
      DELETE msdb.dbo.sysmaintplan_log
      RETURN (0)
   END

   DELETE msdb.dbo.sysmaintplan_log 
    WHERE ( task_detail_id in 
            (SELECT task_detail_id 
             FROM msdb.dbo.sysmaintplan_log 
             WHERE ((@plan_id IS NULL)     OR (plan_id = @plan_id)) AND 
                   ((@subplan_id IS NULL)  OR (subplan_id = @subplan_id)) AND 
                   ((@oldest_time IS NULL) OR (start_time < @oldest_time))) )

    RETURN (0)
END
GO


/**************************************************************/
/* sp_maintplan_delete_subplan                                */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_delete_subplan...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_delete_subplan')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_delete_subplan
go
CREATE PROCEDURE sp_maintplan_delete_subplan
    @subplan_id       UNIQUEIDENTIFIER,
    @delete_jobs BIT                   = 1
AS
BEGIN

    DECLARE @retval     INT
    DECLARE @job        UNIQUEIDENTIFIER
    DECLARE @jobMsx     UNIQUEIDENTIFIER

    SET NOCOUNT ON
    SET @retval = 0

    -- Raise an error if the @subplan_id doesn't exist
    IF( NOT EXISTS(SELECT * FROM sysmaintplan_subplans WHERE subplan_id = @subplan_id))
    BEGIN
        DECLARE @subplan_id_as_char VARCHAR(36)
        SELECT @subplan_id_as_char = CONVERT(VARCHAR(36), @subplan_id)
        RAISERROR(14262, -1, -1, '@subplan_id', @subplan_id_as_char)
        RETURN(1)
    END


    BEGIN TRAN

    --Is there an Agent Job/Schedule associated with this subplan?
    SELECT @job = job_id, @jobMsx = msx_job_id
    FROM msdb.dbo.sysmaintplan_subplans 
    WHERE subplan_id = @subplan_id

    EXEC @retval = msdb.dbo.sp_maintplan_delete_log @subplan_id = @subplan_id
    IF (@retval <> 0)
    BEGIN
        ROLLBACK TRAN
        RETURN @retval
    END

    -- Delete the subplans table entry first since it has a foreign
    -- key constraint on its job_id existing in sysjobs.
    DELETE msdb.dbo.sysmaintplan_subplans 
    WHERE (subplan_id = @subplan_id)

    IF (@delete_jobs = 1)
    BEGIN
        --delete the local job associated with this subplan
        IF (@job IS NOT NULL)
        BEGIN
            EXEC @retval = msdb.dbo.sp_delete_job @job_id = @job, @delete_unused_schedule = 1
            IF (@retval <> 0)
            BEGIN
                ROLLBACK TRAN
                RETURN @retval
            END
        END

        --delete the multi-server job associated with this subplan.
        IF (@jobMsx IS NOT NULL)
        BEGIN 
            EXEC @retval = msdb.dbo.sp_delete_job @job_id = @jobMsx, @delete_unused_schedule = 1
            IF (@retval <> 0)
            BEGIN
                ROLLBACK TRAN
                RETURN @retval
            END
        END
    END

    COMMIT TRAN
    RETURN (0)
END
go

/**************************************************************/
/* SP_MAINTPLAN_UPDATE_SUBPLAN_TSX                            */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_update_subplan_tsx...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_update_subplan_tsx')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_update_subplan_tsx
go
-- This procedure is called when a maintenance plan subplan record
-- needs to be created or updated to match a multi-server Agent job
-- that has arrived from the master server.
CREATE PROCEDURE sp_maintplan_update_subplan_tsx
    @subplan_id    UNIQUEIDENTIFIER,
    @plan_id       UNIQUEIDENTIFIER,
    @name          sysname,
    @description   NVARCHAR(512),
    @job_id        UNIQUEIDENTIFIER
AS
BEGIN
    -- Find out what schedule, if any, is associated with the job.
    declare @schedule_id int
    select @schedule_id = (SELECT TOP(1) schedule_id
                           FROM msdb.dbo.sysjobschedules
                           WHERE (job_id = @job_id) )

    exec sp_maintplan_update_subplan @subplan_id, @plan_id, @name, @description, @job_id, @schedule_id, @allow_create=1

    -- Be sure to mark this subplan as coming from the master, not locally.
    update sysmaintplan_subplans
    set msx_plan = 1
    where subplan_id = @subplan_id
    
END
go

/**************************************************************/
/* SP_MAINTPLAN_SUBPLANS_BY_JOB                               */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_subplans_by_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_subplans_by_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_subplans_by_job
go
-- If the given job_id is associated with a maintenance plan,
-- then matching entries from sysmaintplan_subplans are returned.
CREATE PROCEDURE sp_maintplan_subplans_by_job
    @job_id  UNIQUEIDENTIFIER
AS
BEGIN
    select plans.name as 'plan_name', plans.id as 'plan_id', subplans.subplan_name, subplans.subplan_id
    from sysmaintplan_plans plans, sysmaintplan_subplans subplans
    where  plans.id = subplans.plan_id
    and (job_id = @job_id
         or msx_job_id = @job_id)
    order by subplans.plan_id, subplans.subplan_id
END
go


/**************************************************************/
/* sp_maintplan_open_logentry                                 */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_open_logentry...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_open_logentry')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_open_logentry
go
CREATE PROCEDURE sp_maintplan_open_logentry
    @plan_id       UNIQUEIDENTIFIER,
    @subplan_id       UNIQUEIDENTIFIER,   
    @start_time       DATETIME            = NULL,
    @task_detail_id  UNIQUEIDENTIFIER    = NULL OUTPUT
AS
BEGIN

   --Set defaults
   IF (@start_time IS NULL)
   BEGIN
      SELECT @start_time = GETDATE()
   END

   SELECT @task_detail_id = NEWID()

   --Insert a new record into sysmaintplan_log table
   INSERT INTO msdb.dbo.sysmaintplan_log(task_detail_id, plan_id, subplan_id, start_time)
    VALUES(@task_detail_id, @plan_id, @subplan_id, @start_time)

   RETURN (@@ERROR)
END
GO


/**************************************************************/
/* sp_maintplan_close_logentry                                */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_close_logentry...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_close_logentry')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_close_logentry
go
CREATE PROCEDURE sp_maintplan_close_logentry
    @task_detail_id     UNIQUEIDENTIFIER,
    @end_time          DATETIME            = NULL,
    @succeeded         TINYINT
AS
BEGIN

   --Set defaults
   IF (@end_time IS NULL)
   BEGIN
      SELECT @end_time = GETDATE()
   END

    -- Raise an error if the @task_detail_id doesn't exist
    IF( NOT EXISTS(SELECT * FROM sysmaintplan_log WHERE (task_detail_id = @task_detail_id)))
   BEGIN
        DECLARE @task_detail_id_as_char VARCHAR(36)
        SELECT @task_detail_id_as_char = CONVERT(VARCHAR(36), @task_detail_id)
        RAISERROR(14262, -1, -1, '@task_detail_id', @task_detail_id_as_char)
      RETURN(1)
   END

   UPDATE msdb.dbo.sysmaintplan_log 
    SET end_time = @end_time, succeeded = @succeeded 
    WHERE (task_detail_id = @task_detail_id)

    RETURN (@@ERROR)
END
go



/**************************************************************/
/* sp_maintplan_update_log                                    */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_update_log...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_update_log')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_update_log
go
CREATE PROCEDURE sp_maintplan_update_log
    --Updates the log_details table
    @task_detail_id      UNIQUEIDENTIFIER,       --Required
    @Line1              NVARCHAR(256),       --Required
    @Line2              NVARCHAR(256)   = NULL,
    @Line3              NVARCHAR(256)   = NULL,
    @Line4              NVARCHAR(256)   = NULL,
    @Line5              NVARCHAR(256)   = NULL,
    @server_name      sysname,            --Required
    @succeeded         TINYINT,           --Required
    @start_time           DATETIME,          --Required
    @end_time          DATETIME,          --Required
    @error_number     int=NULL,
    @error_message       NVARCHAR(max)   = NULL,
    @command           NVARCHAR(max)   = NULL
AS
BEGIN

   --Prep strings
   SET NOCOUNT ON
   SELECT @Line1 = LTRIM(RTRIM(@Line1))
   SELECT @Line2 = LTRIM(RTRIM(@Line2))
   SELECT @Line3 = LTRIM(RTRIM(@Line3))
   SELECT @Line4 = LTRIM(RTRIM(@Line4))
   SELECT @Line5 = LTRIM(RTRIM(@Line5))

   INSERT INTO msdb.dbo.sysmaintplan_logdetail(
        task_detail_id, 
        line1,
        line2, 
        line3, 
        line4, 
        line5, 
        server_name, 
        start_time, 
        end_time, 
        error_number, 
        error_message, 
        command, 
        succeeded)
   VALUES(
        @task_detail_id,
        @Line1,
        @Line2,
        @Line3,
        @Line4,
        @Line5,
        @server_name,
        @start_time,
        @end_time,
        @error_number,
        @error_message,
        @command,
        @succeeded)

    RETURN (@@ERROR)
END
GO



/**************************************************************/
/* sp_maintplan_update_subplan                                */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_update_subplan...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_update_subplan')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_update_subplan
go
CREATE PROCEDURE sp_maintplan_update_subplan
    @subplan_id       UNIQUEIDENTIFIER,
    @plan_id       UNIQUEIDENTIFIER    = NULL,
    @name          sysname             = NULL,
    @description  NVARCHAR(512)       = NULL,
    @job_id        UNIQUEIDENTIFIER    = NULL,
    @schedule_id  INT                 = NULL,
    @allow_create   BIT                 = 0,
    @msx_job_id    UNIQUEIDENTIFIER    = NULL
AS
BEGIN

   SET NOCOUNT ON

   SELECT @name = LTRIM(RTRIM(@name))
   SELECT @description = LTRIM(RTRIM(@description))

   --Are we creating a new entry or updating an existing one?

   IF( NOT EXISTS(SELECT * FROM msdb.dbo.sysmaintplan_subplans WHERE subplan_id = @subplan_id) )
   BEGIN
        -- Only allow creation of a record if user permits it
        IF(@allow_create = 0)
        BEGIN
            DECLARE @subplan_id_as_char VARCHAR(36)
            SELECT @subplan_id_as_char = CONVERT(VARCHAR(36), @subplan_id)
            RAISERROR(14262, -1, -1, '@subplan_id', @subplan_id_as_char)
          RETURN(1)
        END

        --Insert it's a new subplan
      IF (@name IS NULL)
      BEGIN
          RAISERROR(12981, -1, -1, '@name')
         RETURN(1) -- Failure
      END

      IF (@plan_id IS NULL)
      BEGIN
          RAISERROR(12981, -1, -1, '@plan_id')
         RETURN(1) -- Failure
      END

      INSERT INTO msdb.dbo.sysmaintplan_subplans(
            subplan_id,
            plan_id,
            subplan_description,
            subplan_name,
            job_id,
            schedule_id,
            msx_job_id)
      VALUES(
            @subplan_id,
            @plan_id,
            @description,
            @name,
            @job_id,
            @schedule_id,
            @msx_job_id)

   END
   ELSE
   BEGIN --Update the table

      DECLARE @s_subplan_name sysname
      DECLARE @s_job_id UNIQUEIDENTIFIER

      SELECT @s_subplan_name         = subplan_name,
            @s_job_id               = job_id
      FROM msdb.dbo.sysmaintplan_subplans
      WHERE (@subplan_id = subplan_id)

      --Determine if user wants to change these variables
      IF (@name IS NOT NULL)          SELECT @s_subplan_name          = @name
      IF (@job_id IS NOT NULL)        SELECT @s_job_id                = @job_id

      --UPDATE the record

      UPDATE msdb.dbo.sysmaintplan_subplans 
        SET subplan_name        = @s_subplan_name,
            subplan_description = @description,
            job_id              = @s_job_id,
            schedule_id         = @schedule_id,
            msx_job_id          = @msx_job_id
      WHERE (subplan_id = @subplan_id)

   END

    RETURN (@@ERROR)
END
GO


/**************************************************************/
/* sp_maintplan_delete_plan                                   */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_delete_plan...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_delete_plan')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_delete_plan
go
CREATE PROCEDURE sp_maintplan_delete_plan
    @plan_id   UNIQUEIDENTIFIER
AS
BEGIN
   SET NOCOUNT ON

   DECLARE @sp_id UNIQUEIDENTIFIER
    DECLARE @retval     INT

    SET @retval = 0

   --Loop through Subplans
   DECLARE sp CURSOR LOCAL FOR 
        SELECT subplan_id 
        FROM msdb.dbo.sysmaintplan_subplans 
        WHERE plan_id = @plan_id FOR READ ONLY

   OPEN sp
   FETCH NEXT FROM sp INTO @sp_id
   WHILE @@FETCH_STATUS = 0
   BEGIN 
     EXECUTE @retval = sp_maintplan_delete_subplan @subplan_id = @sp_id
      IF(@retval <> 0)
        BREAK

     FETCH NEXT FROM sp INTO @sp_id
   END
   CLOSE sp
   DEALLOCATE sp

    RETURN (@retval)
END
go



/**************************************************************/
/* sp_maintplan_start                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_start...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_start')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_start
go
CREATE PROCEDURE sp_maintplan_start
    @plan_id        UNIQUEIDENTIFIER    = NULL,
    @subplan_id     UNIQUEIDENTIFIER    = NULL
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @jobid  UNIQUEIDENTIFIER
    DECLARE @retval INT
    SET @retval = 0

    -- A @plan_id or @subplan_id must be supplied
   IF (@plan_id IS NULL) AND (@subplan_id IS NULL)
   BEGIN
      RAISERROR(12982, -1, -1, '@plan_id', '@subplan_id')
      RETURN(1)
   END

    -- either @plan_id or @subplan_id must be exclusively set
   IF (@plan_id IS NOT NULL) AND (@subplan_id IS NOT NULL)
   BEGIN
      RAISERROR(12982, -1, -1, '@plan_id', '@subplan_id')
      RETURN(1)
   END

    IF (@subplan_id IS NOT NULL)
    BEGIN 
        -- subplan_id supplied so simply start the subplan's job

        SELECT @jobid = job_id 
        FROM msdb.dbo.sysmaintplan_subplans 
        WHERE subplan_id = @subplan_id 

        if(@jobid IS NOT NULL)
        BEGIN
            EXEC @retval = msdb.dbo.sp_start_job @job_id = @jobid
        END

    END
    ELSE
    BEGIN
        -- Loop through Subplans and fire off all associated jobs
       DECLARE spj CURSOR LOCAL FOR 
            SELECT job_id
            FROM msdb.dbo.sysmaintplan_subplans 
            WHERE plan_id = @plan_id FOR READ ONLY

       OPEN spj
       FETCH NEXT FROM spj INTO @jobid
       WHILE (@@FETCH_STATUS = 0)
       BEGIN 
           EXEC @retval = msdb.dbo.sp_start_job @job_id = @jobid
            IF(@retval <> 0)
                BREAK

           FETCH NEXT FROM spj INTO @jobid
       END

       CLOSE spj
       DEALLOCATE spj

    END

    RETURN (@retval)
END
GO

/*==================================================================*/
--TODO: The following SYSDBMAINT... tables and SP's will be removed  
/*==================================================================*/


/**************************************************************/
/* SYSDBMAINTPLANS                                            */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysdbmaintplans')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysdbmaintplans...'

  CREATE TABLE sysdbmaintplans
  (
  plan_id                    UNIQUEIDENTIFIER NOT NULL PRIMARY KEY CLUSTERED,
  plan_name                  sysname          NOT NULL,
  date_created               DATETIME         NOT NULL DEFAULT (GETDATE()),
  owner                      sysname          NOT NULL DEFAULT (ISNULL(NT_CLIENT(), SUSER_SNAME())),
  max_history_rows           INT              NOT NULL DEFAULT (0),
  remote_history_server      sysname          NOT NULL DEFAULT (''),
  max_remote_history_rows    INT              NOT NULL DEFAULT (0),
  user_defined_1             INT              NULL,
  user_defined_2             NVARCHAR(100)    NULL,
  user_defined_3             DATETIME         NULL,
  user_defined_4             UNIQUEIDENTIFIER NULL
  )
END
go

-- Add row for "plan 0"
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysdbmaintplans
                WHERE (plan_id = CONVERT(UNIQUEIDENTIFIER, 0x00))))
  INSERT INTO sysdbmaintplans(plan_id, plan_name, owner) VALUES (0x00, N'All ad-hoc plans', N'sa')
go

/**************************************************************/
/* SYSDBMAINTPLAN_JOBS                                        */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysdbmaintplan_jobs')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysdbmaintplan_jobs...'

  CREATE TABLE sysdbmaintplan_jobs
  (
  plan_id UNIQUEIDENTIFIER NOT NULL UNIQUE CLUSTERED (plan_id, job_id)
                                    FOREIGN KEY REFERENCES msdb.dbo.sysdbmaintplans (plan_id),
  job_id  UNIQUEIDENTIFIER NOT NULL
  )
END
go

/**************************************************************/
/* SYSDBMAINTPLAN_DATABASES                                   */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysdbmaintplan_databases')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysdbmaintplan_databases...'

  CREATE TABLE sysdbmaintplan_databases
  (
  plan_id       UNIQUEIDENTIFIER NOT NULL UNIQUE CLUSTERED (plan_id, database_name)
                                          FOREIGN KEY REFERENCES msdb.dbo.sysdbmaintplans (plan_id),
  database_name sysname          NOT NULL
  )
END
go

/**************************************************************/
/* SYSDBMAINTPLAN_HISTORY                                     */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysdbmaintplan_history')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysdbmaintplan_history...'

  CREATE TABLE sysdbmaintplan_history
  (
  sequence_id    INT               NOT NULL IDENTITY UNIQUE NONCLUSTERED,
  plan_id        UNIQUEIDENTIFIER  NOT NULL DEFAULT('00000000-0000-0000-0000-000000000000'),
  plan_name      sysname           NOT NULL DEFAULT('All ad-hoc plans'),
  database_name  sysname           NULL,
  server_name    sysname           NOT NULL DEFAULT (CONVERT(sysname, ServerProperty('ServerName'))),
  activity       NVARCHAR(128)     NULL,
  succeeded      BIT               NOT NULL DEFAULT (1),
  end_time       DATETIME          NOT NULL DEFAULT (GETDATE()),
  duration       INT               NULL     DEFAULT (0),
  start_time     AS                DATEADD (ss, -duration, end_time),
  error_number   INT               NOT NULL DEFAULT (0),
  message        NVARCHAR(512)     NULL
  )

  CREATE CLUSTERED INDEX clust ON sysdbmaintplan_history(plan_id)
END
-- ALTER TABLE to correct default constraint 
ELSE
BEGIN
  DECLARE @t TABLE
  (
  constraint_type         NVARCHAR(146)  COLLATE database_default NULL,
  constraint_name         sysname        COLLATE database_default NULL,
  delete_action           NVARCHAR(20)   COLLATE database_default NULL,
  update_action           NVARCHAR(20)   COLLATE database_default NULL,
  status_enabled          NVARCHAR(20)   COLLATE database_default NULL,
  status_for_replication  NVARCHAR(20)   COLLATE database_default NULL,
  constraint_keys         NVARCHAR(2126) COLLATE database_default NULL
  )

  INSERT INTO @t EXEC sp_helpconstraint N'sysdbmaintplan_history', 'nomsg'

  DECLARE @constraint_name sysname
  DECLARE @sql NVARCHAR(4000)

  SELECT @constraint_name = constraint_name 
  FROM   @t 
  WHERE  constraint_type = N'DEFAULT on column server_name' 
  AND    constraint_keys = N'(@@servername)'

  -- default found
  IF (@constraint_name IS NOT NULL)
  BEGIN
    PRINT ''
    PRINT 'Alter sysdbmaintplan_history ...'
    SELECT @sql = N'ALTER TABLE sysdbmaintplan_history DROP CONSTRAINT ' + @constraint_name
    EXEC (@sql)

    ALTER TABLE sysdbmaintplan_history 
      ADD CONSTRAINT servername_default DEFAULT (CONVERT(sysname, ServerProperty('ServerName')))
      FOR server_name
  END
END
go

/**************************************************************/
/* SPs for the maintenance plans                              */
/**************************************************************/
/**************************************************************/
/* sp_clear_dbmaintplan_by_db                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_clear_dbmaintplan_by_db...'
GO
IF (EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sp_clear_dbmaintplan_by_db') AND (type = 'P')))
  DROP PROCEDURE sp_clear_dbmaintplan_by_db
GO
CREATE PROCEDURE sp_clear_dbmaintplan_by_db
  @db_name sysname
AS
BEGIN
  DECLARE planid_cursor CURSOR
  FOR
  select plan_id from msdb.dbo.sysdbmaintplan_databases where database_name=@db_name
  OPEN planid_cursor
  declare @planid uniqueidentifier
  FETCH NEXT FROM planid_cursor INTO @planid
  WHILE (@@FETCH_STATUS <> -1)
  BEGIN
    IF (@@FETCH_STATUS <> -2)
    BEGIN
      delete from msdb.dbo.sysdbmaintplan_databases where plan_id=@planid AND database_name=@db_name
      if (NOT EXISTS(select * from msdb.dbo.sysdbmaintplan_databases where plan_id=@planid))
      BEGIN
        --delete the job
        DECLARE jobid_cursor CURSOR
        FOR
        select job_id from msdb.dbo.sysdbmaintplan_jobs where plan_id=@planid
        OPEN jobid_cursor
        DECLARE @jobid uniqueidentifier
        FETCH NEXT FROM jobid_cursor INTO @jobid
        WHILE (@@FETCH_STATUS <> -1)
        BEGIN
          if (@@FETCH_STATUS <> -2)
          BEGIN
            execute msdb.dbo.sp_delete_job @jobid
          END
          FETCH NEXT FROM jobid_cursor into @jobid
        END
        CLOSE jobid_cursor
        DEALLOCATE jobid_cursor
        --delete the history
        delete from msdb.dbo.sysdbmaintplan_history where plan_id=@planid
        --delete the plan
        delete from msdb.dbo.sysdbmaintplans where plan_id=@planid
      END
    END
    FETCH NEXT FROM planid_cursor INTO @planid
  END
  CLOSE planid_cursor
  DEALLOCATE planid_cursor
END
GO

/**************************************************************/
/* sp_add_maintenance_plan                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_maintenance_plan...'
GO
IF (EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sp_add_maintenance_plan') AND (type = 'P')))
  DROP PROCEDURE sp_add_maintenance_plan
GO
CREATE PROCEDURE sp_add_maintenance_plan
  @plan_name varchar(128),
  @plan_id   UNIQUEIDENTIFIER OUTPUT
AS
BEGIN
  IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysdbmaintplans
                WHERE plan_name=@plan_name))
    BEGIN
      SELECT @plan_id=NEWID()
      INSERT INTO msdb.dbo.sysdbmaintplans (plan_id, plan_name) VALUES (@plan_id, @plan_name)
    END
  ELSE
    BEGIN
      RAISERROR(14261,-1,-1,'@plan_name',@plan_name)
      RETURN(1) -- failure
    END
END
GO

/**************************************************************/
/* sp_delete_maintenance_plan                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_maintenance_plan...'
GO
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_maintenance_plan')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_maintenance_plan
GO
CREATE PROCEDURE sp_delete_maintenance_plan
  @plan_id UNIQUEIDENTIFIER
AS
BEGIN
  /*check if the plan_id is valid*/
  IF (NOT EXISTS(SELECT *
                 FROM sysdbmaintplans
                 WHERE plan_id=@plan_id))
  BEGIN
    DECLARE @syserr VARCHAR(100)
    SELECT @syserr=CONVERT(VARCHAR(100),@plan_id)
    RAISERROR(14262,-1,-1,'@plan_id',@syserr)
    RETURN(1)
  END
  /* clean the related records in sysdbmaintplan_database */
  DELETE FROM msdb.dbo.sysdbmaintplan_databases
  WHERE plan_id=@plan_id
  /* clean the related records in sysdbmaintplan_jobs*/
  DELETE FROM msdb.dbo.sysdbmaintplan_jobs
  WHERE plan_id=@plan_id
  /* clean sysdbmaintplans */
  DELETE FROM msdb.dbo.sysdbmaintplans
  WHERE  plan_id= @plan_id
END
GO

/**************************************************************/
/* sp_add_maintenance_plan_db                                 */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_add_maintenance_plan_db...'
GO
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_maintenance_plan_db')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_maintenance_plan_db
GO
CREATE PROCEDURE sp_add_maintenance_plan_db
  @plan_id UNIQUEIDENTIFIER,
  @db_name sysname
AS
BEGIN
  DECLARE @syserr VARCHAR(100)
  /*check if the plan_id is valid */
  IF (NOT EXISTS (SELECT plan_id
              FROM  msdb.dbo.sysdbmaintplans
              WHERE plan_id=@plan_id))
  BEGIN
    SELECT @syserr=CONVERT(VARCHAR(100),@plan_id)
    RAISERROR(14262,-1,-1,'@plan_id',@syserr)
    RETURN(1)
  END
  /*check if the database name is valid */
  IF (NOT EXISTS (SELECT name
              FROM master.dbo.sysdatabases
              WHERE name=@db_name))
   BEGIN
    RAISERROR(14262,-1,-1,'@db_name',@db_name)
    RETURN(1)
  END
  /*check if the (plan_id, database) pair already exists*/
  IF (EXISTS (SELECT *
              FROM sysdbmaintplan_databases
              WHERE plan_id=@plan_id AND database_name=@db_name))
  BEGIN
    SELECT @syserr=CONVERT(VARCHAR(100),@plan_id)+' + '+@db_name
    RAISERROR(14261,-1,-1,'@plan_id+@db_name',@syserr)
    RETURN(1)
  END
  INSERT INTO msdb.dbo.sysdbmaintplan_databases (plan_id,database_name) VALUES (@plan_id, @db_name)
END
GO

/**************************************************************/
/* sp_delete_maintenance_plan_db                              */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_delete_maintenance_plan_db...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_maintenance_plan_db')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_maintenance_plan_db
go
CREATE PROCEDURE sp_delete_maintenance_plan_db
  @plan_id uniqueidentifier,
  @db_name sysname
AS
BEGIN
  /*check if the (plan_id, db_name) exists in the table*/
  IF (NOT EXISTS(SELECT *
                 FROM msdb.dbo.sysdbmaintplan_databases
                 WHERE @plan_id=plan_id AND @db_name=database_name))
  BEGIN
    DECLARE @syserr VARCHAR(300)
    SELECT @syserr=CONVERT(VARCHAR(100),@plan_id)+' + '+@db_name
    RAISERROR(14262,-1,-1,'@plan_id+@db_name',@syserr)
    RETURN(1)
  END
  /*delete the pair*/
  DELETE FROM msdb.dbo.sysdbmaintplan_databases
  WHERE plan_id=@plan_id AND database_name=@db_name
END
GO

/**************************************************************/
/* sp_add_maintenance_plan_job                                */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_add_maintenance_plan_job...'
GO
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_maintenance_plan_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_maintenance_plan_job
GO
CREATE PROCEDURE sp_add_maintenance_plan_job
  @plan_id UNIQUEIDENTIFIER,
  @job_id  UNIQUEIDENTIFIER
AS
BEGIN
  DECLARE @syserr varchar(100)
  /*check if the @plan_id is valid*/
  IF (NOT EXISTS(SELECT plan_id
                 FROM msdb.dbo.sysdbmaintplans
                 WHERE plan_id=@plan_id))
  BEGIN
    SELECT @syserr=CONVERT(VARCHAR(100),@plan_id)
    RAISERROR(14262,-1,-1,'@plan_id',@syserr)
    RETURN(1)
  END
  /*check if the @job_id is valid*/
  IF (NOT EXISTS(SELECT job_id
                 FROM msdb.dbo.sysjobs
                 WHERE job_id=@job_id))
  BEGIN
    SELECT @syserr=CONVERT(VARCHAR(100),@job_id)
    RAISERROR(14262,-1,-1,'@job_id',@syserr)
    RETURN(1)
  END
  /*check if the job has at least one step calling xp_sqlmaint*/
  DECLARE @maxind INT
  SELECT @maxind=(SELECT MAX(CHARINDEX('xp_sqlmaint', command))
                FROM  msdb.dbo.sysjobsteps
                WHERE @job_id=job_id)
  IF (@maxind<=0)
  BEGIN
    /*print N'Warning: The job is not for maitenance plan.' -- will add the new sysmessage here*/
    SELECT @syserr=CONVERT(VARCHAR(100),@job_id)
    RAISERROR(14199,-1,-1,@syserr)
    RETURN(1)
  END
  INSERT INTO msdb.dbo.sysdbmaintplan_jobs(plan_id,job_id) VALUES (@plan_id, @job_id) --don't have to check duplicate here
END
GO

/**************************************************************/
/* sp_delete_maintenance_plan_job                             */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_delete_maintenance_plan_job...'
GO
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_maintenance_plan_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_maintenance_plan_job
GO
CREATE PROCEDURE sp_delete_maintenance_plan_job
  @plan_id uniqueidentifier,
  @job_id  uniqueidentifier
AS
BEGIN
  /*check if the (plan_id, job_id) exists*/
  IF (NOT EXISTS(SELECT *
                 FROM sysdbmaintplan_jobs
                 WHERE @plan_id=plan_id AND @job_id=job_id))
  BEGIN
    DECLARE @syserr VARCHAR(300)
    SELECT @syserr=CONVERT(VARCHAR(100),@plan_id)+' + '+CONVERT(VARCHAR(100),@job_id)
    RAISERROR(14262,-1,-1,'@plan_id+@job_id',@syserr)
    RETURN(1)
  END
  DELETE FROM msdb.dbo.sysdbmaintplan_jobs
  WHERE plan_id=@plan_id AND job_id=@job_id
END
GO

/**************************************************************/
/* sp_help_maintenance_plan                                   */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_help_maintenance_plan...'
GO
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_maintenance_plan')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_maintenance_plan
GO
CREATE PROCEDURE sp_help_maintenance_plan
  @plan_id UNIQUEIDENTIFIER = NULL
AS
BEGIN
  IF (@plan_id IS NOT NULL)
    BEGIN
      /*return the information about the plan itself*/
      SELECT *
      FROM msdb.dbo.sysdbmaintplans
      WHERE plan_id=@plan_id
      /*return the information about databases this plan defined on*/
      SELECT database_name
      FROM msdb.dbo.sysdbmaintplan_databases
      WHERE plan_id=@plan_id
      /*return the information about the jobs that relating to the plan*/
      SELECT job_id
      FROM msdb.dbo.sysdbmaintplan_jobs
      WHERE plan_id=@plan_id
    END
  ELSE
    BEGIN
      SELECT *
      FROM msdb.dbo.sysdbmaintplans
    END
END
GO

/**************************************************************/
/*                                                            */
/* B A C K U P  H I S T O R Y                                 */
/*                                                            */
/**************************************************************/
/**************************************************************/
/* sp_delete_database_backuphistory                           */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_database_backuphistory...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_database_backuphistory')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_database_backuphistory
go
CREATE   PROCEDURE sp_delete_database_backuphistory
  @database_name sysname
AS
BEGIN
  SET NOCOUNT ON

  DECLARE @backup_set_id TABLE      (backup_set_id INT)
  DECLARE @media_set_id TABLE       (media_set_id INT)
  DECLARE @restore_history_id TABLE (restore_history_id INT)

  INSERT INTO @backup_set_id (backup_set_id)
  SELECT DISTINCT backup_set_id
  FROM msdb.dbo.backupset
  WHERE database_name = @database_name

  INSERT INTO @media_set_id (media_set_id)
  SELECT DISTINCT media_set_id
  FROM msdb.dbo.backupset
  WHERE database_name = @database_name

  INSERT INTO @restore_history_id (restore_history_id)
  SELECT DISTINCT restore_history_id
  FROM msdb.dbo.restorehistory
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)

  BEGIN TRANSACTION

  DELETE FROM msdb.dbo.backupfile
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.backupfilegroup
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.restorefile
  WHERE restore_history_id IN (SELECT restore_history_id
                               FROM @restore_history_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.restorefilegroup
  WHERE restore_history_id IN (SELECT restore_history_id
                               FROM @restore_history_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.restorehistory
  WHERE restore_history_id IN (SELECT restore_history_id
                               FROM @restore_history_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.backupset
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE msdb.dbo.backupmediafamily
  FROM msdb.dbo.backupmediafamily bmf
  WHERE bmf.media_set_id IN (SELECT media_set_id
                             FROM @media_set_id)
    AND ((SELECT COUNT(*)
          FROM msdb.dbo.backupset
          WHERE media_set_id = bmf.media_set_id) = 0)
  IF (@@error > 0)
    GOTO Quit

  DELETE msdb.dbo.backupmediaset
  FROM msdb.dbo.backupmediaset bms
  WHERE bms.media_set_id IN (SELECT media_set_id
                             FROM @media_set_id)
    AND ((SELECT COUNT(*)
          FROM msdb.dbo.backupset
          WHERE media_set_id = bms.media_set_id) = 0)
  IF (@@error > 0)
    GOTO Quit

  COMMIT TRANSACTION
  RETURN

Quit:
  ROLLBACK TRANSACTION
END
go

/**************************************************************/
/* SP_DELETE_BACKUPHISTORY                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_backuphistory...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_backuphistory')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_backuphistory
go
CREATE   PROCEDURE sp_delete_backuphistory
  @oldest_date datetime
AS
BEGIN
  SET NOCOUNT ON

  DECLARE @backup_set_id TABLE      (backup_set_id INT)
  DECLARE @media_set_id TABLE       (media_set_id INT)
  DECLARE @restore_history_id TABLE (restore_history_id INT)

  INSERT INTO @backup_set_id (backup_set_id)
  SELECT DISTINCT backup_set_id
  FROM msdb.dbo.backupset
  WHERE backup_finish_date < @oldest_date

  INSERT INTO @media_set_id (media_set_id)
  SELECT DISTINCT media_set_id
  FROM msdb.dbo.backupset
  WHERE backup_finish_date < @oldest_date

  INSERT INTO @restore_history_id (restore_history_id)
  SELECT DISTINCT restore_history_id
  FROM msdb.dbo.restorehistory
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)

  BEGIN TRANSACTION

  DELETE FROM msdb.dbo.backupfile
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.backupfilegroup
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.restorefile
  WHERE restore_history_id IN (SELECT restore_history_id
                               FROM @restore_history_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.restorefilegroup
  WHERE restore_history_id IN (SELECT restore_history_id
                               FROM @restore_history_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.restorehistory
  WHERE restore_history_id IN (SELECT restore_history_id
                               FROM @restore_history_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.backupset
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE msdb.dbo.backupmediafamily
  FROM msdb.dbo.backupmediafamily bmf
  WHERE bmf.media_set_id IN (SELECT media_set_id
                             FROM @media_set_id)
    AND ((SELECT COUNT(*)
          FROM msdb.dbo.backupset
          WHERE media_set_id = bmf.media_set_id) = 0)
  IF (@@error > 0)
    GOTO Quit

  DELETE msdb.dbo.backupmediaset
  FROM msdb.dbo.backupmediaset bms
  WHERE bms.media_set_id IN (SELECT media_set_id
                             FROM @media_set_id)
    AND ((SELECT COUNT(*)
          FROM msdb.dbo.backupset
          WHERE media_set_id = bms.media_set_id) = 0)
  IF (@@error > 0)
    GOTO Quit

  COMMIT TRANSACTION
  RETURN

Quit:
  ROLLBACK TRANSACTION

END
go




/**********************************************************************/
/* TABLE : log_shipping_primaries                                     */
/* Populated on the monitor server                                    */
/*                                                                    */
/**********************************************************************/

IF (NOT EXISTS (SELECT *
            FROM INFORMATION_SCHEMA.TABLES
            WHERE (TABLE_NAME = N'log_shipping_primaries')))
BEGIN
 PRINT ''
 PRINT 'Creating table log_shipping_primaries...'
 CREATE TABLE log_shipping_primaries
 (
  primary_id                   INT IDENTITY     NOT NULL PRIMARY KEY,
  primary_server_name          sysname          NOT NULL,
  primary_database_name        sysname          NOT NULL,
  maintenance_plan_id          UNIQUEIDENTIFIER NULL,
  backup_threshold             INT              NOT NULL,
  threshold_alert              INT              NOT NULL,
  threshold_alert_enabled      BIT              NOT NULL, /* 1 = enabled, 0 = disabled */
  last_backup_filename         NVARCHAR(500)    NULL,
  last_updated                 DATETIME         NULL,
  planned_outage_start_time    INT              NOT NULL,
  planned_outage_end_time      INT              NOT NULL,
  planned_outage_weekday_mask  INT              NOT NULL,
  source_directory             NVARCHAR(500)    NULL
 )
END
ELSE 
BEGIN
  IF (NOT EXISTS (SELECT * 
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE (TABLE_NAME = N'log_shipping_primaries')
      AND (COLUMN_NAME = N'source_directory')))

  BEGIN
    PRINT ''
    PRINT 'Adding columns to table log_shipping_primaries...'

    ALTER TABLE log_shipping_primaries
     ADD source_directory NVARCHAR(500) NULL

  END
END 
go

/**********************************************************************/
/* TABLE : log_shipping_secondaries                                   */
/* Populated on the monitor server                                    */
/*                                                                    */
/**********************************************************************/

IF (NOT EXISTS (SELECT *
            FROM INFORMATION_SCHEMA.TABLES
            WHERE (TABLE_NAME = N'log_shipping_secondaries')))
BEGIN
 PRINT ''
 PRINT 'Creating table log_shipping_secondaries...'
 CREATE TABLE log_shipping_secondaries
 (
  primary_id                   INT                FOREIGN KEY REFERENCES log_shipping_primaries (primary_id),
  secondary_server_name        sysname,
  secondary_database_name      sysname,
  last_copied_filename         NVARCHAR(500),
  last_loaded_filename         NVARCHAR(500),
  last_copied_last_updated     DATETIME,
  last_loaded_last_updated     DATETIME,
  secondary_plan_id            UNIQUEIDENTIFIER,
  copy_enabled                 BIT,
  load_enabled                 BIT,              /* 1 = load enabled, 0 = load disabled */
  out_of_sync_threshold        INT,
  threshold_alert              INT,
  threshold_alert_enabled      BIT,              /*1 = enabled, 0 = disabled */
  planned_outage_start_time    INT,
  planned_outage_end_time      INT,
  planned_outage_weekday_mask  INT,
  allow_role_change            BIT DEFAULT (0)
 )
END
ELSE 
BEGIN
  IF (NOT EXISTS (SELECT * 
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE (TABLE_NAME = N'log_shipping_secondaries')
      AND (COLUMN_NAME = N'allow_role_change')))

  BEGIN
    PRINT ''
    PRINT 'Adding columns to table log_shipping_secondaries...'

    ALTER TABLE log_shipping_secondaries
     ADD allow_role_change BIT DEFAULT (0)

  END
END 
go

/**************************************************************/
/* sp_add_log_shipping_monitor_jobs                           */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_add_log_shipping_monitor_jobs...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_add_log_shipping_monitor_jobs' AND type = N'P')  )
  drop procedure sp_add_log_shipping_monitor_jobs
go
CREATE PROCEDURE sp_add_log_shipping_monitor_jobs AS 
BEGIN
  SET NOCOUNT ON
  BEGIN TRANSACTION
  DECLARE @rv INT
  DECLARE @backup_job_name sysname
  SET @backup_job_name = N'Log Shipping Alert Job - Backup'
  IF (NOT EXISTS (SELECT * FROM msdb.dbo.sysjobs WHERE name = @backup_job_name))
  BEGIN
    EXECUTE @rv = msdb.dbo.sp_add_job @job_name = N'Log Shipping Alert Job - Backup'

    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error 

    EXECUTE @rv = msdb.dbo.sp_add_jobstep 
      @job_name = N'Log Shipping Alert Job - Backup', 
      @step_id = 1, 
      @step_name = N'Log Shipping Alert - Backup', 
      @command = N'EXECUTE msdb.dbo.sp_log_shipping_monitor_backup',
      @on_fail_action = 2, 
      @flags = 4, 
      @subsystem = N'TSQL', 
      @on_success_step_id = 0, 
      @on_success_action = 1, 
      @on_fail_step_id = 0
    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error 

   EXECUTE @rv = msdb.dbo.sp_add_jobschedule 
      @job_name = @backup_job_name, 
      @freq_type = 4, 
      @freq_interval = 1, 
      @freq_subday_type = 0x4, 
      @freq_subday_interval = 1, -- run every minute
      @freq_relative_interval = 0, 
      @name = @backup_job_name
    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error

   EXECUTE @rv = msdb.dbo.sp_add_jobserver @job_name = @backup_job_name, @server_name = NULL
    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error
  END

  DECLARE @restore_job_name sysname
  SET @restore_job_name = 'Log Shipping Alert Job - Restore'
  IF (NOT EXISTS (SELECT * FROM msdb.dbo.sysjobs WHERE name = @restore_job_name))
  BEGIN
    EXECUTE @rv = msdb.dbo.sp_add_job @job_name = @restore_job_name

    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error 

    EXECUTE @rv = msdb.dbo.sp_add_jobstep 
      @job_name = @restore_job_name, 
      @step_id = 1, 
      @step_name = @restore_job_name, 
      @command = N'EXECUTE msdb.dbo.sp_log_shipping_monitor_restore',
      @on_fail_action = 2, 
      @flags = 4, 
      @subsystem = N'TSQL', 
      @on_success_step_id = 0, 
      @on_success_action = 1, 
      @on_fail_step_id = 0
    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error 

    EXECUTE @rv = msdb.dbo.sp_add_jobschedule 
      @job_name = @restore_job_name, 
      @freq_type = 4, 
      @freq_interval = 1, 
      @freq_subday_type = 0x4, 
      @freq_subday_interval = 1, -- run every minute
      @freq_relative_interval = 0, 
      @name = @restore_job_name
    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error

    EXECUTE @rv = msdb.dbo.sp_add_jobserver @job_name = @restore_job_name, @server_name = NULL
    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error
  END
  COMMIT TRANSACTION
  RETURN

rollback_quit:
  ROLLBACK TRANSACTION
END
go

/**************************************************************/
/* sp_add_log_shipping_primary                                */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_add_log_shipping_primary...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_add_log_shipping_primary' AND type = N'P'))
  drop procedure sp_add_log_shipping_primary
go
CREATE PROCEDURE sp_add_log_shipping_primary
  @primary_server_name         sysname,
  @primary_database_name       sysname,
  @maintenance_plan_id         UNIQUEIDENTIFIER = NULL,
  @backup_threshold            INT              = 60,
  @threshold_alert             INT              = 14420,
  @threshold_alert_enabled     BIT              = 1,
  @planned_outage_start_time   INT              = 0,
  @planned_outage_end_time     INT              = 0,
  @planned_outage_weekday_mask INT              = 0,
  @primary_id              INT = NULL OUTPUT       
AS
BEGIN
  SET NOCOUNT ON
  IF EXISTS (SELECT * FROM msdb.dbo.log_shipping_primaries WHERE primary_server_name = @primary_server_name AND primary_database_name = @primary_database_name)
  BEGIN  
    DECLARE @pair_name NVARCHAR 
   SELECT @pair_name = @primary_server_name + N'.' + @primary_database_name
   RAISERROR (14261,16,1, N'primary_server_name.primary_database_name', @pair_name)
    RETURN (1) -- error
  END
  INSERT INTO msdb.dbo.log_shipping_primaries (
    primary_server_name,
    primary_database_name,
    maintenance_plan_id,
    backup_threshold,
    threshold_alert,
    threshold_alert_enabled,
    last_backup_filename,
    last_updated,
    planned_outage_start_time,
    planned_outage_end_time,
    planned_outage_weekday_mask,
    source_directory)  
  VALUES (@primary_server_name,  
    @primary_database_name, 
    @maintenance_plan_id, 
    @backup_threshold,
    @threshold_alert,
    @threshold_alert_enabled,
    N'first_file_000000000000.trn',
    GETDATE (),
    @planned_outage_start_time,
    @planned_outage_end_time,
    @planned_outage_weekday_mask,
    NULL)

  SELECT @primary_id = @@IDENTITY

  EXECUTE msdb.dbo.sp_add_log_shipping_monitor_jobs
END
go

/**************************************************************/
/* sp_add_log_shipping_secondary                              */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_add_log_shipping_secondary...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_add_log_shipping_secondary' AND type = N'P'))
  drop procedure sp_add_log_shipping_secondary
go
CREATE PROCEDURE sp_add_log_shipping_secondary
  @primary_id                  INT,
  @secondary_server_name       sysname,
  @secondary_database_name     sysname,
  @secondary_plan_id           UNIQUEIDENTIFIER,
  @copy_enabled                BIT              = 1,
  @load_enabled                BIT              = 1,
  @out_of_sync_threshold       INT              = 60,
  @threshold_alert             INT              = 14421,
  @threshold_alert_enabled     BIT              = 1,
  @planned_outage_start_time   INT              = 0,
  @planned_outage_end_time     INT              = 0,
  @planned_outage_weekday_mask INT              = 0,
  @allow_role_change           BIT              = 0 
AS
BEGIN
  SET NOCOUNT ON
  IF NOT EXISTS (SELECT * FROM msdb.dbo.log_shipping_primaries where primary_id = @primary_id)
  BEGIN
    RAISERROR (14262, 16, 1, N'primary_id', N'msdb.dbo.log_shipping_primaries')
    RETURN(1)
  END

  INSERT INTO msdb.dbo.log_shipping_secondaries (
    primary_id,
    secondary_server_name,
    secondary_database_name,
    last_copied_filename,
    last_loaded_filename,
    last_copied_last_updated,
    last_loaded_last_updated,
    secondary_plan_id,
    copy_enabled,
    load_enabled,
    out_of_sync_threshold,
    threshold_alert,
    threshold_alert_enabled,
    planned_outage_start_time,
    planned_outage_end_time,
    planned_outage_weekday_mask,
    allow_role_change)
   VALUES (@primary_id,
    @secondary_server_name,
    @secondary_database_name,
    N'first_file_000000000000.trn',
    N'first_file_000000000000.trn',
    GETDATE (),
    GETDATE (),
    @secondary_plan_id,
    @copy_enabled,
    @load_enabled,
    @out_of_sync_threshold,
    @threshold_alert,
    @threshold_alert_enabled,
    @planned_outage_start_time,
    @planned_outage_end_time,
    @planned_outage_weekday_mask,
    @allow_role_change)
END
go

/**************************************************************/
/* sp_delete_log_shipping_monitor_jobs                        */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_delete_log_shipping_monitor_jobs...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_delete_log_shipping_monitor_jobs' AND type = N'P')  )
  drop procedure sp_delete_log_shipping_monitor_jobs
go
CREATE PROCEDURE sp_delete_log_shipping_monitor_jobs AS
BEGIN
  DECLARE @backup_job_name sysname
  SET NOCOUNT ON
  SET @backup_job_name = N'Log Shipping Alert Job - Backup'
  IF (EXISTS (SELECT * FROM msdb.dbo.sysjobs WHERE name = @backup_job_name))
    EXECUTE msdb.dbo.sp_delete_job @job_name = N'Log Shipping Alert Job - Backup'

  DECLARE @restore_job_name sysname
  SET @restore_job_name = 'Log Shipping Alert Job - Restore'
  IF (EXISTS (SELECT * FROM msdb.dbo.sysjobs WHERE name = @restore_job_name))
    EXECUTE msdb.dbo.sp_delete_job @job_name = N'Log Shipping Alert Job - Restore'
END
go

/**************************************************************/
/* sp_delete_log_shipping_primary                             */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_delete_log_shipping_primary...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_delete_log_shipping_primary' AND type = N'P')  )
  drop procedure sp_delete_log_shipping_primary
go
CREATE PROCEDURE sp_delete_log_shipping_primary 
  @primary_server_name sysname,
  @primary_database_name sysname,
  @delete_secondaries BIT = 0
AS BEGIN
  DECLARE @primary_id INT

  SET NOCOUNT ON

  SELECT @primary_id = primary_id 
    FROM msdb.dbo.log_shipping_primaries 
    WHERE primary_server_name = @primary_server_name AND primary_database_name = @primary_database_name
  IF (@primary_id IS NULL)
    RETURN (0)

  BEGIN TRANSACTION
  IF (EXISTS (SELECT * FROM msdb.dbo.log_shipping_secondaries WHERE primary_id = @primary_id))
  BEGIN
    IF (@delete_secondaries = 0)
    BEGIN
      RAISERROR (14429,-1,-1)
      goto rollback_quit
    END
    DELETE FROM msdb.dbo.log_shipping_secondaries WHERE primary_id = @primary_id
    IF (@@ERROR <> 0)
      GOTO rollback_quit
  END
  DELETE FROM msdb.dbo.log_shipping_primaries WHERE primary_id = @primary_id
  IF (@@ERROR <> 0)
    GOTO rollback_quit

  COMMIT TRANSACTION
  DECLARE @i INT
  SELECT @i = COUNT(*) FROM msdb.dbo.log_shipping_primaries
  IF (@i=0)
    EXECUTE msdb.dbo.sp_delete_log_shipping_monitor_jobs
  RETURN (0)

rollback_quit:
  ROLLBACK TRANSACTION
  RETURN(1) -- error
END
go

/**************************************************************/
/* sp_delete_log_shipping_secondary                           */
/**************************************************************/
PRINT ''
PRINT 'Creating sp_delete_log_shipping_secondary...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_delete_log_shipping_secondary' AND type = N'P')  )
  drop procedure sp_delete_log_shipping_secondary
go
CREATE PROCEDURE sp_delete_log_shipping_secondary 
  @secondary_server_name   sysname,
  @secondary_database_name sysname
AS BEGIN
  SET NOCOUNT ON
  DELETE FROM msdb.dbo.log_shipping_secondaries WHERE 
    secondary_server_name   = @secondary_server_name AND
    secondary_database_name = @secondary_database_name
END
go

/**************************************************************/
/* sp_log_shipping_in_sync                                    */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_log_shipping_in_sync...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_log_shipping_in_sync' AND type = N'P')  )
  drop procedure sp_log_shipping_in_sync
go
CREATE PROCEDURE sp_log_shipping_in_sync
  @last_updated        DATETIME,
  @compare_with        DATETIME,
  @threshold           INT,
  @outage_start_time   INT,
  @outage_end_time     INT,
  @outage_weekday_mask INT,
  @enabled             BIT = 1,
  @delta               INT = NULL OUTPUT
AS BEGIN
  SET NOCOUNT ON
  DECLARE @cur_time INT

  SELECT @delta = DATEDIFF (mi, @last_updated, @compare_with)
  -- in sync
  IF (@delta <= @threshold)
    RETURN (0) -- in sync

  IF (@enabled = 0) 
    RETURN(0) -- in sync

  IF (@outage_weekday_mask & DATEPART(dw, GETDATE ()) > 0) -- potentially in outage window
  BEGIN
    SELECT @cur_time = DATEPART (hh, GETDATE()) * 10000 +
                       DATEPART (mi, GETDATE()) * 100 + 
                       DATEPART (ss, GETDATE())
     -- outage doesn't span midnight
    IF (@outage_start_time < @outage_end_time)
    BEGIN
      IF (@cur_time >= @outage_start_time AND @cur_time < @outage_end_time)
        RETURN(1) -- in outage
    END
     -- outage does span midnight
   ELSE IF (@outage_start_time > @outage_end_time)
   BEGIN
     IF (@cur_time >= @outage_start_time OR @cur_time < @outage_end_time)
       RETURN(1) -- in outage
   END
  END
  RETURN(-1 ) -- not in outage, not in sync
END
go

/**************************************************************/
/* sp_log_shipping_get_date_from_file                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_log_shipping_get_date_from_file...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_log_shipping_get_date_from_file' AND type = N'P')  )
  drop procedure sp_log_shipping_get_date_from_file
go
CREATE PROCEDURE sp_log_shipping_get_date_from_file 
  @db_name sysname,
  @filename NVARCHAR (500),
  @file_date DATETIME OUTPUT
AS
BEGIN
  SET NOCOUNT ON

  DECLARE @tempname NVARCHAR (500)
  IF (LEN (@filename) - (LEN(@db_name) + LEN ('_tlog_')) <= 0)
    RETURN(1) -- filename string isn't long enough
  SELECT @tempname = RIGHT (@filename, LEN (@filename) - (LEN(@db_name) + LEN ('_tlog_')))
  IF (CHARINDEX ('.',@tempname,0) > 0)
    SELECT @tempname = LEFT (@tempname, CHARINDEX ('.',@tempname,0) - 1)
  IF (LEN (@tempname) <>  8 AND LEN (@tempname) <> 12)
    RETURN (1) -- error must be yyyymmddhhmm or yyyymmdd
  IF (ISNUMERIC (@tempname) = 0 OR CHARINDEX ('.',@tempname,0) <> 0 OR CONVERT (FLOAT,SUBSTRING (@tempname, 1,8)) < 1 )
    RETURN (1) -- must be numeric, can't contain any '.' etc
  SELECT @file_date = CONVERT (DATETIME,SUBSTRING (@tempname, 1,8),112)
  IF (LEN (@tempname) = 12)
  BEGIN
    SELECT @file_date = DATEADD (hh, CONVERT (INT, SUBSTRING (@tempname,9,2)),@file_date)
    SELECT @file_date = DATEADD (mi, CONVERT (INT, SUBSTRING (@tempname,11,2)),@file_date)
  END
  RETURN (0) -- success
END
go

/**************************************************************/
/* sp_get_log_shipping_monitor_info                           */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_get_log_shipping_monitor_info...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_get_log_shipping_monitor_info' AND type = N'P')  )
  drop procedure sp_get_log_shipping_monitor_info
go
CREATE PROCEDURE sp_get_log_shipping_monitor_info
  @primary_server_name     sysname = N'%',
  @primary_database_name   sysname = N'%',
  @secondary_server_name   sysname = N'%',
  @secondary_database_name sysname = N'%'
AS BEGIN
  SET NOCOUNT ON
  DECLARE @lsp TABLE (
    primary_server_name            sysname       COLLATE database_default NOT NULL,
    primary_database_name          sysname       COLLATE database_default NOT NULL,
    secondary_server_name          sysname       COLLATE database_default NOT NULL,
    secondary_database_name        sysname       COLLATE database_default NOT NULL,
    backup_threshold               INT           NOT NULL,
    backup_threshold_alert         INT           NOT NULL,
    backup_threshold_alert_enabled BIT           NOT NULL,
    last_backup_filename           NVARCHAR(500) COLLATE database_default NOT NULL,
    last_backup_last_updated       DATETIME      NOT NULL,
    backup_outage_start_time       INT           NOT NULL,
    backup_outage_end_time         INT           NOT NULL,
    backup_outage_weekday_mask     INT           NOT NULL,
    backup_in_sync                 INT           NULL, -- 0 = in sync, -1 = out of sync, 1 = in outage window
    backup_delta                   INT           NULL,
    last_copied_filename           NVARCHAR(500) COLLATE database_default NOT NULL,
    last_copied_last_updated       DATETIME      NOT NULL,
    last_loaded_filename           NVARCHAR(500) COLLATE database_default NOT NULL,
    last_loaded_last_updated       DATETIME      NOT NULL,
    copy_delta                     INT           NULL,
    copy_enabled                   BIT           NOT NULL,
    load_enabled                   BIT           NOT NULL,
    out_of_sync_threshold          INT           NOT NULL,
    load_threshold_alert           INT           NOT NULL,
    load_threshold_alert_enabled   BIT           NOT NULL,
    load_outage_start_time         INT           NOT NULL,
    load_outage_end_time           INT           NOT NULL,
    load_outage_weekday_mask       INT           NOT NULL,
    load_in_sync                   INT           NULL, -- 0 = in sync, -1 = out of sync, 1 = in outage window
    load_delta                     INT           NULL,
    maintenance_plan_id             UNIQUEIDENTIFIER NULL,
    secondary_plan_id              UNIQUEIDENTIFIER NOT NULL)

  INSERT INTO @lsp

 SELECT
    primary_server_name,
    primary_database_name,
    secondary_server_name,
    secondary_database_name,
    backup_threshold,
    p.threshold_alert,
    p.threshold_alert_enabled,
    last_backup_filename,
    p.last_updated,
    p.planned_outage_start_time,
    p.planned_outage_end_time,
    p.planned_outage_weekday_mask,
    NULL,
    NULL,
    last_copied_filename,
    last_copied_last_updated,
    last_loaded_filename,
    last_loaded_last_updated,
    NULL,
    copy_enabled,
    load_enabled,
    out_of_sync_threshold,
    s.threshold_alert,
    s.threshold_alert_enabled,
    s.planned_outage_start_time,
    s.planned_outage_weekday_mask,
    s.planned_outage_end_time,
    NULL,
    NULL,
    maintenance_plan_id,
    secondary_plan_id
  FROM msdb.dbo.log_shipping_primaries p, msdb.dbo.log_shipping_secondaries s
  WHERE 
    p.primary_id = s.primary_id AND
    primary_server_name LIKE @primary_server_name AND
    primary_database_name LIKE @primary_database_name AND
    secondary_server_name LIKE @secondary_server_name AND
    secondary_database_name LIKE @secondary_database_name

  DECLARE @load_in_sync                   INT
  DECLARE @backup_in_sync                 INT
  DECLARE @_primary_server_name           sysname 
  DECLARE @_primary_database_name         sysname 
  DECLARE @_secondary_server_name         sysname
  DECLARE @_secondary_database_name       sysname
  DECLARE @last_loaded_last_updated       DATETIME
  DECLARE @last_loaded_filename           NVARCHAR (500)
  DECLARE @last_copied_filename           NVARCHAR (500)
  DECLARE @last_backup_last_updated       DATETIME
  DECLARE @last_backup_filename           NVARCHAR (500)
  DECLARE @backup_outage_start_time       INT
  DECLARE @backup_outage_end_time         INT
  DECLARE @backup_outage_weekday_mask     INT
  DECLARE @backup_threshold               INT
  DECLARE @backup_threshold_alert_enabled BIT
  DECLARE @load_outage_start_time         INT
  DECLARE @load_outage_end_time           INT
  DECLARE @load_outage_weekday_mask       INT
  DECLARE @load_threshold                 INT
  DECLARE @load_threshold_alert_enabled   BIT
  DECLARE @backupdt                       DATETIME
  DECLARE @restoredt                      DATETIME
  DECLARE @copydt                         DATETIME
  DECLARE @rv                             INT
  DECLARE @dt                             DATETIME
  DECLARE @copy_delta                     INT
  DECLARE @load_delta                     INT
  DECLARE @backup_delta                   INT
  DECLARE @last_copied_last_updated       DATETIME

  SELECT @dt = GETDATE ()

  DECLARE sync_update CURSOR FOR
    SELECT 
      primary_server_name, 
      primary_database_name, 
      secondary_server_name, 
      secondary_database_name,
      last_backup_filename,
      last_backup_last_updated,
      last_loaded_filename,
      last_loaded_last_updated,
      backup_outage_start_time,
      backup_outage_end_time,
      backup_outage_weekday_mask,
      backup_threshold,
      backup_threshold_alert_enabled,
      load_outage_start_time,
      load_outage_end_time,
      out_of_sync_threshold,
      load_outage_weekday_mask,
      load_threshold_alert_enabled,
      last_copied_filename,
      last_copied_last_updated
    FROM @lsp
    FOR READ ONLY

  OPEN sync_update

loop:
  FETCH NEXT FROM sync_update INTO
    @_primary_server_name, 
    @_primary_database_name, 
    @_secondary_server_name, 
    @_secondary_database_name,
    @last_backup_filename,
    @last_backup_last_updated,
    @last_loaded_filename,
    @last_loaded_last_updated,
    @backup_outage_start_time,
    @backup_outage_end_time,
    @backup_outage_weekday_mask,
    @backup_threshold,
    @backup_threshold_alert_enabled,
    @load_outage_start_time,
    @load_outage_end_time,
    @load_threshold,
    @load_outage_weekday_mask,
    @load_threshold_alert_enabled,
    @last_copied_filename,
    @last_copied_last_updated

  IF @@fetch_status <> 0
    GOTO _loop

  EXECUTE @rv = sp_log_shipping_get_date_from_file @_primary_database_name, @last_backup_filename, @backupdt OUTPUT
  IF (@rv <> 0)
    SELECT @backupdt = @last_backup_last_updated
  EXECUTE @rv = sp_log_shipping_get_date_from_file @_primary_database_name, @last_loaded_filename, @restoredt OUTPUT
  IF  (@rv <> 0)
    SELECT @restoredt = @last_loaded_last_updated
  EXECUTE @rv = sp_log_shipping_get_date_from_file @_primary_database_name, @last_copied_filename, @copydt OUTPUT
  IF  (@rv <> 0)
    SELECT @copydt = @last_copied_last_updated
  
  EXECUTE @load_in_sync = msdb.dbo.sp_log_shipping_in_sync
    @restoredt,
    @backupdt,
    @load_threshold,
    @load_outage_start_time,
    @load_outage_end_time,
    @load_outage_weekday_mask,
    @load_threshold_alert_enabled,
    @load_delta OUTPUT

  EXECUTE @backup_in_sync = msdb.dbo.sp_log_shipping_in_sync
    @last_backup_last_updated,
    @dt,
    @backup_threshold,
    @backup_outage_start_time,
    @backup_outage_end_time,
    @backup_outage_weekday_mask,
    @backup_threshold_alert_enabled,
    @backup_delta OUTPUT

  EXECUTE msdb.dbo.sp_log_shipping_in_sync
    @copydt,
    @backupdt,
    1,0,0,0,0,
    @copy_delta OUTPUT

  UPDATE @lsp 
  SET backup_in_sync = @backup_in_sync, load_in_sync  = @load_in_sync, 
    copy_delta = @copy_delta, load_delta = @load_delta, backup_delta = @backup_delta
  WHERE primary_server_name = @_primary_server_name AND
    secondary_server_name = @_secondary_server_name AND
    primary_database_name = @_primary_database_name AND
    secondary_database_name = @_secondary_database_name 
  GOTO loop
_loop:
  CLOSE sync_update
  DEALLOCATE sync_update
  SELECT * FROM @lsp
END
go

/**************************************************************/
/* sp_update_log_shipping_monitor_info                        */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_update_log_shipping_monitor_info...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_update_log_shipping_monitor_info' AND type = N'P')  )
  DROP PROCEDURE sp_update_log_shipping_monitor_info
go
CREATE PROCEDURE sp_update_log_shipping_monitor_info
  @primary_server_name                 sysname,
  @primary_database_name               sysname,
  @secondary_server_name               sysname,
  @secondary_database_name             sysname,
  @backup_threshold                    INT = NULL,
  @backup_threshold_alert              INT = NULL,
  @backup_threshold_alert_enabled      BIT = NULL,
  @backup_outage_start_time            INT = NULL,
  @backup_outage_end_time              INT = NULL,
  @backup_outage_weekday_mask          INT = NULL,
  @copy_enabled                        BIT = NULL,
  @load_enabled                        BIT = NULL,
  @out_of_sync_threshold               INT = NULL,
  @out_of_sync_threshold_alert         INT = NULL,
  @out_of_sync_threshold_alert_enabled BIT = NULL,
  @out_of_sync_outage_start_time       INT = NULL,
  @out_of_sync_outage_end_time         INT = NULL,
  @out_of_sync_outage_weekday_mask     INT = NULL
AS BEGIN
  SET NOCOUNT ON
  DECLARE @_backup_threshold                    INT
  DECLARE @_backup_threshold_alert              INT
  DECLARE @_backup_threshold_alert_enabled      BIT
  DECLARE @_backup_outage_start_time            INT
  DECLARE @_backup_outage_end_time              INT
  DECLARE @_backup_outage_weekday_mask          INT
  DECLARE @_copy_enabled                        BIT
  DECLARE @_load_enabled                        BIT
  DECLARE @_out_of_sync_threshold               INT
  DECLARE @_out_of_sync_threshold_alert         INT
  DECLARE @_out_of_sync_threshold_alert_enabled BIT
  DECLARE @_out_of_sync_outage_start_time       INT
  DECLARE @_out_of_sync_outage_end_time         INT
  DECLARE @_out_of_sync_outage_weekday_mask     INT

  -- check that the primary exists
  IF (NOT EXISTS (SELECT * FROM msdb.dbo.log_shipping_primaries WHERE primary_server_name = @primary_server_name AND primary_database_name = @primary_database_name))
  BEGIN
    DECLARE @pp sysname
    SELECT @pp = @primary_server_name + N'.' + @primary_database_name
    RAISERROR (14262, 16, 1, N'primary_server_name.primary_database_name', @pp)
    RETURN (1) -- error
  END

  -- check that the secondary exists
  IF (NOT EXISTS (SELECT * FROM msdb.dbo.log_shipping_secondaries WHERE secondary_server_name = @secondary_server_name AND secondary_database_name = @secondary_database_name))
  BEGIN
    DECLARE @sp sysname
    SELECT @sp = @secondary_server_name + N'.' + @secondary_database_name
    RAISERROR (14262, 16, 1, N'secondary_server_name.secondary_database_name', @sp)
    RETURN (1) -- error
  END

  -- load the original variables

 SELECT
    @_backup_threshold                    = backup_threshold,
    @_backup_threshold_alert              = p.threshold_alert,
    @_backup_threshold_alert_enabled      = p.threshold_alert_enabled,
    @_backup_outage_start_time            = p.planned_outage_start_time,
    @_backup_outage_end_time              = p.planned_outage_end_time,
    @_backup_outage_weekday_mask          = p.planned_outage_weekday_mask,
    @_copy_enabled                        = copy_enabled,
    @_load_enabled                        = load_enabled,
    @_out_of_sync_threshold               = out_of_sync_threshold,
    @_out_of_sync_threshold_alert         = s.threshold_alert,
    @_out_of_sync_threshold_alert_enabled = s.threshold_alert_enabled,
    @_out_of_sync_outage_start_time       = s.planned_outage_start_time,
    @_out_of_sync_outage_weekday_mask     = s.planned_outage_weekday_mask,
    @_out_of_sync_outage_end_time         = s.planned_outage_end_time
  FROM msdb.dbo.log_shipping_primaries p, msdb.dbo.log_shipping_secondaries s
  WHERE 
    p.primary_id            = s.primary_id           AND
    primary_server_name     = @primary_server_name   AND
    primary_database_name   = @primary_database_name AND
    secondary_server_name   = @secondary_server_name AND
    secondary_database_name = @secondary_database_name

  SELECT @_backup_threshold                    = ISNULL (@backup_threshold,                    @_backup_threshold)
  SELECT @_backup_threshold_alert              = ISNULL (@backup_threshold_alert,              @_backup_threshold_alert)
  SELECT @_backup_threshold_alert_enabled      = ISNULL (@backup_threshold_alert_enabled,      @_backup_threshold_alert_enabled)
  SELECT @_backup_outage_start_time            = ISNULL (@backup_outage_start_time,            @_backup_outage_start_time)
  SELECT @_backup_outage_end_time              = ISNULL (@backup_outage_end_time,              @_backup_outage_end_time)
  SELECT @_backup_outage_weekday_mask          = ISNULL (@backup_outage_weekday_mask,          @_backup_outage_weekday_mask)
  SELECT @_copy_enabled                        = ISNULL (@copy_enabled,                        @_copy_enabled)
  SELECT @_load_enabled                        = ISNULL (@load_enabled,                        @_load_enabled)
  SELECT @_out_of_sync_threshold               = ISNULL (@out_of_sync_threshold,               @_out_of_sync_threshold)
  SELECT @_out_of_sync_threshold_alert         = ISNULL (@out_of_sync_threshold_alert,         @_out_of_sync_threshold_alert)
  SELECT @_out_of_sync_threshold_alert_enabled = ISNULL (@out_of_sync_threshold_alert_enabled, @_out_of_sync_threshold_alert_enabled)
  SELECT @_out_of_sync_outage_start_time       = ISNULL (@out_of_sync_outage_start_time,       @_out_of_sync_outage_start_time)
  SELECT @_out_of_sync_outage_end_time         = ISNULL (@out_of_sync_outage_end_time,         @_out_of_sync_outage_end_time)
  SELECT @_out_of_sync_outage_weekday_mask     = ISNULL (@out_of_sync_outage_weekday_mask,     @_out_of_sync_outage_weekday_mask)

  -- updates
  UPDATE msdb.dbo.log_shipping_primaries SET
    backup_threshold            = @_backup_threshold,
    threshold_alert             = @_backup_threshold_alert,
    threshold_alert_enabled     = @_backup_threshold_alert_enabled,
    planned_outage_start_time   = @_backup_outage_start_time,
    planned_outage_end_time     = @_backup_outage_end_time,
    planned_outage_weekday_mask = @_backup_outage_weekday_mask
  WHERE primary_server_name = @primary_server_name AND primary_database_name = @primary_database_name

  UPDATE msdb.dbo.log_shipping_secondaries SET
    copy_enabled                = @_copy_enabled,
    load_enabled                = @_load_enabled,
    out_of_sync_threshold       = @_out_of_sync_threshold,
    threshold_alert             = @_out_of_sync_threshold_alert,
    threshold_alert_enabled     = @_out_of_sync_threshold_alert_enabled,
    planned_outage_start_time   = @_out_of_sync_outage_start_time,
    planned_outage_end_time     = @_out_of_sync_outage_weekday_mask,
    planned_outage_weekday_mask = @_out_of_sync_outage_end_time
  WHERE secondary_server_name = @secondary_server_name AND secondary_database_name = @secondary_database_name
RETURN(0)
END
go

/**************************************************************/
/* sp_delete_log_shipping_monitor_info                        */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_delete_log_shipping_monitor_info...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_delete_log_shipping_monitor_info' AND type = N'P')  )
  DROP PROCEDURE sp_delete_log_shipping_monitor_info
go
CREATE PROCEDURE sp_delete_log_shipping_monitor_info
  @primary_server_name                 sysname,
  @primary_database_name               sysname,
  @secondary_server_name               sysname,
  @secondary_database_name             sysname
AS BEGIN
  -- check that the primary exists
  IF (NOT EXISTS (SELECT * FROM msdb.dbo.log_shipping_primaries WHERE primary_server_name = @primary_server_name AND primary_database_name = @primary_database_name))
  BEGIN
    DECLARE @pp sysname
    SELECT @pp = @primary_server_name + N'.' + @primary_database_name
    RAISERROR (14262, 16, 1, N'primary_server_name.primary_database_name', @pp)
    RETURN (1) -- error
  END

  -- check that the secondary exists
  IF (NOT EXISTS (SELECT * FROM msdb.dbo.log_shipping_secondaries WHERE secondary_server_name = @secondary_server_name AND secondary_database_name = @secondary_database_name))
  BEGIN
    DECLARE @sp sysname
    SELECT @sp = @secondary_server_name + N'.' + @secondary_database_name
    RAISERROR (14262, 16, 1, N'secondary_server_name.secondary_database_name', @sp)
    RETURN (1) -- error
  END

  BEGIN TRANSACTION

  -- delete the secondary
  DELETE FROM msdb.dbo.log_shipping_secondaries WHERE secondary_server_name = @secondary_server_name AND secondary_database_name = @secondary_database_name
  IF (@@error <> 0)
    goto rollback_quit

  -- if there are no more secondaries for this primary then delete it
  IF (NOT EXISTS (SELECT * FROM msdb.dbo.log_shipping_primaries p, msdb.dbo.log_shipping_secondaries s WHERE p.primary_id = s.primary_id AND primary_server_name = @primary_server_name AND primary_database_name = @primary_database_name))
  BEGIN
    DELETE FROM msdb.dbo.log_shipping_primaries WHERE primary_server_name = @primary_server_name AND primary_database_name = @primary_database_name
    IF (@@error <> 0)
      goto rollback_quit
  END
 COMMIT TRANSACTION
 RETURN (0)

rollback_quit:
  ROLLBACK TRANSACTION
  RETURN(1) -- Failure
END
go 

/**************************************************************/
/* sp_remove_log_shipping_monitor_account                     */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_remove_log_shipping_monitor_account...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_remove_log_shipping_monitor_account' AND type = N'P')  )
  DROP PROCEDURE sp_remove_log_shipping_monitor_account
go

CREATE PROCEDURE sp_remove_log_shipping_monitor_account
AS
BEGIN
  SET NOCOUNT ON
  EXECUTE sp_dropuser N'log_shipping_monitor_probe'
  EXECUTE sp_droplogin N'log_shipping_monitor_probe'
END
go

/**************************************************************/
/* sp_log_shipping_monitor_backup                             */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_log_shipping_monitor_backup...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_log_shipping_monitor_backup' AND type = N'P')  )
  drop procedure sp_log_shipping_monitor_backup
go

CREATE PROCEDURE sp_log_shipping_monitor_backup AS
BEGIN
  DECLARE @primary_id                  sysname
  DECLARE @primary_server_name         sysname 
  DECLARE @primary_database_name       sysname 
  DECLARE @maintenance_plan_id         UNIQUEIDENTIFIER
  DECLARE @backup_threshold            INT
  DECLARE @threshold_alert             INT 
  DECLARE @threshold_alert_enabled     BIT 
  DECLARE @last_backup_filename        sysname 
  DECLARE @last_updated                DATETIME
  DECLARE @planned_outage_start_time   INT
  DECLARE @planned_outage_end_time     INT 
  DECLARE @planned_outage_weekday_mask INT
  DECLARE @sync_status                 INT
  DECLARE @backup_delta                INT
  DECLARE @delta_string                NVARCHAR (10)
  DECLARE @dt                             DATETIME

  SELECT @dt = GETDATE ()

  SET NOCOUNT ON

  DECLARE bmlsp_cur CURSOR FOR
    SELECT primary_id, 
           primary_server_name, 
           primary_database_name, 
         maintenance_plan_id, 
           backup_threshold, 
           threshold_alert, 
           threshold_alert_enabled, 
           last_backup_filename, 
           last_updated,
           planned_outage_start_time, 
           planned_outage_end_time, 
           planned_outage_weekday_mask 
    FROM msdb.dbo.log_shipping_primaries
    FOR READ ONLY

  OPEN bmlsp_cur
loop:
  FETCH NEXT FROM bmlsp_cur 
  INTO @primary_id, 
       @primary_server_name, 
      @primary_database_name, 
      @maintenance_plan_id,
       @backup_threshold, 
      @threshold_alert, 
      @threshold_alert_enabled, 
      @last_backup_filename, 
      @last_updated, 
      @planned_outage_start_time,
       @planned_outage_end_time, 
      @planned_outage_weekday_mask

  IF @@FETCH_STATUS <> 0 -- nothing more to fetch, finish the loop
    GOTO _loop

  EXECUTE @sync_status = sp_log_shipping_in_sync
    @last_updated,
    @dt,
     @backup_threshold,
   @planned_outage_start_time,
   @planned_outage_end_time,
    @planned_outage_weekday_mask,
   @threshold_alert_enabled,
   @backup_delta OUTPUT

   IF (@sync_status < 0)
   BEGIN
     SELECT @delta_string = CONVERT (NVARCHAR(10), @backup_delta)
     RAISERROR (@threshold_alert, 16, 1, @primary_server_name, @primary_database_name, @delta_string)
   END

  GOTO loop
_loop:
  CLOSE bmlsp_cur
  DEALLOCATE bmlsp_cur
END
go

/**************************************************************/
/* sp_log_shipping_monitor_restore                            */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_log_shipping_monitor_restore...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_log_shipping_monitor_restore' AND type = N'P')  )
  drop procedure sp_log_shipping_monitor_restore
go
CREATE PROCEDURE sp_log_shipping_monitor_restore AS
BEGIN
  SET NOCOUNT ON
  DECLARE @primary_id                  INT
  DECLARE @secondary_server_name       sysname
  DECLARE @secondary_database_name     sysname
  DECLARE @secondary_plan_id           UNIQUEIDENTIFIER
  DECLARE @out_of_sync_threshold       INT 
  DECLARE @threshold_alert             INT 
  DECLARE @threshold_alert_enabled     BIT 
  DECLARE @last_loaded_filename        NVARCHAR (500)
  DECLARE @last_backup_filename        NVARCHAR (500) 
  DECLARE @primary_database_name       sysname
  DECLARE @last_loaded_last_updated    DATETIME
  DECLARE @last_backup_last_updated    DATETIME
  DECLARE @planned_outage_start_time   INT 
  DECLARE @planned_outage_end_time     INT 
  DECLARE @planned_outage_weekday_mask INT
  DECLARE @sync_status                 INT
  DECLARE @sync_delta                  INT
  DECLARE @delta_string                NVARCHAR(10)

  SET NOCOUNT ON
  DECLARE @backupdt  DATETIME
  DECLARE @restoredt DATETIME
  DECLARE @rv        INT
  DECLARE rmlsp_cur CURSOR FOR
    SELECT s.primary_id, 
      s.secondary_server_name, 
      s.secondary_database_name, 
      s.secondary_plan_id, 
      s.out_of_sync_threshold, 
      s.threshold_alert, 
      s.threshold_alert_enabled, 
      s.last_loaded_filename, 
      s.last_loaded_last_updated,
      p.last_backup_filename,
      p.last_updated,
      p.primary_database_name,
      s.planned_outage_start_time, 
      s.planned_outage_end_time, 
      s.planned_outage_weekday_mask 
    FROM msdb.dbo.log_shipping_secondaries s 
    INNER JOIN msdb.dbo.log_shipping_primaries p 
    ON s.primary_id = p.primary_id
    FOR READ ONLY

  OPEN rmlsp_cur
loop:
  FETCH NEXT FROM rmlsp_cur 
  INTO @primary_id, 
      @secondary_server_name, 
         @secondary_database_name, 
         @secondary_plan_id, 
       @out_of_sync_threshold, 
         @threshold_alert, 
         @threshold_alert_enabled, 
         @last_loaded_filename, 
         @last_loaded_last_updated,
       @last_backup_filename,
       @last_backup_last_updated,
       @primary_database_name,
       @planned_outage_start_time, 
         @planned_outage_end_time, 
         @planned_outage_weekday_mask 

  IF @@FETCH_STATUS <> 0 -- nothing more to fetch, finish the loop
    GOTO _loop

  EXECUTE @rv = sp_log_shipping_get_date_from_file @primary_database_name, @last_backup_filename, @backupdt OUTPUT
  IF (@rv <> 0)
    SELECT @backupdt = @last_backup_last_updated
  
  EXECUTE @rv = sp_log_shipping_get_date_from_file @primary_database_name, @last_loaded_filename, @restoredt OUTPUT
  IF  (@rv <> 0)
    SELECT @restoredt = @last_loaded_last_updated

  EXECUTE @sync_status = sp_log_shipping_in_sync
    @restoredt,
    @backupdt,
     @out_of_sync_threshold,
     @planned_outage_start_time,
     @planned_outage_end_time,
    @planned_outage_weekday_mask,
    @threshold_alert_enabled,
    @sync_delta OUTPUT

   IF (@sync_status < 0)
   BEGIN
     SELECT @delta_string = CONVERT (NVARCHAR(10), @sync_delta)
     RAISERROR (@threshold_alert, 16, 1, @secondary_server_name, @secondary_database_name, @delta_string)
   END

  GOTO loop
_loop:
  CLOSE rmlsp_cur
  DEALLOCATE rmlsp_cur
END
go

/**************************************************************/
/* sp_change_monitor_role                                     */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_change_monitor_role...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_change_monitor_role' AND type = N'P')  )
  DROP PROCEDURE sp_change_monitor_role
go
CREATE PROCEDURE sp_change_monitor_role
  @primary_server     sysname,
  @secondary_server   sysname,
  @database           sysname,
  @new_source         NVARCHAR (128)
AS BEGIN
  SET NOCOUNT ON

  BEGIN TRANSACTION

  -- drop the secondary
  DELETE FROM msdb.dbo.log_shipping_secondaries 
    WHERE secondary_server_name = @secondary_server AND secondary_database_name = @database

  IF (@@ROWCOUNT <> 1)
  BEGIN
      ROLLBACK TRANSACTION
      RAISERROR (14442,-1,-1)
      return(1)
  END

  -- let everyone know that we are the new primary
  UPDATE msdb.dbo.log_shipping_primaries 
    SET primary_server_name = @secondary_server, primary_database_name = @database, source_directory = @new_source
    WHERE primary_server_name = @primary_server AND primary_database_name = @database

  IF (@@ROWCOUNT <> 1)
  BEGIN
      ROLLBACK TRANSACTION
      RAISERROR (14442,-1,-1)
      return(1)
  END
  COMMIT TRANSACTION

END
go

/**************************************************************/
/* sp_create_log_shipping_monitor_account                     */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_create_log_shipping_monitor_account...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_create_log_shipping_monitor_account' AND type = N'P')  )
  drop procedure sp_create_log_shipping_monitor_account
go
CREATE PROCEDURE sp_create_log_shipping_monitor_account @password sysname
AS
BEGIN
  DECLARE @rv INT
  SET NOCOUNT ON
-- raise an error if the password already exists
  if exists(select * from master.dbo.syslogins where loginname = N'log_shipping_monitor_probe')
  begin
    raiserror(15025,-1,-1,N'log_shipping_monitor_probe')
    RETURN (1) -- error
  end

  IF (@password = N'')
  BEGIN
    EXECUTE @rv = sp_addlogin N'log_shipping_monitor_probe', @defdb = N'msdb'
    IF @@error <>0 or @rv <> 0
      RETURN (1) -- error
  END
  ELSE
  BEGIN
    EXECUTE @rv = sp_addlogin N'log_shipping_monitor_probe', @password, N'msdb'
    IF @@error <>0 or @rv <> 0
      RETURN (1) -- error
  END

  EXECUTE @rv = sp_grantdbaccess N'log_shipping_monitor_probe', N'log_shipping_monitor_probe'
  IF @@error <>0 or @rv <> 0
    RETURN (1) -- error

  GRANT UPDATE ON log_shipping_primaries   TO log_shipping_monitor_probe
  GRANT UPDATE ON log_shipping_secondaries TO log_shipping_monitor_probe
  GRANT SELECT ON log_shipping_primaries   TO log_shipping_monitor_probe
  GRANT SELECT ON log_shipping_secondaries TO log_shipping_monitor_probe

  RETURN (0)
END
go

/**************************************************************/
/* INTEGRATION SERVICES SECTION                               */
/**************************************************************/

USE msdb
GO

if not exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sysssispackages]'))
BEGIN
CREATE TABLE [dbo].[sysssispackages] (
   [name] [sysname] NOT NULL ,
   [id] [uniqueidentifier] NOT NULL ,
   [description] [nvarchar] (1024) NULL ,
   [createdate] [datetime] NOT NULL ,
   [folderid] [uniqueidentifier] NOT NULL ,
   [ownersid] [varbinary] (85) NOT NULL ,
   [packagedata] [image] NOT NULL ,
   [packageformat] [int] NOT NULL,
   [packagetype] [int] NOT NULL CONSTRAINT [DF__sysssispackages] DEFAULT (0),
   [vermajor] [int] NOT NULL,
   [verminor] [int] NOT NULL,
   [verbuild] [int] NOT NULL,
   [vercomments] [nvarchar] (1024) NULL,
   [verid] [uniqueidentifier] NOT NULL,
   [isencrypted] [bit] NOT NULL CONSTRAINT [DF__sysssispackages_2] DEFAULT (0),
   [readrolesid] [varbinary] (85) NULL,
   [writerolesid] [varbinary] (85) NULL,
   CONSTRAINT [pk_sysssispackages] PRIMARY KEY NONCLUSTERED 
   (
      [folderid],
      [name]
   ) ON [PRIMARY] ,
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
END
else
BEGIN
IF NOT EXISTS (
  select * from msdb.dbo.syscolumns where name='isencrypted' and id =
      (select id from msdb.dbo.sysobjects where name='sysssispackages'))
BEGIN
  ALTER TABLE [dbo].[sysssispackages] ADD [isencrypted] [bit] NOT NULL CONSTRAINT [DF__sysssispackages_2] DEFAULT (0)
  ALTER TABLE [dbo].[sysssispackages] ADD [readrolesid] [varbinary] (85) NULL
  ALTER TABLE [dbo].[sysssispackages] ADD [writerolesid] [varbinary] (85) NULL
END
ELSE
BEGIN
  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='readrolesid' and id =
        (select id from msdb.dbo.sysobjects where name='sysssispackages'))
  BEGIN
    ALTER TABLE [dbo].[sysssispackages] DROP COLUMN [readrole]
    ALTER TABLE [dbo].[sysssispackages] DROP COLUMN [writerole]
    ALTER TABLE [dbo].[sysssispackages] ADD [readrolesid] [varbinary] (85) NULL
    ALTER TABLE [dbo].[sysssispackages] ADD [writerolesid] [varbinary] (85) NULL
  END
END
END
GO

/**************************************************************/
/* sysmaintplan_plans                                      */
/**************************************************************/
PRINT ''
PRINT 'Creating view sysmaintplan_plans...'
go
IF (NOT OBJECT_ID(N'dbo.sysmaintplan_plans', 'V') IS NULL)
  DROP VIEW sysmaintplan_plans
go
CREATE VIEW sysmaintplan_plans
AS
   SELECT
   s.name AS [name],
   s.id AS [id],
   s.description AS [description],
   s.createdate AS [create_date],
   suser_sname(s.ownersid) AS [owner],
   s.vermajor AS [version_major],
   s.verminor AS [version_minor],
   s.verbuild AS [version_build],
   s.vercomments AS [version_comments],
   ISNULL((select TOP 1 msx_plan from sysmaintplan_subplans where plan_id = s.id), 0) AS [from_msx],
   CASE WHEN (NOT EXISTS (select TOP 1 msx_job_id 
                          from sysmaintplan_subplans subplans, sysjobservers jobservers
                          where plan_id = s.id 
                          and msx_job_id is not null
                          and subplans.msx_job_id = jobservers.job_id
                          and server_id != 0)) 
        then 0 
        else 1 END AS [has_targets]
   FROM
   msdb.dbo.sysssispackages AS s
   WHERE
   (s.folderid = '08aa12d5-8f98-4dab-a4fc-980b150a5dc8' and s.packagetype = 6)
go

if not exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sysssispackagefolders]'))
BEGIN
CREATE TABLE [dbo].[sysssispackagefolders] (
   [folderid] [uniqueidentifier] NOT NULL ,
   [parentfolderid] [uniqueidentifier] NULL ,
   [foldername] [sysname] NOT NULL ,
   CONSTRAINT [PK_sysssispackagefolders] PRIMARY KEY NONCLUSTERED 
   (
      [folderid]
   )  ON [PRIMARY],
   CONSTRAINT [U_sysssispackagefoldersuniquepath] UNIQUE NONCLUSTERED 
   (
      [parentfolderid], 
      [foldername]
   ) ON [PRIMARY]
) ON [PRIMARY]
END
GO

-- WARNING! IMPORTANT! If you change sysssislog table schema,
-- be sure to update \dts\src\dtr\runtime\logproviders.cpp !!!

if not exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sysssislog]'))
BEGIN
CREATE TABLE [dbo].[sysssislog] (
   [id] [int] NOT NULL IDENTITY PRIMARY KEY,
   [event] [sysname] NOT NULL,
   [computer] [nvarchar] (128) NOT NULL,
   [operator] [nvarchar] (128) NOT NULL,
   [source] [nvarchar] (1024) NOT NULL,
   [sourceid] [uniqueidentifier] NOT NULL,
   [executionid] [uniqueidentifier] NOT NULL,
   [starttime] [datetime] NOT NULL,
   [endtime] [datetime] NOT NULL,
   [datacode] [int] NOT NULL,
   [databytes] [image] NULL,
   [message] [nvarchar] (2048) NOT NULL,
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
END
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_addlogentry]'))
    drop procedure [dbo].[sp_ssis_addlogentry]   

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_addlogentry]
  @event sysname,
  @computer nvarchar(128),
  @operator nvarchar(128),
  @source nvarchar(1024),
  @sourceid uniqueidentifier,
  @executionid uniqueidentifier,
  @starttime datetime,
  @endtime datetime,
  @datacode int,
  @databytes image,
  @message nvarchar(2048)
AS
  INSERT INTO sysssislog (
      event,
      computer,
      operator,
      source,
      sourceid,
      executionid,
      starttime,
      endtime,
      datacode,
      databytes,
      message )
  VALUES (
      @event,
      @computer,
      @operator,
      @source,
      @sourceid,
      @executionid,
      @starttime,
      @endtime,
      @datacode,
      @databytes,
      @message )
  RETURN 0
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_listpackages]'))
    drop procedure [dbo].[sp_ssis_listpackages]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_listpackages]
  @folderid uniqueidentifier
AS
  SELECT
      name,
      id,
      description,
      createdate,
      folderid,
      datalength(packagedata),
      vermajor,
      verminor,
      verbuild,
      vercomments,
      verid
  FROM
      sysssispackages
  WHERE
      [folderid] = @folderid
  ORDER BY
      name
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_listfolders]'))
    drop procedure [dbo].[sp_ssis_listfolders]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_listfolders]
  @parentfolderid uniqueidentifier = NULL
AS
  SELECT
   folderid,
   parentfolderid,
   foldername
  FROM
      sysssispackagefolders
  WHERE
      [parentfolderid] = @parentfolderid OR 
      (@parentfolderid IS NULL AND [parentfolderid] IS NULL)
  ORDER BY 
      foldername
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_deletepackage]'))
    drop procedure [dbo].[sp_ssis_deletepackage]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_deletepackage]
  @name sysname,
  @folderid uniqueidentifier
AS
  DECLARE @sid varbinary(85)
  DECLARE @writerolesid varbinary(85)
  DECLARE @writerole nvarchar(128)
  SELECT
      @sid = [ownersid],
      @writerolesid = [writerolesid]
  FROM
      sysssispackages
  WHERE
      [name] = @name AND
      [folderid] = @folderid
  IF @sid IS NOT NULL
  BEGIN
      --// The row exists, check security
      IF @writerolesid IS NOT NULL
      BEGIN
          SELECT @writerole = [name] FROM sys.database_principals WHERE [type] = ''R'' AND [sid] = @writerolesid
          IF @writerole IS NULL SET @writerole = ''db_ssisadmin''
      END
      IF @writerole IS NULL
      BEGIN
          IF (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
          BEGIN
              IF (@sid<>SUSER_SID()) OR (IS_MEMBER(''db_ssisltduser'')<>1)
              BEGIN
                  RAISERROR (14307, -1, -1, @name)
                  RETURN 1  -- Failure
              END
          END
      END
      ELSE
      BEGIN
          -- If writerrole is set for this package, 
          -- Allow sysadmins and the members of writer role to delete this package
          IF (IS_MEMBER(@writerole)<>1)  AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
          BEGIN
              IF (@sid<>SUSER_SID()) OR (IS_MEMBER(''db_ssisltduser'')<>1)
              BEGIN
                  RAISERROR (14307, -1, -1, @name)
                  RETURN 1  -- Failure
              END
          END
      END
  END
  DELETE FROM sysssispackages
  WHERE
      [name] = @name AND
      [folderid] = @folderid
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_deletefolder]'))
    drop procedure [dbo].[sp_ssis_deletefolder]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_deletefolder]
  @folderid uniqueidentifier
AS
   DECLARE @name  sysname
   DECLARE @count int

   IF @folderid = ''00000000-0000-0000-0000-000000000000''
   BEGIN
      RAISERROR (14307, -1, -1, ''00000000-0000-0000-0000-000000000000'')
      RETURN 1  -- Failure
   END

   SELECT
       @name = [foldername]
   FROM
       sysssispackagefolders
   WHERE
       [folderid] = @folderid
   IF @name IS NOT NULL
   BEGIN
       --// The row exists, check security
       IF (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
       BEGIN
           IF (IS_MEMBER(''db_ssisltduser'')<>1)
           BEGIN
               RAISERROR (14307, -1, -1, @name)
               RETURN 1  -- Failure
           END
       END
   END

   -- Get the number of packages in this folder
   SELECT
      @count = count(*)
   FROM
      sysssispackages
   WHERE
      [folderid] = @folderid

   -- Are there any packages in this folder
   IF @count > 0
   BEGIN
      -- Yes, do not delete
      RAISERROR (14593, -1, -1, @name)
      RETURN 1  -- Failure
   END

   -- Get the number of folders in this folder
   SELECT
      @count = count(*)
   FROM
      sysssispackagefolders
   WHERE
      [parentfolderid] = @folderid

   -- Are there any folders in this folder
   IF @count > 0
   BEGIN
      -- Yes, do not delete
      RAISERROR (14593, -1, -1, @name)
      RETURN 1  -- Failure
   END

   DELETE FROM sysssispackagefolders
   WHERE
       [folderid] = @folderid
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_getpackage]'))
    drop procedure [dbo].[sp_ssis_getpackage]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_getpackage]
  @name sysname,
  @folderid uniqueidentifier
AS
  DECLARE @sid varbinary(85)
  DECLARE @isencrypted bit
  DECLARE @readrolesid varbinary(85)
  DECLARE @readrole nvarchar(128)
  --// Check security, if the row exists
  SELECT @sid = [ownersid], @readrolesid = [readrolesid] FROM sysssispackages WHERE [name] = @name AND [folderid] = @folderid
  IF @sid IS NOT NULL
  BEGIN
      IF @readrolesid IS NOT NULL
      BEGIN
          SELECT @readrole = [name] FROM sys.database_principals WHERE [type] = ''R'' AND [sid] = @readrolesid
          IF @readrole IS NULL SET @readrole = ''db_ssisadmin''
      END
      IF @readrole IS NOT NULL
      BEGIN
          IF (IS_MEMBER(@readrole)<>1) AND (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
          BEGIN
              IF (IS_MEMBER(''db_ssisltduser'')<>1) OR (@sid<>SUSER_SID())
              BEGIN
                  RAISERROR (14307, -1, -1, @name)
                  RETURN 1  -- Failure
              END
          END
      END
      ELSE
      BEGIN
          IF (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1) AND (IS_MEMBER(''db_ssisoperator'')<>1)
          BEGIN
              IF (IS_MEMBER(''db_ssisltduser'')<>1) OR (@sid<>SUSER_SID())
              BEGIN
                  RAISERROR (14586, -1, -1, @name)
                  RETURN 1  -- Failure
              END
          END
      END
  END

  SELECT
      packagedata
  FROM
      sysssispackages
  WHERE
      [name] = @name AND
      [folderid] = @folderid
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_getfolder]'))
    drop procedure [dbo].[sp_ssis_getfolder]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_getfolder]
  @name sysname,
  @parentfolderid uniqueidentifier
AS
  SELECT
   folder.folderid,
   folder.foldername,
   folder.parentfolderid,
   parent.foldername
  FROM
      sysssispackagefolders folder 
  LEFT OUTER JOIN 
      sysssispackagefolders parent
  ON
      folder.parentfolderid = parent.folderid
  WHERE
      folder.foldername = @name AND
      (folder.parentfolderid = @parentfolderid OR 
      (@parentfolderid IS NULL AND folder.parentfolderid IS NULL))
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_putpackage]'))
    drop procedure [dbo].[sp_ssis_putpackage]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_putpackage]
  @name sysname,
  @id uniqueidentifier,
  @description nvarchar(1024),
  @createdate datetime,
  @folderid uniqueidentifier,
  @packagedata image,
  @packageformat int,
  @packagetype int,
  @vermajor int,
  @verminor int,
  @verbuild int,
  @vercomments nvarchar(1024),
  @verid uniqueidentifier
AS
  SET NOCOUNT ON
  DECLARE @sid varbinary(85)
  DECLARE @writerolesid varbinary(85)
  DECLARE @writerole nvarchar(128)
  --// Determine if we should INSERT or UPDATE
  SELECT @sid = [ownersid], @writerolesid = [writerolesid] FROM sysssispackages WHERE [name] = @name AND [folderid] = @folderid
  IF @sid IS NOT NULL
  BEGIN
      --// The row exists, check security
      IF @writerolesid IS NOT NULL
      BEGIN
          SELECT @writerole = [name] FROM sys.database_principals WHERE [type] = ''R'' AND [sid] = @writerolesid
          IF @writerole IS NULL SET @writerole = ''db_ssisadmin''
      END
      IF @writerole IS NULL
      BEGIN
          IF (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
          BEGIN
              IF (@sid<>SUSER_SID()) OR (IS_MEMBER(''db_ssisltduser'')<>1)
              BEGIN
                  RAISERROR (14307, -1, -1, @name)
                  RETURN 1  -- Failure
              END
          END
      END
      ELSE
      BEGIN
          IF (IS_MEMBER(@writerole)<>1) AND (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
          BEGIN
              IF (@sid<>SUSER_SID()) OR (IS_MEMBER(''db_ssisltduser'')<>1)
              BEGIN
                  RAISERROR (14307, -1, -1, @name)
                  RETURN 1  -- Failure
              END
          END
      END
      --// Security check passed, UPDATE now
      UPDATE sysssispackages
      SET
          id = @id,
          description = @description,
          createdate = @createdate,
          packagedata = @packagedata,
          packageformat = @packageformat,
          packagetype = @packagetype,
          vermajor = @vermajor,
          verminor = @verminor,
          verbuild = @verbuild,
          vercomments = @vercomments,
          verid = @verid
      WHERE
          name = @name AND folderid = @folderid
  END
  ELSE
  BEGIN
      --// The row does not exist, check security
      IF (IS_MEMBER(''db_ssisltduser'')<>1) AND (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
      BEGIN
          RAISERROR (14307, -1, -1, @name)
          RETURN 1  -- Failure
      END
      --// Security check passed, INSERT now
      INSERT INTO sysssispackages (
          name,
          id,
          description,
          createdate,
          folderid,
          ownersid,
          packagedata,
          packageformat,
          packagetype,
          vermajor,
          verminor,
          verbuild,
          vercomments,
          verid )
      VALUES (
          @name,
          @id,
          @description,
          @createdate,
          @folderid,
          SUSER_SID(),
          @packagedata,
          @packageformat,
          @packagetype,
          @vermajor,
          @verminor,
          @verbuild,
          @vercomments,
          @verid )
  END
  RETURN 0    -- SUCCESS
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_checkexists]'))
    drop procedure [dbo].[sp_ssis_checkexists]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_checkexists]
  @name sysname,
  @folderid uniqueidentifier
AS
  SET NOCOUNT ON
  SELECT TOP 1 1 FROM sysssispackages WHERE [name] = @name AND [folderid] = @folderid
  RETURN 0    -- SUCCESS
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_addfolder]'))
    drop procedure [dbo].[sp_ssis_addfolder]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_addfolder]
  @parentfolderid uniqueidentifier,
  @name sysname,
  @folderid uniqueidentifier = NULL
AS
   --Check security
   IF (IS_MEMBER(''db_ssisltduser'')<>1) AND (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
   BEGIN
       RAISERROR (14591, -1, -1, @name)
       RETURN 1  -- Failure
   END

   --// Security check passed, INSERT now
   INSERT INTO sysssispackagefolders (folderid, parentfolderid, foldername)
   VALUES (ISNULL(@folderid, NEWID()), @parentfolderid, @name)
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_renamefolder]'))
    drop procedure [dbo].[sp_ssis_renamefolder]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_renamefolder]
  @folderid uniqueidentifier,
  @name sysname
AS
   --Check security
   IF (IS_MEMBER(''db_ssisltduser'')<>1) AND (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
   BEGIN
       RAISERROR (14591, -1, -1, @name)
       RETURN 1  -- Failure
   END

   --// Security check passed, INSERT now
   UPDATE sysssispackagefolders
   SET [foldername] = @name
   WHERE [folderid] = @folderid
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_setpackageroles]'))
DROP PROCEDURE [dbo].[sp_ssis_setpackageroles]
GO

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_setpackageroles]
  @name sysname,
  @folderid uniqueidentifier,
  @readrole nvarchar (128),
  @writerole nvarchar (128)
AS
  SET NOCOUNT ON
  DECLARE @sid varbinary(85)
  --// Determine if we should INSERT or UPDATE
  SELECT @sid = ownersid FROM sysssispackages WHERE name = @name AND folderid = @folderid
  IF @sid IS NOT NULL
  BEGIN
      --// The row exists, check security
      IF (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
      BEGIN
          IF (@sid<>SUSER_SID())
          BEGIN
              RAISERROR (14307, -1, -1, @name)
              RETURN 1  -- Failure
          END
      END
      --// Security check passed, UPDATE now
      DECLARE @readrolesid varbinary(85)
      DECLARE @writerolesid varbinary(85)
      SELECT @readrolesid = [sid] FROM sys.database_principals WHERE [type] = ''R'' AND [name] = @readrole
      SELECT @writerolesid = [sid] FROM sys.database_principals WHERE [type] = ''R'' AND [name] = @writerole
      IF @readrolesid IS NULL AND @readrole IS NOT NULL
      BEGIN
          RAISERROR (15014, -1, -1, @readrole)
          RETURN 1
      END
      IF @writerolesid IS NULL AND @writerole IS NOT NULL
      BEGIN
          RAISERROR (15014, -1, -1, @writerole)
          RETURN 1
      END
      UPDATE sysssispackages
      SET
          [readrolesid] = @readrolesid,
          [writerolesid] = @writerolesid
      WHERE
          name = @name AND folderid = @folderid
  END
  ELSE
  BEGIN
      RAISERROR (14307, -1, -1, @name)
      RETURN 1  -- Failure
  END
  RETURN 0    -- SUCCESS
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_getpackageroles]'))
DROP PROCEDURE [dbo].[sp_ssis_getpackageroles]
GO

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_getpackageroles]
  @name sysname,
  @folderid uniqueidentifier
AS
  DECLARE @readrolesid varbinary(85)
  DECLARE @writerolesid varbinary(85)
  DECLARE @readrole nvarchar(128)
  DECLARE @writerole nvarchar(128)
  SELECT
      @readrolesid = [readrolesid],
      @writerolesid = [writerolesid]
  FROM
      sysssispackages
  WHERE
      [name] = @name AND
      [folderid] = @folderid
  SELECT @readrole = [name] FROM sys.database_principals WHERE [type] = ''R'' AND [sid] = @readrolesid
  SELECT @writerole = [name] FROM sys.database_principals WHERE [type] = ''R'' AND [sid] = @writerolesid
  SELECT @readrole AS readrole, @writerole AS writerole
'
GO


GRANT  EXECUTE  ON [dbo].[sp_ssis_addlogentry]     TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_addlogentry]     TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_deletepackage]   TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_deletepackage]   TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_deletepackage]   TO [db_ssisoperator]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getpackage]      TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getpackage]      TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getpackage]      TO [db_ssisoperator]
GRANT  EXECUTE  ON [dbo].[sp_ssis_listpackages]    TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_listpackages]    TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_listpackages]    TO [db_ssisoperator]
GRANT  EXECUTE  ON [dbo].[sp_ssis_putpackage]      TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_putpackage]      TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_putpackage]      TO [db_ssisoperator]
GRANT  EXECUTE  ON [dbo].[sp_ssis_checkexists]     TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_checkexists]     TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_checkexists]     TO [db_ssisoperator]
GRANT  EXECUTE  ON [dbo].[sp_ssis_listfolders]     TO [db_ssisadmin]  
GRANT  EXECUTE  ON [dbo].[sp_ssis_listfolders]     TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_listfolders]     TO [db_ssisoperator] 
GRANT  EXECUTE  ON [dbo].[sp_ssis_deletefolder]    TO [db_ssisadmin]  
GRANT  EXECUTE  ON [dbo].[sp_ssis_deletefolder]    TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_addfolder]       TO [db_ssisadmin]  
GRANT  EXECUTE  ON [dbo].[sp_ssis_addfolder]       TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_renamefolder]    TO [db_ssisadmin]  
GRANT  EXECUTE  ON [dbo].[sp_ssis_renamefolder]    TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getfolder]       TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getfolder]       TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getfolder]       TO [db_ssisoperator]
GRANT  EXECUTE  ON [dbo].[sp_ssis_setpackageroles] TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_setpackageroles] TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getpackageroles] TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getpackageroles] TO [db_ssisltduser]
GO

GRANT  ALL      ON [dbo].[sysssislog]            TO [db_ssisadmin]
GRANT  INSERT   ON [dbo].[sysssislog]            TO [db_ssisltduser]
GRANT  SELECT   ON [dbo].[sysssislog]            TO [db_ssisltduser]
GRANT  INSERT   ON [dbo].[sysssislog]            TO [db_ssisoperator]
GRANT  SELECT   ON [dbo].[sysssislog]            TO [db_ssisoperator]
GO

-- Maintenance Plans
 -- Allow SQLAgent on target servers to gather information about
 -- maintenance plans from the master.
GRANT EXECUTE ON sp_maintplan_subplans_by_job  TO SQLAgentUserRole
GRANT EXECUTE ON sp_maintplan_subplans_by_job  TO TargetServersRole


/**************************************************************/
/*                                                            */
/*  D  A  T  A     C  O  L  L  E  C  T  O  R                  */
/*                                                            */
/**************************************************************/
USE msdb
GO

---------------------------------------------------------------
-- Data Collector: Security: Database Principals
---------------------------------------------------------------

PRINT ''
PRINT 'Create dc_operator role...'
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'dc_operator' AND type = 'R'))
BEGIN
    CREATE ROLE [dc_operator]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'dc_operator' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [dc_operator]
        CREATE ROLE [dc_operator]
    END
END
GO

EXECUTE sp_addrolemember @rolename = 'db_ssisltduser' , 
                   @membername = 'dc_operator' 
GO

EXECUTE sp_addrolemember @rolename = 'db_ssisoperator' , 
                   @membername = 'dc_operator' 
GO

EXECUTE sp_addrolemember @rolename = 'SQLAgentUserRole' , 
                   @membername = 'dc_operator' 
GO

PRINT ''
PRINT 'Create dc_admin role...'
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'dc_admin' AND type = 'R'))
BEGIN
    CREATE ROLE [dc_admin]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'dc_admin' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [dc_admin]
        CREATE ROLE [dc_admin]
    END
END
GO

EXECUTE sp_addrolemember @rolename = 'dc_operator' , 
                   @membername = 'dc_admin' 
GO

PRINT ''
PRINT 'Create dc_proxy role...'
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'dc_proxy' AND type = 'R'))
BEGIN
    CREATE ROLE [dc_proxy]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'dc_proxy' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [dc_proxy]
        CREATE ROLE [dc_proxy]
    END
END
GO

EXECUTE sp_addrolemember @rolename = 'db_ssisltduser' , 
                   @membername = 'dc_proxy' 
GO

EXECUTE sp_addrolemember @rolename = 'db_ssisoperator' , 
                   @membername = 'dc_proxy' 
GO

PRINT ''
PRINT 'Create loginless user that has ownership of data collector agent-related securables...'
IF (NOT EXISTS(SELECT * FROM sys.database_principals WHERE NAME = 'MS_DataCollectorInternalUser'))
BEGIN
    CREATE USER [MS_DataCollectorInternalUser] WITHOUT LOGIN
END
GO

EXECUTE sp_addrolemember @rolename = 'db_ssisoperator' , 
                   @membername = 'MS_DataCollectorInternalUser' 
GO

EXECUTE sp_addrolemember @rolename = 'SQLAgentUserRole' , 
                   @membername = 'MS_DataCollectorInternalUser' 
GO

EXECUTE sp_addrolemember @rolename = 'dc_admin' , 
                   @membername = 'MS_DataCollectorInternalUser' 
GO

GRANT IMPERSONATE ON USER::[MS_DataCollectorInternalUser] TO [dc_admin];
GO

---------------------------------------------------------------
-- Configuration store
---------------------------------------------------------------
IF (OBJECT_ID(N'[dbo].[syscollector_config_store_internal]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [dbo].[syscollector_config_store_internal]...'
    CREATE TABLE [dbo].[syscollector_config_store_internal] (
        parameter_name                nvarchar(128) NOT NULL,
        parameter_value                sql_variant NULL,
        CONSTRAINT [PK_syscollector_config_store_internal_paremeter_name] PRIMARY KEY CLUSTERED (parameter_name ASC)
        )
END
GO

IF (NOT OBJECT_ID(N'[dbo].[syscollector_config_store]', 'V') IS NULL)
BEGIN
    PRINT 'Dropping view [dbo].[syscollector_config_store]...'
    DROP VIEW [dbo].[syscollector_config_store]
END
GO

PRINT 'Creating view [dbo].[syscollector_config_store]...'
GO
CREATE VIEW [dbo].[syscollector_config_store]
AS
    SELECT
        s.parameter_name,
        s.parameter_value
    FROM 
        [dbo].[syscollector_config_store_internal] s
GO

-- populate config store with known name and value pairs
IF (NOT EXISTS(SELECT * FROM [dbo].[syscollector_config_store_internal]
                WHERE parameter_name = N'MDWInstance'))
BEGIN
INSERT INTO [dbo].[syscollector_config_store_internal] (
    parameter_name, 
    parameter_value
)
VALUES
(
    N'MDWInstance',
    NULL
)
END

IF (NOT EXISTS(SELECT * FROM [dbo].[syscollector_config_store_internal]
                WHERE parameter_name = N'MDWDatabase'))
BEGIN
INSERT INTO [dbo].[syscollector_config_store_internal] (
    parameter_name, 
    parameter_value
)
VALUES
(
    N'MDWDatabase',
    NULL
)
END

IF (NOT EXISTS(SELECT * FROM [dbo].[syscollector_config_store_internal]
                WHERE parameter_name = 'CollectorEnabled'))
BEGIN
INSERT INTO [dbo].[syscollector_config_store_internal] (
    parameter_name, 
    parameter_value
)
VALUES
(
    'CollectorEnabled',
    0
)
END

IF (NOT EXISTS(SELECT * FROM [dbo].[syscollector_config_store_internal]
                WHERE parameter_name = 'CacheWindow'))
BEGIN
INSERT INTO [dbo].[syscollector_config_store_internal] (
    parameter_name, 
    parameter_value
)
VALUES
(
    'CacheWindow',
    1  --By default, the collector will keep 1 window's worth of uploads
)
END

IF (NOT EXISTS(SELECT * FROM [dbo].[syscollector_config_store_internal]
                WHERE parameter_name = 'CacheDirectory'))
BEGIN
INSERT INTO [dbo].[syscollector_config_store_internal] (
    parameter_name, 
    parameter_value
)
VALUES
(
    'CacheDirectory',
    NULL
)
END

GO

---------------------------------------------------------------
-- Access collector level properties
---------------------------------------------------------------

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_verify_collector_state]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_verify_collector_state]...'
    DROP PROCEDURE [dbo].[sp_syscollector_verify_collector_state]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_verify_collector_state]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_verify_collector_state]
    @desired_state          int
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    DECLARE @collector_enabled      INT
    SET @collector_enabled = CONVERT(int, (SELECT parameter_value FROM dbo.syscollector_config_store_internal
                            WHERE parameter_name = 'CollectorEnabled'))

    IF (@collector_enabled IS NULL)
    BEGIN
        RAISERROR(14691, -1, -1)
        RETURN(1)
    END

    IF (@collector_enabled = 0) AND (@desired_state = 1)
    BEGIN
        RAISERROR(14681, -1, -1)
        RETURN(1)
    END

    IF (@collector_enabled = 1) AND (@desired_state = 0)
    BEGIN
        RAISERROR(14690, -1, -1)
        RETURN(1)
    END

    RETURN(0)
END
GO

IF (NOT OBJECT_ID('dbo.sp_syscollector_set_warehouse_instance_name', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_set_warehouse_instance_name]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_set_warehouse_instance_name]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_set_warehouse_instance_name]
    @instance_name                    sysname = NULL
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN(1) -- Failure
    END

    -- Check if the collector is disabled
    DECLARE @retVal int
    EXEC @retVal = [dbo].[sp_syscollector_verify_collector_state] @desired_state = 0
    IF (@retVal <> 0)
        RETURN (1)

    UPDATE [msdb].[dbo].[syscollector_config_store_internal]
    SET parameter_value = @instance_name
    WHERE parameter_name = N'MDWInstance'

    RETURN (0)
END
GO

IF (NOT OBJECT_ID('dbo.sp_syscollector_set_warehouse_database_name', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_set_warehouse_database_name]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_set_warehouse_database_name]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_set_warehouse_database_name]
    @database_name                    sysname = NULL
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN(1) -- Failure
    END

    -- Check if the collector is disabled
    DECLARE @retVal int
    EXEC @retVal = [dbo].[sp_syscollector_verify_collector_state] @desired_state = 0
    IF (@retVal <> 0)
        RETURN (1)

    UPDATE [msdb].[dbo].[syscollector_config_store_internal]
    SET parameter_value = @database_name
    WHERE parameter_name = N'MDWDatabase'

    RETURN (0)
END
GO

IF (NOT OBJECT_ID('dbo.sp_syscollector_set_cache_directory', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_set_cache_directory]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_set_cache_directory]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_set_cache_directory]
    @cache_directory                    nvarchar(255) = NULL
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN(1) -- Failure
    END

    SET @cache_directory = NULLIF(LTRIM(RTRIM(@cache_directory)), N'')

    -- Check if the collector is disabled
    DECLARE @retVal int
    EXEC @retVal = [dbo].[sp_syscollector_verify_collector_state] @desired_state = 0
    IF (@retVal <> 0)
        RETURN (1)

    UPDATE [msdb].[dbo].[syscollector_config_store_internal]
    SET parameter_value = @cache_directory
    WHERE parameter_name = N'CacheDirectory'

    RETURN (0)
END
GO

IF (NOT OBJECT_ID('dbo.sp_syscollector_set_cache_window', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_set_cache_window]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_set_cache_window]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_set_cache_window]
    @cache_window                    int = 1
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN(1) -- Failure
    END

    -- Check if the collector is disabled
    DECLARE @retVal int
    EXEC @retVal = [dbo].[sp_syscollector_verify_collector_state] @desired_state = 0
    IF (@retVal <> 0)
        RETURN (1)

    IF (@cache_window < -1)
    BEGIN
        RAISERROR(14687, -1, -1, @cache_window)
        RETURN(1)
    END

    UPDATE [msdb].[dbo].[syscollector_config_store_internal]
    SET parameter_value = @cache_window
    WHERE parameter_name = N'CacheWindow'

    RETURN (0)
END
GO

IF (NOT OBJECT_ID('dbo.sp_syscollector_get_warehouse_connection_string', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_get_warehouse_connection_string]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_get_warehouse_connection_string]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_get_warehouse_connection_string]
    @connection_string              nvarchar(512) = NULL OUTPUT
AS
BEGIN
    DECLARE @instance_name sysname
    DECLARE @database_name sysname
    DECLARE @user_name sysname
    DECLARE @password sysname

    SELECT @instance_name = CONVERT(sysname,parameter_value)
    FROM [msdb].[dbo].[syscollector_config_store_internal]
    WHERE parameter_name = N'MDWInstance'

    IF (@instance_name IS NULL)
    BEGIN
        RAISERROR(14686, -1, -1)
        RETURN (1)
    END
    
    -- '"' is the delimiter for the sql client connection string
    SET @instance_name = QUOTENAME(@instance_name, '"')

    SELECT @database_name = CONVERT(sysname,parameter_value)
    FROM [msdb].[dbo].[syscollector_config_store_internal]
    WHERE parameter_name = N'MDWDatabase'

    IF (@database_name IS NULL)
    BEGIN
        RAISERROR(14686, -1, -1)
        RETURN (1)
    END

    SET @database_name = QUOTENAME(@database_name, '"')

    SET @connection_string = N'Data Source=' + @instance_name + N';Application Name="Data Collector - MDW";Initial Catalog=' + @database_name
    SET @connection_string = @connection_string + N';Use Encryption for Data=true;Trust Server Certificate=true;Provider=SQLNCLI10;Integrated Security=SSPI;Connect Timeout=60;';

    RETURN (0)
END
GO

-- 
-- Return the highest version of the Management Data Warehouse that this
-- Collector is no compatible with. Anything higher than this version
-- is fine.
--
-- If you change this number, make sure to change the corresponding versions
-- in HighestIncompatibleMDWVersion.cs
--
IF (NOT OBJECT_ID('dbo.fn_syscollector_highest_incompatible_mdw_version', 'FN') IS NULL)
BEGIN
    DROP FUNCTION [dbo].[fn_syscollector_highest_incompatible_mdw_version]
END
GO

PRINT ''
PRINT 'Creating function [dbo].[fn_syscollector_highest_incompatible_mdw_version]...'
GO
CREATE FUNCTION [dbo].[fn_syscollector_highest_incompatible_mdw_version]()
RETURNS nvarchar(50)
BEGIN
    RETURN '10.00.1300.13'  -- CTP6
END
GO
---------------------------------------------------------------
-- Collection set
---------------------------------------------------------------

IF (OBJECT_ID(N'[dbo].[syscollector_collection_sets_internal]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [dbo].[syscollector_collection_sets_internal]...'
    CREATE TABLE [dbo].[syscollector_collection_sets_internal] (
        collection_set_id            int IDENTITY NOT NULL,
        collection_set_uid            uniqueidentifier NOT NULL,      
        schedule_uid                uniqueidentifier NULL,          -- schedule to run collection or upload
        name                        sysname NOT NULL,               -- name of the collection set, must be unique
        name_id                        int NULL,                        -- sysmessage id of the name of the set (for localizing system collection set)
        target                        nvarchar(max) NULL,             -- future use
        is_running                    bit default 0 NOT NULL,         -- is the collection set active
        proxy_id                    int NULL,                       -- proxy to use to run the collection set
        is_system                    bit NOT NULL,                   -- indicates MS-shipped collection set
        collection_job_id            uniqueidentifier NULL,          -- id of the collection job
        upload_job_id                uniqueidentifier NULL,          -- id of the upload job
        collection_mode                smallint default 0 NOT NULL,    -- 0 - cached, 1 - non-cached
        logging_level                smallint default 2 NOT NULL,    -- 0 - errors only, 1 - errors & warnings, 2 - detailed
        description                    nvarchar(4000) NULL,            -- description of the set
        description_id                int NULL,                        -- sysmessage id of the description of the set (for localizing system collection set)
        days_until_expiration       smallint NOT NULL,              -- how long to keep the data from this collection set
        dump_on_any_error           bit default 0 NOT NULL,         -- configure SQL dumper to dump on any SSIS errors
        dump_on_codes               nvarchar(max) NULL,             -- configure SQL dumper to dump when we hit one of the specified SSIS errors 
        CONSTRAINT [PK_syscollector_collection_sets_internal] PRIMARY KEY CLUSTERED (collection_set_id ASC),
        CONSTRAINT [UQ_syscollector_collection_sets_internal_name] UNIQUE (name)
        )
    ALTER TABLE syscollector_collection_sets_internal
        ADD CONSTRAINT [FK_syscollector_collection_sets_internal_sysproxies] FOREIGN KEY(proxy_id)
        REFERENCES sysproxies (proxy_id)
    ALTER TABLE syscollector_collection_sets_internal
        ADD CONSTRAINT [FK_syscollector_collection_sets_collection_sysjobs] FOREIGN KEY(collection_job_id)
        REFERENCES sysjobs (job_id)
    ALTER TABLE syscollector_collection_sets_internal
        ADD CONSTRAINT [FK_syscollector_collection_sets_upload_sysjobs] FOREIGN KEY(upload_job_id)
        REFERENCES sysjobs(job_id)
END ELSE BEGIN
    IF (NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name = N'name_id' AND id = OBJECT_ID(N'[dbo].[syscollector_collection_sets_internal]')))
    BEGIN
        PRINT 'Altering table [dbo].[syscollector_collection_sets_internal]...'
        ALTER TABLE [dbo].[syscollector_collection_sets_internal] ADD name_id int NULL
    END
    
    IF (NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name = N'description_id' AND id = OBJECT_ID(N'[dbo].[syscollector_collection_sets_internal]')))
    BEGIN
        PRINT 'Altering table [dbo].[syscollector_collection_sets_internal]...'
        ALTER TABLE [dbo].[syscollector_collection_sets_internal] ADD description_id int NULL
    END
        IF (NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name = N'dump_on_any_error' AND id = OBJECT_ID(N'[dbo].[syscollector_collection_sets_internal]')))
    BEGIN
        PRINT 'Altering table [dbo].[syscollector_collection_sets_internal]...'
        ALTER TABLE [dbo].[syscollector_collection_sets_internal] ADD dump_on_any_error bit default 0
    END
    
    IF (NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name = N'dump_on_codes' AND id = OBJECT_ID(N'[dbo].[syscollector_collection_sets_internal]')))
    BEGIN
        PRINT 'Altering table [dbo].[syscollector_collection_sets_internal]...'
        ALTER TABLE [dbo].[syscollector_collection_sets_internal] ADD dump_on_codes nvarchar(max) NULL
    END    
END
GO

IF (NOT OBJECT_ID(N'[dbo].[syscollector_collection_sets]', 'V') IS NULL)
BEGIN
    PRINT 'Dropping view [dbo].[syscollector_collection_sets]...'
    DROP VIEW [dbo].[syscollector_collection_sets]
END
GO

PRINT 'Creating view [dbo].[syscollector_collection_sets]...'
GO
CREATE VIEW [dbo].[syscollector_collection_sets]
AS
    SELECT 
        s.collection_set_id,
        s.collection_set_uid,
        CASE 
            WHEN s.name_id IS NULL THEN s.name 
            ELSE FORMATMESSAGE(s.name_id)
        END AS name,        
        s.target,
        s.is_system,
        s.is_running,
        s.collection_mode,
        s.proxy_id,
        s.schedule_uid,
        s.collection_job_id,
        s.upload_job_id,
        s.logging_level,
        s.days_until_expiration,
        CASE 
            WHEN s.description_id IS NULL THEN s.description
            ELSE FORMATMESSAGE(s.description_id)
        END AS description,
        s.dump_on_any_error,
        s.dump_on_codes
    FROM 
        [dbo].[syscollector_collection_sets_internal] AS s
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_verify_collection_set]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_verify_collection_set]...'
    DROP PROCEDURE [dbo].[sp_syscollector_verify_collection_set]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_verify_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_verify_collection_set]
    @collection_set_id        int = NULL OUTPUT,
    @name                    sysname = NULL OUTPUT
AS
BEGIN
    IF (@name IS NOT NULL)
    BEGIN
        -- Remove any leading/trailing spaces from parameters
        SET @name =            NULLIF(LTRIM(RTRIM(@name)), N'')
    END

    IF (@collection_set_id IS NULL AND @name IS NULL)
    BEGIN
        RAISERROR(14624, -1, -1, '@collection_set_id, @name')
        RETURN(1)
    END

    IF (@collection_set_id IS NOT NULL AND @name IS NOT NULL)
    BEGIN
        IF (NOT EXISTS(SELECT *
                        FROM dbo.syscollector_collection_sets
                        WHERE collection_set_id = @collection_set_id
                        AND name = @name))
        BEGIN
            DECLARE @errMsg NVARCHAR(196)
            SELECT @errMsg = CONVERT(NVARCHAR(36), @collection_set_id) + ', ' + @name
            RAISERROR(14262, -1, -1, '@collection_set_id, @name', @errMsg)
            RETURN(1)
        END
    END
    -- Check id
    ELSE IF (@collection_set_id IS NOT NULL)
    BEGIN
        SELECT @name = name
        FROM dbo.syscollector_collection_sets
        WHERE (collection_set_id = @collection_set_id)
    
        -- the view would take care of all the permissions issues.
        IF (@name IS NULL) 
        BEGIN
            DECLARE @collection_set_id_as_char VARCHAR(36)
            SELECT @collection_set_id_as_char = CONVERT(VARCHAR(36), @collection_set_id)
            RAISERROR(14262, -1, -1, '@collection_set_id', @collection_set_id_as_char)
            RETURN(1) -- Failure
        END
    END
    -- Check name
    ELSE IF (@name IS NOT NULL)
    BEGIN
        -- get the corresponding collection_set_id (if the collection set exists)
        SELECT @collection_set_id = collection_set_id
        FROM dbo.syscollector_collection_sets
        WHERE (name = @name)

        -- the view would take care of all the permissions issues.
        IF (@collection_set_id IS NULL)
        BEGIN
            RAISERROR(14262, -1, -1, '@name', @name)
            RETURN(1) -- Failure
        END
    END
    RETURN (0)
END
GO

-- Create one schedule that starts when SQL Agent starts so that all continuous
-- collection jobs can attach to it, the schedule has to be accessible to the internal dc user that owns agent objects
-- EXECUTE AS USER = 'MS_DataCollectorInternalUser';

IF (NOT EXISTS (SELECT * FROM sysschedules_localserver_view WHERE name = N'RunAsSQLAgentServiceStartSchedule'))
BEGIN
    EXEC dbo.sp_add_schedule
        @schedule_name = N'RunAsSQLAgentServiceStartSchedule',
        @freq_type = 0x40,            -- FREQTYPE_AUTOSTART
        @freq_interval = 1
END

-- REVERT;
GO


IF (NOT OBJECT_ID('[dbo].[sp_syscollector_create_jobs]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_create_jobs]...'
    DROP PROCEDURE [dbo].[sp_syscollector_create_jobs]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_create_jobs]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_create_jobs]
    @collection_set_id        int,
    @collection_set_uid        uniqueidentifier,
    @collection_set_name    sysname,
    @proxy_id                int = NULL,
    @schedule_id            int = NULL,
    @collection_mode        smallint,
    @collection_job_id        uniqueidentifier OUTPUT,
    @upload_job_id            uniqueidentifier OUTPUT
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_syscollector_create_jobs
    ELSE
        BEGIN TRANSACTION

    BEGIN TRY

    -- job step names and commands shared between collection modes
    DECLARE @collection_set_id_as_char nvarchar(36)

    DECLARE @collection_step_command nvarchar(512)
    DECLARE @upload_step_command nvarchar(512)
    DECLARE @autostop_step_command nvarchar(512)
    DECLARE @purge_step_command nvarchar(1024)

    DECLARE @collection_step_name sysname
    DECLARE @upload_step_name sysname
    DECLARE @autostop_step_name sysname
    DECLARE @purge_step_name sysname

    DECLARE @instance_name  nvarchar(128)
    SET @instance_name = ISNULL(UPPER(CONVERT(nvarchar(128), SERVERPROPERTY('InstanceName'))), N'MSSQLSERVER')

    DECLARE @job_name sysname
    DECLARE @job_id uniqueidentifier        
    DECLARE @description nvarchar(512)

    IF(@collection_set_id IS NOT NULL)
    BEGIN
        SET @collection_set_id_as_char = CONVERT(NVARCHAR(36), @collection_set_id)
        SET @collection_step_command = 
            N'dcexec -c -s ' + @collection_set_id_as_char + N' -i ' + QUOTENAME(@instance_name, N'"') + 
            N' -m ' + CONVERT(NVARCHAR(36), @collection_mode);
        SET @upload_step_command = 
            N'dcexec -u -s ' + @collection_set_id_as_char + N' -i ' + QUOTENAME(@instance_name, N'"');
        SET @autostop_step_command =
            N'exec dbo.sp_syscollector_stop_collection_set @collection_set_id=' + @collection_set_id_as_char 
            + N', @stop_collection_job = 0';  -- do not stop the collection job, otherwise you will abort yourself!
        SET @purge_step_command = 
            N'
            EXEC [dbo].[sp_syscollector_purge_collection_logs]
            '
    END

    -- verify that the proxy_id exists
    IF (@proxy_id IS NOT NULL)
    BEGIN
        DECLARE @proxy_name sysname
        DECLARE @retVal int
        -- this will throw an error of proxy_id does not exist
        EXEC @retVal = msdb.dbo.sp_verify_proxy_identifiers '@proxy_name', '@proxy_id', @proxy_name OUTPUT, @proxy_id OUTPUT
        IF (@retVal <> 0)
            RETURN (0)
    END

    -- add jobs, job steps and attach schedule separately for different modes
    IF (@collection_mode = 1)    -- non-cached mode
    BEGIN
        -- create 1 job and 2 steps, first for collection & upload, second for log purging
        SET @job_name = N'collection_set_' + @collection_set_id_as_char + '_noncached_collect_and_upload'
        SET @collection_step_name = @job_name + '_collect'
        SET @upload_step_name = @job_name + '_upload'
        SET @purge_step_name = @job_name + '_purge_logs'
        SET @description = N'Data Collector job for collection set ' + QUOTENAME(@collection_set_name)

        -- add agent job and job server
        EXEC dbo.sp_add_job 
            @job_name        = @job_name,
            @category_id    = 8, -- N'Data Collector'
            @enabled        = 0,
            @description    = @description,
            @job_id            = @job_id OUTPUT
        
        EXEC dbo.sp_add_jobserver
            @job_id            = @job_id,
            @server_name    = N'(local)'

        -- add both collect and upload job steps to the same job
        EXEC dbo.sp_add_jobstep
            @job_id                = @job_id,
            @step_name            = @collection_step_name,
            @subsystem            = 'CMDEXEC',
            @command            = @collection_step_command,
            @on_success_action    =  3,        -- go to the next job step (purge the log)
            @on_fail_action        =  2,        -- quit with failure
            @proxy_id            = @proxy_id,
            @flags              = 16        -- Write log to table (append to existing history)

        EXEC dbo.sp_add_jobstep
            @job_id                = @job_id,
            @step_name            = @purge_step_name,
            @subsystem            = 'TSQL',
            @database_name        = 'msdb',
            @command            = @purge_step_command,
            @on_success_action    =  3,        -- go to the next job step (upload)
            @on_fail_action        =  3,        -- go to the next job step (upload)
            @proxy_id            = NULL,
            @flags                = 16        -- write log to table (append to existing history)

        EXEC dbo.sp_add_jobstep
            @job_id                = @job_id,
            @step_name            = @upload_step_name,
            @subsystem            = 'CMDEXEC',
            @command            = @upload_step_command,
            @on_success_action    =  1,        -- quit with success
            @on_fail_action        =  2,        -- quit with failure
            @proxy_id            = @proxy_id,
            @flags              = 16        -- Write log to table (append to existing history)

        IF @schedule_id IS NOT NULL
        BEGIN
            -- attach the schedule
            EXEC dbo.sp_attach_schedule
                @job_id            = @job_id,
                @schedule_id    = @schedule_id
        END

        SET @upload_job_id = @job_id
        SET @collection_job_id = @job_id
    END

    IF (@collection_mode = 0) -- cached mode
    BEGIN
        -- create 2 jobs for collect and upload
        -- add to collect job an extra step that autostops collection called in case collect job fails
        DECLARE @upload_job_name        sysname
        DECLARE @collection_job_name    sysname
        SET @upload_job_name = N'collection_set_' + @collection_set_id_as_char + '_upload'
        SET @collection_job_name = N'collection_set_' + @collection_set_id_as_char + '_collection'

        SET @collection_step_name = @collection_job_name + '_collect'
        SET @autostop_step_name = @collection_job_name + '_autostop'
        SET @upload_step_name = @upload_job_name + '_upload'
        SET @purge_step_name = @upload_job_name + '_purge_logs'

        -- modify the collection step to pass in the stop event name passed in by agent
        SET @collection_step_command = @collection_step_command + N' -e $' + N'(ESCAPE_NONE(' + N'STOPEVENT))'

        -- add agent job and job server
        EXEC dbo.sp_add_job 
            @job_name        = @upload_job_name,
            @category_id    = 8, -- N'Data Collector'
            @enabled        = 0,
            @job_id            = @upload_job_id OUTPUT
        
        EXEC dbo.sp_add_jobserver
            @job_id            = @upload_job_id,
            @server_name    = N'(local)'

        EXEC dbo.sp_add_job 
            @job_name        = @collection_job_name,
            @category_id    = 8, -- N'Data Collector'
            @enabled        = 0,
            @job_id            = @collection_job_id OUTPUT

        EXEC dbo.sp_add_jobserver
            @job_id            = @collection_job_id,
            @server_name    = N'(local)'

        -- add upload job step to upload job and collection job
        -- step to collection job separately
        EXEC dbo.sp_add_jobstep
            @job_id                = @upload_job_id,
            @step_name            = @purge_step_name,
            @subsystem            = 'TSQL',
            @database_name        = 'msdb',
            @command            = @purge_step_command,
            @on_success_action    =  3,        -- go to next job step (upload)
            @on_fail_action        =  3,        -- go to next job step (upload)
            @proxy_id            = NULL,
            @flags                = 16        -- write log to table (append to existing history)

        EXEC dbo.sp_add_jobstep
            @job_id                = @upload_job_id,
            @step_name            = @upload_step_name,
            @subsystem            = 'CMDEXEC',
            @command            = @upload_step_command,
            @on_success_action    =  1,        -- quit with success
            @on_fail_action        =  2,        -- quit with failure
            @proxy_id            = @proxy_id

        EXEC dbo.sp_add_jobstep
            @job_id                = @collection_job_id,
            @step_name            = @collection_step_name,
            @subsystem            = 'CMDEXEC',
            @command            = @collection_step_command,
            @on_success_action    =  1,        -- quit with success
            @on_fail_action        =  3,        -- go to next job step (auto-stop)
            @proxy_id            = @proxy_id,
            @flags              = 80 -- 16 (write log to table (append to existing history) 
                                     -- + 64 (create a stop event and pass it to the command line)

        EXEC dbo.sp_add_jobstep
            @job_id                = @collection_job_id,
            @step_name            = @autostop_step_name,
            @subsystem            = 'TSQL',
            @database_name        = 'msdb',
            @command            = @autostop_step_command,
            @on_success_action    =  2,        -- quit with failure
            @on_fail_action        =  2,        -- quit with failure
            @proxy_id            = NULL,
            @flags                = 16        -- write log to table (append to existing history)

        -- attach the input schedule to the upload job
        EXEC dbo.sp_attach_schedule
            @job_id            = @upload_job_id,
            @schedule_id    = @schedule_id

        -- attach the RunAsSQLAgentServiceStartSchedule to the collection job
        EXEC dbo.sp_attach_schedule
            @job_id            = @collection_job_id,
            @schedule_name    = N'RunAsSQLAgentServiceStartSchedule'
    END

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)

    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_syscollector_create_jobs

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_create_collection_set]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_create_collection_set]...'
    DROP PROCEDURE [dbo].[sp_syscollector_create_collection_set]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_create_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_create_collection_set]
    @name                        sysname,
    @target                        nvarchar(128) = NULL,
    @collection_mode            smallint = 0,    -- 0: cached, 1: non-cached
    @days_until_expiration      smallint = 730, -- two years
    @proxy_id                   int = NULL,     -- mutual exclusive; must specify either proxy_id or proxy_name to identify the proxy
    @proxy_name                    sysname = NULL,
    @schedule_uid               uniqueidentifier = NULL, 
    @schedule_name              sysname = NULL, -- mutual exclusive; must specify either schedule_uid or schedule_name to identify the schedule
    @logging_level                smallint = 1,
    @description                nvarchar(4000) = NULL,
    @collection_set_id            int OUTPUT,
    @collection_set_uid            uniqueidentifier = NULL OUTPUT
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_create_collection_set
    ELSE
        BEGIN TRANSACTION

    BEGIN TRY

    -- Security check (role membership)
    EXECUTE AS CALLER;
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN (1)
    END
    REVERT;

    -- Remove any leading/trailing spaces from parameters
    SET @name                    = NULLIF(LTRIM(RTRIM(@name)), N'')
    SET @proxy_name                = NULLIF(LTRIM(RTRIM(@proxy_name)), N'')
    SET @schedule_name            = NULLIF(LTRIM(RTRIM(@schedule_name)), N'')
    SET @target                    = NULLIF(LTRIM(RTRIM(@target)), N'')
    SET @description            = LTRIM(RTRIM(@description))

    IF (@name IS NULL)
    BEGIN
        RAISERROR(21263, -1, -1, '@name')
        RETURN (1)
    END

    -- can't have both name and uid for the schedule
    IF (@schedule_uid IS NOT NULL) AND (@schedule_name IS NOT NULL)
    BEGIN
        RAISERROR(14373, -1, -1, '@schedule_uid', '@schedule_name')
        RETURN (1)
    END

    -- Execute the check for the schedule as caller to ensure only schedules owned by caller can be attached
    EXECUTE AS CALLER;

    DECLARE @schedule_id int
    IF (@schedule_uid IS NOT NULL)
    BEGIN
        SElECT @schedule_id = schedule_id FROM sysschedules_localserver_view WHERE @schedule_uid = schedule_uid
    
        IF (@schedule_id IS NULL)
        BEGIN
            DECLARE @schedule_uid_as_char VARCHAR(36)
            SELECT @schedule_uid_as_char = CONVERT(VARCHAR(36), @schedule_uid)
            REVERT;
            RAISERROR(14262, -1, -1, N'@schedule_uid', @schedule_uid_as_char)
            RETURN (1)
        END
    END
    ELSE IF (@schedule_name IS NOT NULL)
    BEGIN
        SELECT @schedule_id = schedule_id, @schedule_uid = schedule_uid FROM sysschedules_localserver_view WHERE name = @schedule_name 
    
        IF (@schedule_id IS NULL)
        BEGIN
            REVERT;
            RAISERROR(14262, -1, -1, N'@schedule_name', @schedule_name)
            RETURN (1)
        END
    END

    REVERT;

    -- if collection_mode is cached, make sure schedule_id is not null
    IF    (@collection_mode = 0 AND @schedule_id IS NULL)
    BEGIN
        RAISERROR(14683, -1, -1)    
        RETURN (1)
    END    

    IF (@proxy_id IS NOT NULL) OR (@proxy_name IS NOT NULL) 
    BEGIN
        -- check if the proxy exists
        EXEC sp_verify_proxy_identifiers '@proxy_name',
                                         '@proxy_id',
                                         @proxy_name OUTPUT,
                                         @proxy_id   OUTPUT

        -- check if proxy_id is granted to dc_admin
        IF (@proxy_id NOT IN (SELECT proxy_id 
                              FROM sysproxylogin 
                              WHERE sid = USER_SID(USER_ID('dc_admin'))
                              )
            )
        BEGIN
            RAISERROR(14523, -1, -1, N'dc_admin', @proxy_name)
            RETURN (1)
        END
    END

    IF (@collection_mode < 0 OR @collection_mode > 1)
    BEGIN
        RAISERROR(14266, -1, -1, '@collection_mode', '0, 1')
        RETURN (1)
    END

    IF (@logging_level < 0 OR @logging_level > 2)
    BEGIN
        RAISERROR(14266, -1, -1, '@logging_level', '0, 1, or 2')
        RETURN (1)
    END

    IF (@collection_set_uid IS NULL)
    BEGIN
        SET @collection_set_uid = NEWID()
    END

    IF (@days_until_expiration < 0)
    BEGIN
        RAISERROR(14266, -1, -1, '@days_until_expiration', '>= 0')
        RETURN (1)
    END

    INSERT INTO [dbo].[syscollector_collection_sets_internal]
    (
        collection_set_uid,
        schedule_uid,
        name,
        target,
        is_running,
        proxy_id,
        is_system,
        upload_job_id,
        collection_job_id,
        collection_mode,
        logging_level,
        days_until_expiration,
        description
    )
    VALUES
    (
        @collection_set_uid,
        @schedule_uid,
        @name,
        @target,
        0,
        @proxy_id,
        0,
        NULL,
        NULL,
        @collection_mode,
        @logging_level,
        @days_until_expiration,
        @description
    )

    SET @collection_set_id = SCOPE_IDENTITY()

    IF (@collection_set_id IS NULL)
    BEGIN
        DECLARE @collection_set_id_as_char VARCHAR(36)
        SELECT @collection_set_id_as_char = CONVERT(VARCHAR(36), @collection_set_id)
        RAISERROR(14262, -1, -1, '@collection_set_id', @collection_set_id_as_char)
        RETURN (1)
    END

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)

    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_create_collection_set

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)        
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_update_job_proxy]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_update_job_proxy]...'
    DROP PROCEDURE [dbo].[sp_syscollector_update_job_proxy]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_update_job_proxy]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_update_job_proxy]
    @job_id                    uniqueidentifier,
    @proxy_id                int = NULL,
    @proxy_name                sysname = NULL
AS
BEGIN
    -- update the proxy id for the all job steps
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_syscollector_update_proxy
    ELSE
        BEGIN TRANSACTION
    
    BEGIN TRY
        DECLARE @step_id         INT
        DECLARE cursor_job_steps CURSOR FOR
            SELECT step_id FROM dbo.sysjobsteps WHERE job_id = @job_id AND subsystem = N'CMDEXEC'

        OPEN cursor_job_steps
        FETCH NEXT FROM cursor_job_steps INTO @step_id

        WHILE @@FETCH_STATUS = 0
        BEGIN
            EXEC dbo.sp_update_jobstep
                @job_id = @job_id,
                @step_id = @step_id,
                @proxy_id = @proxy_id,
                @proxy_name = @proxy_name

            FETCH NEXT FROM cursor_job_steps INTO @step_id
        END

        CLOSE      cursor_job_steps
        DEALLOCATE cursor_job_steps

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_syscollector_update_proxy

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)        
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_update_collection_set_internal]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_update_collection_set_internal]...'
    DROP PROCEDURE [dbo].[sp_syscollector_update_collection_set_internal]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_update_collection_set_internal]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_update_collection_set_internal]
    @collection_set_id          int,
    @collection_set_uid         uniqueidentifier,
    @name                       sysname,
    @new_name                   sysname,
    @target                     nvarchar(128),
    @collection_mode            smallint,         
    @days_until_expiration      smallint,
    @proxy_id                   int,              
    @proxy_name                 sysname,          
    @schedule_uid               uniqueidentifier, 
    @schedule_name              sysname,          
    @logging_level              smallint,
    @description                nvarchar(4000),   
    @schedule_id                int,
    @old_collection_mode        smallint,
    @old_proxy_id               int,
    @old_collection_job_id      uniqueidentifier,
    @old_upload_job_id          uniqueidentifier    
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_update_collection_set
    ELSE
        BEGIN TRANSACTION
    BEGIN TRY
        DECLARE @old_upload_schedule_id    int
        DECLARE @old_upload_schedule_uid uniqueidentifier

        SELECT  @old_upload_schedule_id = sv.schedule_id,
                @old_upload_schedule_uid = cs.schedule_uid
        FROM dbo.syscollector_collection_sets cs 
        JOIN sysschedules_localserver_view sv ON (cs.schedule_uid = sv.schedule_uid)
        WHERE collection_set_id = @collection_set_id

        -- update job names, schedule, and collection mode in a transaction to maintain a consistent state in case of failures
        IF (@collection_mode IS NOT NULL AND @collection_mode != @old_collection_mode)
        BEGIN
            IF (@schedule_id IS NULL)
            BEGIN
                -- if no schedules is supplied as a parameter to this update SP, 
                -- we can use the one that is already in the collection set table
                SET @schedule_uid = @old_upload_schedule_uid
                
                SELECT @schedule_id = schedule_id 
                FROM sysschedules_localserver_view 
                WHERE @schedule_uid = schedule_uid
            END

            IF (@schedule_name IS NOT NULL AND @schedule_name = N'')
            BEGIN
                SET @schedule_id = NULL 
            END

            -- make sure there exists a schedule we can use
            IF (@old_collection_mode = 1 AND @schedule_id IS NULL) -- a switch from non-cached to cached mode require a schedule
            BEGIN
                -- no schedules specified in input or collection set table, raise error
                RAISERROR(14683, -1, -1)
                RETURN (1)
            END

            -- Only update the jobs if we have jobs already created. Otherwise the right
            -- jobs will be created when the collection set starts for the first time.
            IF (@old_collection_job_id IS NOT NULL AND @old_upload_job_id IS NOT NULL)
            BEGIN
                -- create new jobs 
                DECLARE @collection_job_id        uniqueidentifier 
                DECLARE @upload_job_id            uniqueidentifier 

                DECLARE @collection_set_name sysname;
                SET @collection_set_name = ISNULL(@new_name, @name);
                EXEC [dbo].[sp_syscollector_create_jobs] 
                    @collection_set_id        = @collection_set_id,
                    @collection_set_uid     = @collection_set_uid,
                    @collection_set_name    = @collection_set_name,
                    @proxy_id                = @proxy_id,
                    @schedule_id            = @schedule_id,
                    @collection_mode        = @collection_mode,
                    @collection_job_id        = @collection_job_id OUTPUT,
                    @upload_job_id            = @upload_job_id OUTPUT

                UPDATE [dbo].[syscollector_collection_sets_internal]
                SET
                    upload_job_id        = @upload_job_id,
                    collection_job_id    = @collection_job_id
                WHERE @collection_set_id = collection_set_id
                
                -- drop old upload and collection jobs
                EXEC dbo.sp_syscollector_delete_jobs 
                    @collection_job_id        = @old_collection_job_id,
                    @upload_job_id            = @old_upload_job_id,
                    @schedule_id            = @old_upload_schedule_id,
                    @collection_mode        = @old_collection_mode
            END
        END
        ELSE -- collection mode unchanged, we do not have to recreate the jobs
        BEGIN
            -- we need to update the proxy id for all job steps
            IF (@old_proxy_id <> @proxy_id) OR (@old_proxy_id IS NULL AND @proxy_id IS NOT NULL)
            BEGIN
                IF (@old_collection_job_id IS NOT NULL)
                BEGIN
                    EXEC dbo.sp_syscollector_update_job_proxy
                        @job_id    = @old_collection_job_id, 
                        @proxy_id  = @proxy_id
                END

                IF (@old_upload_job_id IS NOT NULL)
                BEGIN
                    EXEC dbo.sp_syscollector_update_job_proxy
                        @job_id    = @old_upload_job_id, 
                        @proxy_id  = @proxy_id
                END
            END
            IF (@proxy_name = N'' AND @old_proxy_id IS NOT NULL) 
            BEGIN
                IF (@old_collection_job_id IS NOT NULL)
                BEGIN
                    EXEC dbo.sp_syscollector_update_job_proxy
                        @job_id    = @old_collection_job_id, 
                        @proxy_name = @proxy_name
                END

                IF (@old_upload_job_id IS NOT NULL)
                BEGIN
                    EXEC dbo.sp_syscollector_update_job_proxy
                        @job_id    = @old_upload_job_id, 
                        @proxy_name = @proxy_name
                END
            END

            -- need to update the schedule
            IF (@old_upload_schedule_id <> @schedule_id) OR (@old_upload_schedule_id IS NULL AND @schedule_id IS NOT NULL)
            BEGIN
                -- detach the old schedule 
                IF (@old_upload_job_id IS NOT NULL) AND (@old_upload_schedule_id IS NOT NULL)
                BEGIN
                    EXEC dbo.sp_detach_schedule 
                        @job_id            = @old_upload_job_id,
                        @schedule_id    = @old_upload_schedule_id,
                        @delete_unused_schedule = 0
                END

                -- attach the new schedule
                IF (@old_upload_job_id IS NOT NULL)
                BEGIN
                    EXEC dbo.sp_attach_schedule
                        @job_id            = @old_upload_job_id,
                        @schedule_id    = @schedule_id
                END
            END

            -- special case - remove the existing schedule
            IF (@schedule_name = N'') AND (@old_upload_schedule_id IS NOT NULL)
            BEGIN
                EXEC dbo.sp_detach_schedule 
                    @job_id            = @old_upload_job_id,
                    @schedule_id    = @old_upload_schedule_id,
                    @delete_unused_schedule = 0
            END
        END

        -- after the all operations succeed, update the sollection_sets table
        DECLARE @new_proxy_id int
        SET @new_proxy_id = @proxy_id
        IF (@proxy_name    = N'')    SET @new_proxy_id = NULL

        UPDATE [dbo].[syscollector_collection_sets_internal]
        SET
            name                    = ISNULL(@new_name, name),
            target                    = ISNULL(@target, target),
            proxy_id                = @new_proxy_id,
            collection_mode            = ISNULL(@collection_mode, collection_mode),
            logging_level            = ISNULL(@logging_level, logging_level),
            days_until_expiration   = ISNULL(@days_until_expiration, days_until_expiration)
        WHERE @collection_set_id = collection_set_id

        IF (@schedule_uid IS NOT NULL OR @schedule_name IS NOT NULL)
        BEGIN
            IF (@schedule_name = N'')    SET @schedule_uid = NULL

            UPDATE [dbo].[syscollector_collection_sets_internal]
            SET schedule_uid = @schedule_uid
            WHERE @collection_set_id = collection_set_id
        END

        IF (@description IS NOT NULL)
        BEGIN
            IF (@description = N'')      SET @description = NULL

            UPDATE [dbo].[syscollector_collection_sets_internal]
            SET description = @description
            WHERE @collection_set_id = collection_set_id
        END

        IF (@TranCounter = 0)
        COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_update_collection_set

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_update_collection_set]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_update_collection_set]...'
    DROP PROCEDURE [dbo].[sp_syscollector_update_collection_set]
END
GO

-- Updates a collection set. These are the steps involved
-- 1- Security checks
-- 2- Stop the collection set if currently running (sp_syscollector_stop_collection_set)
-- 3- Perform the actual update transactionally (sp_syscollector_update_collection_set_internal)
-- 4- Restart the collection set if it was running (sp_syscollector_start_collection_set
PRINT 'Creating procedure [dbo].[sp_syscollector_update_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_update_collection_set]
    @collection_set_id        int = NULL,
    @name                    sysname = NULL,
    @new_name                sysname = NULL,
    @target                    nvarchar(128) = NULL,
    @collection_mode        smallint = NULL,         -- 0: cached, 1: non-cached
    @days_until_expiration  smallint = NULL,
    @proxy_id               int = NULL,              -- mutual exclusive; must specify either proxy_id or proxy_name to identify the proxy
    @proxy_name             sysname = NULL,          -- @proxy_name = N'' is a special case to allow change of an existing proxy with NULL
    @schedule_uid           uniqueidentifier = NULL, -- mutual exclusive; must specify either schedule_uid or schedule_name to identify the schedule
    @schedule_name          sysname = NULL,          -- @schedule_name = N'' is a special case to allow change of an existing schedule with NULL
    @logging_level            smallint = NULL,
    @description            nvarchar(4000) = NULL   -- @description = N'' is a special case to allow change of an existing description with NULL
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    -- Security checks will be performed against caller's security context
    EXECUTE AS CALLER;

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN (1)
    END

    -- Security checks (restrict functionality for non-dc_admin-s)
    IF (((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1)) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        AND (@new_name IS NOT NULL))
    BEGIN
        REVERT;
        RAISERROR(14676, -1, -1, '@new_name', 'dc_admin')
        RETURN (1)
    END

    IF (((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1)) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        AND (@target IS NOT NULL))
    BEGIN
        REVERT;
        RAISERROR(14676, -1, -1, '@target', 'dc_admin')
        RETURN (1)
    END

    IF (((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1)) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        AND (@proxy_id IS NOT NULL))
    BEGIN
        REVERT;
        RAISERROR(14676, -1, -1, '@proxy_id', 'dc_admin')
        RETURN (1)
    END

    IF (((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1)) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        AND (@collection_mode IS NOT NULL))
    BEGIN
        REVERT;
        RAISERROR(14676, -1, -1, '@collection_mode', 'dc_admin')
        RETURN (1)
    END

    IF (((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1)) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        AND (@description IS NOT NULL))
    BEGIN
        REVERT;
        RAISERROR(14676, -1, -1, '@description', 'dc_admin')
        RETURN (1)
    END

    IF (((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1)) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        AND (@days_until_expiration IS NOT NULL))
    BEGIN
        REVERT;
        RAISERROR(14676, -1, -1, '@days_until_expiration', 'dc_admin')
        RETURN (1) -- Failure
    END

    -- Security checks done, reverting now to internal data collector user security context
    REVERT;

    -- check for inconsistencies/errors in the parameters
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    IF (@collection_mode IS NOT NULL AND (@collection_mode < 0 OR @collection_mode > 1))
    BEGIN
        RAISERROR(14266, -1, -1, '@collection_mode', '0, 1')
        RETURN (1)
    END

    IF (@logging_level IS NOT NULL AND (@logging_level < 0 OR @logging_level > 2))
    BEGIN
        RAISERROR(14266, -1, -1, '@logging_level', '0, 1, or 2')
        RETURN(1)
    END

    IF (LEN(@new_name) = 0)
    BEGIN
      RAISERROR(21263, -1, -1, '@new_name')
      RETURN(1) -- Failure
    END    

    -- Remove any leading/trailing spaces from parameters
    SET @target                    = NULLIF(LTRIM(RTRIM(@target)), N'')
    SET @new_name                = NULLIF(LTRIM(RTRIM(@new_name)), N'')
    SET @description            = LTRIM(RTRIM(@description))
    
    DECLARE @is_system                    bit
    DECLARE @is_running                    bit
    DECLARE @collection_set_uid            uniqueidentifier
    DECLARE @old_collection_mode        smallint
    DECLARE @old_upload_job_id            uniqueidentifier
    DECLARE @old_collection_job_id        uniqueidentifier
    DECLARE @old_proxy_id                int

    SELECT    @is_running = is_running,
            @is_system = is_system,
            @collection_set_uid = collection_set_uid,
            @old_collection_mode = collection_mode,
            @old_collection_job_id = collection_job_id, 
            @old_upload_job_id = upload_job_id,
            @old_proxy_id = proxy_id
    FROM dbo.syscollector_collection_sets
    WHERE collection_set_id = @collection_set_id

    IF (@is_system = 1 AND (
            @new_name IS NOT NULL OR 
            @description IS NOT NULL))
    BEGIN
        -- cannot update, delete, or add new collection items to a system collection set
        RAISERROR(14696, -1, -1);
        RETURN (1)
    END
    
    IF (@proxy_id IS NOT NULL) OR (@proxy_name IS NOT NULL AND @proxy_name <> N'')
    BEGIN
        -- verify the proxy exists
        EXEC sp_verify_proxy_identifiers '@proxy_name',
                                         '@proxy_id',
                                         @proxy_name OUTPUT,
                                         @proxy_id   OUTPUT

        -- check if proxy_id is granted to dc_admin
        IF (@proxy_id NOT IN (SELECT proxy_id 
                              FROM sysproxylogin 
                              WHERE sid = USER_SID(USER_ID('dc_admin'))
                              )
            )
        BEGIN
            RAISERROR(14523, -1, -1, N'dc_admin', @proxy_name)
            RETURN (1)
        END
    END
    ELSE -- if no proxy_id provided, get the existing proxy_id, might need it later to create new jobs
    BEGIN
        SET @proxy_id = @old_proxy_id
    END

    -- can't have both uid and name passed for the schedule
    IF (@schedule_uid IS NOT NULL) AND (@schedule_name IS NOT NULL AND @schedule_name <> N'')
    BEGIN
        RAISERROR(14373, -1, -1, '@schedule_uid', '@schedule_name')
        RETURN (1)
    END

    -- check if it attempts to remove a schedule when the collection mode is cached
    IF    (@schedule_name = N'' AND @collection_mode = 0)    OR 
        (@collection_mode IS NULL AND @old_collection_mode = 0 AND @schedule_name = N'')
    BEGIN
        RAISERROR(14683, -1, -1)    
        RETURN (1)
    END    

    -- Execute the check for the schedule as caller to ensure only schedules owned by caller can be attached
    EXECUTE AS CALLER;

    DECLARE @schedule_id int
    SET @schedule_id = NULL
    IF (@schedule_uid IS NOT NULL)
    BEGIN
        SElECT @schedule_id = schedule_id FROM sysschedules_localserver_view WHERE @schedule_uid = schedule_uid
    
        IF (@schedule_id IS NULL)
        BEGIN
            DECLARE @schedule_uid_as_char VARCHAR(36)
            SELECT @schedule_uid_as_char = CONVERT(VARCHAR(36), @schedule_uid)
            REVERT;
            RAISERROR(14262, -1, -1, N'@schedule_uid', @schedule_uid_as_char)
            RETURN (1)
        END
    END
    ELSE IF (@schedule_name IS NOT NULL AND @schedule_name <> N'') -- @schedule_name is not null
    BEGIN
        SELECT @schedule_id = schedule_id, @schedule_uid = schedule_uid FROM sysschedules_localserver_view WHERE name = @schedule_name 
    
        IF (@schedule_id IS NULL)
        BEGIN
            REVERT;
            RAISERROR(14262, -1, -1, N'@schedule_name', @schedule_name)
            RETURN (1)
        END
    END

    REVERT;
    
    -- Stop the collection set if it is currently running
    IF (@is_running = 1 AND (
            @new_name IS NOT NULL OR 
            @target IS NOT NULL OR 
            @proxy_id IS NOT NULL OR 
            @logging_level IS NOT NULL OR 
            @collection_mode IS NOT NULL))
    BEGIN
        EXEC @retVal = sp_syscollector_stop_collection_set @collection_set_id = @collection_set_id
        IF (@retVal <> 0)
            RETURN (1)
    END

    -- Passed all necessary checks, go ahead with the update
    EXEC @retVal = sp_syscollector_update_collection_set_internal
        @collection_set_id = @collection_set_id,
        @collection_set_uid = @collection_set_uid,
        @name = @name,
        @new_name = @new_name,
        @target = @target,
        @collection_mode = @collection_mode,
        @days_until_expiration = @days_until_expiration,
        @proxy_id = @proxy_id,
        @proxy_name = @proxy_name,
        @schedule_uid = @schedule_uid,
        @schedule_name = @schedule_name,
        @logging_level = @logging_level,
        @description = @description,
        @schedule_id = @schedule_id,
        @old_collection_mode = @old_collection_mode,
        @old_proxy_id = @old_proxy_id,
        @old_collection_job_id = @old_collection_job_id,
        @old_upload_job_id = @old_upload_job_id
            
     IF (@retVal <> 0)
        RETURN (1)
        
     -- Restart the collection set if it has been already running
     IF (@is_running = 1)
     BEGIN
         EXEC @retVal = sp_syscollector_start_collection_set
                            @collection_set_id = @collection_set_id
         IF (@retVal <> 0)
            RETURN (1)
     END
        
     RETURN (0)
END
GO

IF (NOT OBJECT_ID('dbo.sp_syscollector_configure_sql_dumper', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_configure_sql_dumper]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_configure_sql_dumper]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_configure_sql_dumper]
    @collection_set_id        int = NULL,
    @name                    sysname = NULL,
    @dump_on_any_error      bit = NULL,                -- configure SQL dumper to dump on any SSIS errors
    @dump_on_codes          nvarchar(max) = NULL    -- configure SQL dumper to dump when we hit one of the specified SSIS errors. Set to N'' to remove the codes.
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN(1) -- Failure
    END

    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    DECLARE @is_running bit
    SELECT    @is_running = is_running
    FROM dbo.syscollector_collection_sets
    WHERE collection_set_id = @collection_set_id
    IF (@is_running = 1)
    BEGIN
        RAISERROR(14711, 0, 1)
    END

    IF (@dump_on_codes = N'')
    BEGIN
        UPDATE [dbo].[syscollector_collection_sets_internal]
        SET dump_on_codes = NULL
        WHERE @collection_set_id = collection_set_id
    END
    ELSE IF (@dump_on_codes IS NOT NULL)
    BEGIN
        UPDATE [msdb].[dbo].[syscollector_collection_sets_internal]
        SET dump_on_codes = @dump_on_codes
        WHERE @collection_set_id = collection_set_id
    END    

    IF (@dump_on_any_error IS NOT NULL)
    BEGIN
        UPDATE [msdb].[dbo].[syscollector_collection_sets_internal]
        SET dump_on_any_error = @dump_on_any_error
        WHERE @collection_set_id = collection_set_id
    END

    RETURN (0)
END
GO

---------------------------------------------------------------
-- Collector type
---------------------------------------------------------------
IF (OBJECT_ID(N'[dbo].[syscollector_collector_types_internal]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [dbo].[syscollector_collector_types_internal]...'
    CREATE TABLE [dbo].[syscollector_collector_types_internal] (
        collector_type_uid                uniqueidentifier NOT NULL,
        name                            sysname NOT NULL,
        parameter_schema                xml NULL,
        parameter_formatter                xml NULL,
        schema_collection                sysname NULL,
        collection_package_name            sysname NOT NULL,
        collection_package_folderid        uniqueidentifier NOT NULL,
        upload_package_name                sysname NOT NULL,
        upload_package_folderid            uniqueidentifier NOT NULL,
        is_system                        bit default 0 NOT NULL,
        CONSTRAINT [PK_syscollector_collector_types_internal] PRIMARY KEY CLUSTERED (collector_type_uid ASC),
        CONSTRAINT [UQ_syscollector_collection_types_internal_name] UNIQUE (name)
        )
    ALTER TABLE syscollector_collector_types_internal
        ADD CONSTRAINT [FK_syscollector_collector_types_internal_upload_sysssispackages] FOREIGN KEY(upload_package_folderid, upload_package_name)
        REFERENCES sysssispackages (folderid, [name])
    ALTER TABLE syscollector_collector_types_internal
        ADD CONSTRAINT [FK_syscollector_collector_types_internal_collection_sysssispackages] FOREIGN KEY(collection_package_folderid, collection_package_name)
        REFERENCES sysssispackages (folderid, [name])
END
GO

-- [fn_syscollector_get_package_path]
-- This function returns the full path of a SSIS package given the package id
IF (NOT OBJECT_ID('[dbo].[fn_syscollector_get_package_path]', 'FN') IS NULL)
BEGIN
    PRINT 'Dropping function [dbo].[fn_syscollector_get_package_path]...'
    DROP FUNCTION [dbo].[fn_syscollector_get_package_path]
END
GO

PRINT 'Creating function [dbo].[fn_syscollector_get_package_path]...'
GO
CREATE FUNCTION [dbo].[fn_syscollector_get_package_path] 
(
    @package_id uniqueidentifier
)
RETURNS NVARCHAR(4000)
AS
BEGIN
    IF @package_id IS NULL
        RETURN NULL

    DECLARE @package_path nvarchar(4000)
    DECLARE @prevfolderid uniqueidentifier
    DECLARE @folderid uniqueidentifier
    DECLARE @package_name sysname
    SET @package_path = ''

    SELECT @package_name = name, 
            @folderid = folderid 
    FROM dbo.sysssispackages
    WHERE id = @package_id

    WHILE (@folderid != '00000000-0000-0000-0000-000000000000')
    BEGIN
        SET @prevfolderid = @folderid

        DECLARE @foldername sysname
        SELECT @foldername = foldername, 
                @folderid = parentfolderid 
        FROM dbo.sysssispackagefolders
        WHERE folderid = @prevfolderid
        SET @package_path = @foldername + N'\\' + @package_path
    END

    SET @package_path = N'\\' + @package_path + @package_name
    RETURN @package_path
END
GO

IF (NOT OBJECT_ID(N'[dbo].[syscollector_collector_types]', 'V') IS NULL)
BEGIN
    PRINT 'Dropping view [dbo].[syscollector_collector_types]...'
    DROP VIEW [dbo].[syscollector_collector_types]
END
GO

PRINT 'Creating view [dbo].[syscollector_collector_types]...'
GO
CREATE VIEW [dbo].[syscollector_collector_types]
AS
    SELECT 
        t.collector_type_uid,
        t.name,
        t.parameter_schema,
        t.parameter_formatter,
        s1.id AS collection_package_id,
        dbo.fn_syscollector_get_package_path(s1.id) AS collection_package_path,
        s1.name AS collection_package_name,
        s2.id AS upload_package_id,
        dbo.fn_syscollector_get_package_path(s2.id) AS upload_package_path,
        s2.name AS upload_package_name,
        t.is_system
    FROM 
        [dbo].[syscollector_collector_types_internal] AS t,
        sysssispackages s1,
        sysssispackages s2
    WHERE t.collection_package_folderid = s1.folderid
      AND t.collection_package_name = s1.name
      AND t.upload_package_folderid = s2.folderid
      AND t.upload_package_name = s2.name
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_verify_collector_type]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_verify_collector_type]...'
    DROP PROCEDURE [dbo].[sp_syscollector_verify_collector_type]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_verify_collector_type]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_verify_collector_type]
    @collector_type_uid        uniqueidentifier = NULL OUTPUT,
    @name                    sysname = NULL OUTPUT
AS
BEGIN
    IF (@name IS NOT NULL)
    BEGIN
        -- Remove any leading/trailing spaces from parameters
        SET @name                    = NULLIF(LTRIM(RTRIM(@name)), N'')
    END

    IF (@collector_type_uid IS NULL AND @name IS NULL)
    BEGIN
        RAISERROR(14624, -1, -1, '@collector_type_uid, @name')
        RETURN(1)
    END

    IF (@collector_type_uid IS NOT NULL AND @name IS NOT NULL)
    BEGIN
        IF (NOT EXISTS(SELECT *
                        FROM dbo.syscollector_collector_types
                        WHERE collector_type_uid = @collector_type_uid
                        AND name = @name))
        BEGIN
            DECLARE @errMsg NVARCHAR(196)
            SELECT @errMsg = CONVERT(NVARCHAR(36), @collector_type_uid) + ', ' + @name
            RAISERROR(14262, -1, -1, '@collector_type_uid, @name', @errMsg)
            RETURN(1)
        END
    END
    -- Check id
    ELSE IF (@collector_type_uid IS NOT NULL)
    BEGIN
        SELECT @name = name
        FROM dbo.syscollector_collector_types
        WHERE (collector_type_uid = @collector_type_uid)
    
        -- the view would take care of all the permissions issues.
        IF (@name IS NULL) 
        BEGIN
            DECLARE @collector_type_uid_as_char VARCHAR(36)
            SELECT @collector_type_uid_as_char = CONVERT(VARCHAR(36), @collector_type_uid)
            RAISERROR(14262, -1, -1, '@collector_type_uid', @collector_type_uid_as_char)
            RETURN(1) -- Failure
        END
    END
    -- Check name
    ELSE IF (@name IS NOT NULL)
    BEGIN
        -- get the corresponding collector_type_uid (if the collector type exists)
        SELECT @collector_type_uid = collector_type_uid
        FROM dbo.syscollector_collector_types
        WHERE (name = @name)

        -- the view would take care of all the permissions issues.
        IF (@collector_type_uid IS NULL)
        BEGIN
            RAISERROR(14262, -1, -1, '@name', @name)
            RETURN(1) -- Failure
        END
    END
    RETURN (0)
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_create_collector_type]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_create_collector_type]...'
    DROP PROCEDURE [dbo].[sp_syscollector_create_collector_type]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_create_collector_type]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_create_collector_type]
    @collector_type_uid            uniqueidentifier = NULL OUTPUT,
    @name                        sysname,
    @parameter_schema            xml = NULL,
    @parameter_formatter        xml = NULL,
    @collection_package_id        uniqueidentifier,
    @upload_package_id            uniqueidentifier
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_create_collector_type
    ELSE
        BEGIN TRANSACTION
    BEGIN TRY

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN (1)
    END

    SET @name                = NULLIF(LTRIM(RTRIM(@name)), N'')
    IF (@name IS NULL) 
    BEGIN
        RAISERROR(21263, -1, -1, '@name', @name)
        RETURN (1)
    END

    IF (@collector_type_uid IS NULL) 
    BEGIN
        SET @collector_type_uid = NEWID()
    END
    
    IF (NOT EXISTS(SELECT * from sysssispackages
        WHERE @collection_package_id = id))
    BEGIN
        DECLARE @collection_package_id_as_char VARCHAR(36)
        SELECT @collection_package_id_as_char = CONVERT(VARCHAR(36), @collection_package_id)
        RAISERROR(14262, -1, -1, '@collection_package_id', @collection_package_id_as_char)
        RETURN (1)
    END

    IF (NOT EXISTS(SELECT * from sysssispackages
        WHERE @upload_package_id = id))
    BEGIN
        DECLARE @upload_package_id_as_char VARCHAR(36)
        SELECT @upload_package_id_as_char = CONVERT(VARCHAR(36), @upload_package_id)
        RAISERROR(14262, -1, -1, '@upload_package_id', @upload_package_id_as_char)
        RETURN (1)
    END

    DECLARE @collection_package_name sysname
    DECLARE @collection_package_folderid uniqueidentifier
    DECLARE @upload_package_name sysname
    DECLARE @upload_package_folderid uniqueidentifier    

    SELECT 
        @collection_package_name = name,
        @collection_package_folderid = folderid
    FROM sysssispackages
    WHERE @collection_package_id = id

    SELECT 
        @upload_package_name = name,
        @upload_package_folderid = folderid
    FROM sysssispackages
    WHERE @upload_package_id = id

    DECLARE @schema_collection sysname
    IF (@parameter_schema IS NOT NULL)
    BEGIN
        SET @schema_collection = N'schema_collection_' + @name
        WHILE (EXISTS (SELECT * FROM sys.xml_schema_collections WHERE name = @schema_collection))
        BEGIN
            SET @schema_collection = LEFT(@schema_collection, 119) + '_' + RIGHT(STR(FLOOR(RAND() * 100000000)),8)
        END

        DECLARE @retVal int
        DECLARE @sql_string nvarchar(2048)
        DECLARE @param_definition nvarchar(16)
        SET @param_definition = N'@schema xml'
        SET @sql_string = N'CREATE XML SCHEMA COLLECTION ' + QUOTENAME(@schema_collection, '[') + N' AS @schema; '
        SET @sql_string = @sql_string + N'GRANT EXECUTE ON XML SCHEMA COLLECTION::[dbo].' + QUOTENAME(@schema_collection, '[') + N' TO dc_admin; ' 
        SET @sql_string = @sql_string + N'GRANT VIEW DEFINITION ON XML SCHEMA COLLECTION::[dbo].' + QUOTENAME(@schema_collection, '[') + N' TO dc_admin; '

        EXEC sp_executesql @sql_string, @param_definition, @schema = @parameter_schema
    END

    INSERT INTO [dbo].[syscollector_collector_types_internal]
    (
        collector_type_uid,
        name,
        parameter_schema,
        parameter_formatter,
        schema_collection,
        collection_package_name,
        collection_package_folderid,
        upload_package_name,
        upload_package_folderid
    )
    VALUES
    (
        @collector_type_uid,
        @name,
        @parameter_schema,
        @parameter_formatter,
        @schema_collection,
        @collection_package_name,
        @collection_package_folderid,
        @upload_package_name,
        @upload_package_folderid
    )

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_create_collector_type

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)    
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_update_collector_type]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_update_collector_type]...'
    DROP PROCEDURE [dbo].[sp_syscollector_update_collector_type]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_update_collector_type]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_update_collector_type]
    @collector_type_uid            uniqueidentifier = NULL,
    @name                        sysname = NULL,
    @parameter_schema            xml = NULL,
    @parameter_formatter        xml = NULL,
    @collection_package_id        uniqueidentifier,
    @upload_package_id            uniqueidentifier
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_update_collector_type
    ELSE
        BEGIN TRANSACTION
    BEGIN TRY

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN (1)
    END

    -- Check the validity of the name/uid pair
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collector_type @collector_type_uid OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)
    
    DECLARE @old_parameter_schema       xml
    DECLARE @old_parameter_formatter    xml
    DECLARE @old_collection_package_id  uniqueidentifier
    DECLARE @old_upload_package_id      uniqueidentifier

    SELECT  @old_parameter_schema = parameter_schema,
            @old_parameter_formatter = parameter_formatter,
            @old_collection_package_id = collection_package_id,
            @old_upload_package_id = upload_package_id
    FROM [dbo].[syscollector_collector_types]
    WHERE name = @name
    AND collector_type_uid = @collector_type_uid

    IF (@collection_package_id IS NULL)
    BEGIN
        SET @collection_package_id = @old_collection_package_id
    END
    ELSE IF (NOT EXISTS(SELECT * from sysssispackages
                        WHERE @collection_package_id = id))
    BEGIN
        DECLARE @collection_package_id_as_char VARCHAR(36)
        SELECT @collection_package_id_as_char = CONVERT(VARCHAR(36), @collection_package_id)
        RAISERROR(14262, -1, -1, '@collection_package_id', @collection_package_id_as_char)
        RETURN (1)
    END

    IF (@upload_package_id IS NULL)
    BEGIN
        SET @upload_package_id = @old_upload_package_id
    END
    ELSE IF (NOT EXISTS(SELECT * from sysssispackages
                        WHERE @upload_package_id = id))
    BEGIN
        DECLARE @upload_package_id_as_char VARCHAR(36)
        SELECT @upload_package_id_as_char = CONVERT(VARCHAR(36), @upload_package_id)
        RAISERROR(14262, -1, -1, '@upload_package_id', @upload_package_id_as_char)
        RETURN (1)
    END

    DECLARE @collection_package_name sysname
    DECLARE @collection_package_folderid uniqueidentifier
    DECLARE @upload_package_name sysname
    DECLARE @upload_package_folderid uniqueidentifier    

    SELECT 
        @collection_package_name = name,
        @collection_package_folderid = folderid
    FROM sysssispackages
    WHERE @collection_package_id = id

    SELECT 
        @upload_package_name = name,
        @upload_package_folderid = folderid
    FROM sysssispackages
    WHERE @upload_package_id = id

    DECLARE @schema_collection sysname
    IF (@parameter_schema IS NULL)
    BEGIN
        SET @parameter_schema = @old_parameter_schema
    END
    ELSE
    BEGIN
        SELECT @schema_collection = schema_collection
        FROM [dbo].[syscollector_collector_types_internal]
        WHERE name = @name
        AND collector_type_uid = @collector_type_uid

        -- if a previous xml schema collection existed with the same name, drop it in favor of the new schema
        IF (EXISTS (SELECT * FROM sys.xml_schema_collections WHERE name = @schema_collection))
        BEGIN
            DECLARE @sql_drop_schema nvarchar(512)
            SET @sql_drop_schema = N'DROP XML SCHEMA COLLECTION ' + QUOTENAME(@schema_collection)
            EXECUTE sp_executesql @sql_drop_schema
        END

        -- recreate it with the new schema
        DECLARE @sql_create_schema nvarchar(2048)
        DECLARE @param_definition nvarchar(16)
        SET @param_definition = N'@schema xml'
        SET @sql_create_schema = N'CREATE XML SCHEMA COLLECTION ' + QUOTENAME(@schema_collection) + N' AS @schema; '
        SET @sql_create_schema = @sql_create_schema + N'GRANT EXECUTE ON XML SCHEMA COLLECTION::[dbo].' + QUOTENAME(@schema_collection) + N' TO dc_admin; ' 
        SET @sql_create_schema = @sql_create_schema + N'GRANT VIEW DEFINITION ON XML SCHEMA COLLECTION::[dbo].' + QUOTENAME(@schema_collection) + N' TO dc_admin; '
            
        EXEC sp_executesql @sql_create_schema, @param_definition, @schema = @parameter_schema
    END

    UPDATE [dbo].[syscollector_collector_types_internal]
    SET parameter_schema = @parameter_schema,
        parameter_formatter = @parameter_formatter,
        schema_collection = @schema_collection,
        collection_package_name = @collection_package_name,
        collection_package_folderid = @collection_package_folderid,
        upload_package_name = @upload_package_name,
        upload_package_folderid = @upload_package_folderid
    WHERE @collector_type_uid = collector_type_uid
    AND   @name = name

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_update_collector_type

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)    
    END CATCH
END
GO



IF (NOT OBJECT_ID('[dbo].[sp_syscollector_validate_xml]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_validate_xml]...'
    DROP PROCEDURE [dbo].[sp_syscollector_validate_xml]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_validate_xml]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_validate_xml]
    @collector_type_uid        uniqueidentifier = NULL,
    @name                    sysname = NULL,
    @parameters                xml
AS
BEGIN
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collector_type @collector_type_uid OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    DECLARE @schema_collection sysname
    SET @schema_collection = (SELECT schema_collection 
                                FROM syscollector_collector_types_internal
                                WHERE @collector_type_uid = collector_type_uid)

    IF (@schema_collection IS NULL)
    BEGIN
        RAISERROR(21263, -1, -1, '@schema_collection')
        RETURN (1)
    END

        -- @sql_string should have enough buffer to include 2n+2 max size for QUOTENAME(@schema_collection).
    DECLARE @sql_string nvarchar(328)
    DECLARE @param_definition nvarchar(16)
    SET @param_definition = N'@param xml'
    SET @sql_string = N'DECLARE @validated_xml XML (DOCUMENT ' + QUOTENAME(@schema_collection, '[') + N'); SELECT @validated_xml = @param';
    EXEC @retVal = sp_executesql @sql_string, @param_definition, @param = @parameters

    IF (@retVal <> 0)
    BEGIN
        RETURN (1)
    END
END
GO

---------------------------------------------------------------
-- Collection item
---------------------------------------------------------------
IF (OBJECT_ID(N'[dbo].[syscollector_collection_items_internal]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [dbo].[syscollector_collection_items_internal]...'
    CREATE TABLE [dbo].[syscollector_collection_items_internal] (
        collection_set_id            int NOT NULL,
        collection_item_id            int IDENTITY NOT NULL,
        collector_type_uid            uniqueidentifier NOT NULL,
        name                        sysname NOT NULL,
        name_id                        int NULL,                        -- sysmessage id of the name of the item (for localizing system collection set)
        frequency                    int NOT NULL,
        parameters                    xml NULL,
        CONSTRAINT [PK_syscollector_collection_items_internal] PRIMARY KEY CLUSTERED (collection_set_id ASC, collection_item_id ASC),
        CONSTRAINT [UQ_syscollector_collection_items_internal_name] UNIQUE (name)
        )
    ALTER TABLE syscollector_collection_items_internal
        ADD CONSTRAINT [FK_syscollector_collection_items_internal_syscollector_collection_sets_internal] FOREIGN KEY(collection_set_id)
        REFERENCES syscollector_collection_sets_internal (collection_set_id) ON DELETE CASCADE
    ALTER TABLE syscollector_collection_items_internal
        ADD CONSTRAINT [FK_syscollector_collection_items_internal_syscollector_collector_types_internal] FOREIGN KEY(collector_type_uid)
        REFERENCES syscollector_collector_types_internal (collector_type_uid) ON DELETE CASCADE
END ELSE BEGIN   
    IF (NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name = N'name_id' AND id = OBJECT_ID(N'[dbo].[syscollector_collection_items_internal]')))
    BEGIN
        PRINT 'Altering table [dbo].[syscollector_collection_items_internal]...'
        ALTER TABLE [dbo].[syscollector_collection_items_internal] ADD name_id int NULL
    END    
END
GO

IF (NOT OBJECT_ID(N'[dbo].[syscollector_collection_items]', 'V') IS NULL)
BEGIN
    PRINT 'Dropping view [dbo].[syscollector_collection_items]...'
    DROP VIEW [dbo].[syscollector_collection_items]
END
GO

PRINT 'Creating view [dbo].[syscollector_collection_items]...'
GO
CREATE VIEW [dbo].[syscollector_collection_items]
AS
    SELECT
        i.collection_set_id,
        i.collection_item_id,
        i.collector_type_uid,
        CASE 
            WHEN i.name_id IS NULL THEN i.name 
            ELSE FORMATMESSAGE(i.name_id)
        END AS name,        
        i.frequency,
        i.parameters
    FROM 
        [dbo].[syscollector_collection_items_internal] i
GO

-- This is a stored procedure of collector_type, but it is created here because it 
-- makes references to the collection item view
IF (NOT OBJECT_ID('[dbo].[sp_syscollector_delete_collector_type]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_delete_collector_type]...'
    DROP PROCEDURE [dbo].[sp_syscollector_delete_collector_type]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_delete_collector_type]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_delete_collector_type]
    @collector_type_uid            uniqueidentifier = NULL,
    @name                        sysname = NULL
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_delete_collector_type
    ELSE
        BEGIN TRANSACTION
    BEGIN TRY

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN (1)
    END
    
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collector_type @collector_type_uid OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    IF (EXISTS(SELECT * from dbo.syscollector_collection_items
        WHERE @collector_type_uid = collector_type_uid))
    BEGIN
        RAISERROR(14673, -1, -1, @name)
        RETURN (1)
    END
    
    DECLARE @schema_collection sysname
        -- @sql_string should have enough buffer to include 2n+2 max size for QUOTENAME(@schema_collection).
    DECLARE @sql_string nvarchar(285)
    SET @schema_collection = (SELECT schema_collection 
                                FROM syscollector_collector_types_internal
                                WHERE @collector_type_uid = collector_type_uid)
    SET @sql_string = N'DROP XML SCHEMA COLLECTION ' + QUOTENAME(@schema_collection, '[');
    EXEC sp_executesql @sql_string

    DELETE [dbo].[syscollector_collector_types_internal]
    WHERE collector_type_uid = @collector_type_uid

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_delete_collector_type

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_verify_collection_item]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_verify_collection_item]...'
    DROP PROCEDURE [dbo].[sp_syscollector_verify_collection_item]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_verify_collection_item]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_verify_collection_item]
    @collection_item_id        int = NULL OUTPUT,
    @name                    sysname = NULL OUTPUT
AS
BEGIN
    IF (@name IS NOT NULL)
    BEGIN
        -- Remove any leading/trailing spaces from parameters
        SET @name                    = NULLIF(LTRIM(RTRIM(@name)), N'')
    END

    IF (@collection_item_id IS NULL AND @name IS NULL)
    BEGIN
        RAISERROR(14624, -1, -1, '@collection_item_id, @name')
        RETURN(1)
    END

    IF (@collection_item_id IS NOT NULL AND @name IS NOT NULL)
    BEGIN
        IF (NOT EXISTS(SELECT *
                        FROM dbo.syscollector_collection_items
                        WHERE collection_item_id = @collection_item_id
                        AND name = @name))
        BEGIN
            DECLARE @errMsg NVARCHAR(196)
            SELECT @errMsg = CONVERT(NVARCHAR(36), @collection_item_id) + ', ' + @name
            RAISERROR(14262, -1, -1, '@collection_item_id, @name', @errMsg)
            RETURN(1)
        END
    END
    -- Check id
    ELSE IF (@collection_item_id IS NOT NULL)
    BEGIN
        SELECT @name = name
        FROM dbo.syscollector_collection_items
        WHERE (collection_item_id = @collection_item_id)
    
        -- the view would take care of all the permissions issues.
        IF (@name IS NULL) 
        BEGIN
            DECLARE @collection_item_id_as_char VARCHAR(36)
            SELECT @collection_item_id_as_char = CONVERT(VARCHAR(36), @collection_item_id)
            RAISERROR(14262, -1, -1, '@collection_item_id', @collection_item_id_as_char)
            RETURN(1) -- Failure
        END
    END
    -- Check name
    ELSE IF (@name IS NOT NULL)
    BEGIN
        -- get the corresponding collection_item_id (if the collection_item exists)
        SELECT @collection_item_id = collection_item_id
        FROM dbo.syscollector_collection_items
        WHERE (name = @name)

        -- the view would take care of all the permissions issues.
        IF (@collection_item_id IS NULL)
        BEGIN
            RAISERROR(14262, -1, -1, '@name', @name)
            RETURN(1) -- Failure
        END
    END
    RETURN (0)
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_create_collection_item]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_create_collection_item]...'
    DROP PROCEDURE [dbo].[sp_syscollector_create_collection_item]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_create_collection_item]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_create_collection_item]
    @collection_set_id        int,
    @collector_type_uid        uniqueidentifier,
    @name                    sysname,
    @frequency                int = 5,                -- set by default to the minimum frequency
    @parameters                xml = NULL,
    @collection_item_id        int OUTPUT
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_create_collection_item
    ELSE
        BEGIN TRANSACTION
    BEGIN TRY
        -- Security check (role membership)
        IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        BEGIN
            RAISERROR(14677, -1, -1, 'dc_admin')
            RETURN (1)
        END

        DECLARE @is_system bit
        SELECT    @is_system = is_system
        FROM dbo.syscollector_collection_sets
        WHERE collection_set_id = @collection_set_id
        
        IF (@is_system = 1)
        BEGIN
            -- cannot update, delete, or add new collection items to a system collection set
            RAISERROR(14696, -1, -1);
            RETURN (1)
        END

        SET @name            = NULLIF(LTRIM(RTRIM(@name)), N'')
        IF (@name IS NULL) 
        BEGIN
            RAISERROR(21263, -1, -1, '@name')
            RETURN (1)
        END
        
        IF (@frequency < 5)
        BEGIN
            DECLARE @frequency_as_char VARCHAR(36)
            SELECT @frequency_as_char = CONVERT(VARCHAR(36), @frequency)
            RAISERROR(21405, 16, -1, @frequency_as_char, '@frequency', 5)
            RETURN (1)
        END

        IF (NOT EXISTS(SELECT * from dbo.syscollector_collector_types
            WHERE @collector_type_uid = collector_type_uid))
        BEGIN
            DECLARE @collector_type_uid_as_char VARCHAR(36)
            SELECT @collector_type_uid_as_char = CONVERT(VARCHAR(36), @collector_type_uid)
            RAISERROR(14262, -1, -1, '@collector_type_uid', @collector_type_uid_as_char)
            RETURN (1)
        END
        
        IF (NOT EXISTS(SELECT * from dbo.syscollector_collection_sets
            WHERE @collection_set_id = collection_set_id))
        BEGIN
            DECLARE @collection_set_id_as_char VARCHAR(36)
            SELECT @collection_set_id_as_char = CONVERT(VARCHAR(36), @collection_set_id)
            RAISERROR(14262, -1, -1, '@collection_set_id', @collection_set_id_as_char)
            RETURN (1)
        END

        DECLARE @is_running bit
        SELECT    @is_running = is_running
        FROM dbo.syscollector_collection_sets
        WHERE collection_set_id = @collection_set_id
        IF (@is_running = 1)
        BEGIN
            RAISERROR(14675, -1, -1, @name)
            RETURN (1)
        END

        IF (@parameters IS NULL)
        BEGIN
            DECLARE @parameter_schema xml
            SELECT @parameter_schema = parameter_schema FROM syscollector_collector_types WHERE collector_type_uid = @collector_type_uid
            IF (@parameter_schema IS NOT NULL)    -- only allows parameters to be null if the collector type has a null schema
            BEGIN
                RAISERROR(21263, -1, -1, '@parameters')
                RETURN (1)
            END
        END
        ELSE IF (LTRIM(RTRIM(CONVERT(nvarchar(max), @parameters))) <> N'') -- don't check if the parameters are empty string
        BEGIN
            EXEC dbo.sp_syscollector_validate_xml @collector_type_uid = @collector_type_uid, @parameters = @parameters
        END

        INSERT INTO [dbo].[syscollector_collection_items_internal]
        (
            collection_set_id,
            collector_type_uid,
            name,
            frequency,
            parameters
        )
        VALUES
        (
            @collection_set_id,
            @collector_type_uid,
            @name,
            @frequency,
            @parameters
        )

        SET @collection_item_id = SCOPE_IDENTITY()

        IF (@collection_item_id IS NULL)
        BEGIN
            DECLARE @collection_item_id_as_char VARCHAR(36)
            SELECT @collection_item_id_as_char = CONVERT(VARCHAR(36), @collection_item_id)
            RAISERROR(14262, -1, -1, '@collection_item_id', @collection_item_id_as_char)
            RETURN (1)
        END

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_create_collection_item

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_update_collection_item_internal]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_update_collection_item_internal]...'
    DROP PROCEDURE [dbo].[sp_syscollector_update_collection_item_internal]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_update_collection_item_internal]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_update_collection_item_internal]
    @collection_item_id        int = NULL,
    @name                    sysname = NULL,
    @new_name                sysname = NULL,
    @frequency                int = NULL,
    @parameters                xml = NULL
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_update_collection_item
    ELSE
        BEGIN TRANSACTION
    BEGIN TRY
        UPDATE [dbo].[syscollector_collection_items_internal]
        SET
            name                = ISNULL(@new_name, name),
            frequency            = ISNULL(@frequency, frequency),
            parameters            = ISNULL(@parameters, parameters)
        WHERE @collection_item_id = collection_item_id

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_update_collection_item

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_update_collection_item]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_update_collection_item]...'
    DROP PROCEDURE [dbo].[sp_syscollector_update_collection_item]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_update_collection_item]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_update_collection_item]
    @collection_item_id        int = NULL,
    @name                    sysname = NULL,
    @new_name                sysname = NULL,
    @frequency                int = NULL,
    @parameters                xml = NULL
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END

    -- Security checks (restrict functionality for non-dc_admin-s)
    IF ((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1)) 
        AND (@new_name IS NOT NULL))
    BEGIN
        RAISERROR(14676, -1, -1, '@new_name', 'dc_admin')
        RETURN (1) -- Failure
    END
    IF ((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        AND (@parameters IS NOT NULL))
    BEGIN
        RAISERROR(14676, -1, -1, '@parameters', 'dc_admin')
        RETURN (1) -- Failure
    END

    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_item @collection_item_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (@retVal)

    IF (@frequency < 5)
    BEGIN
        DECLARE @frequency_as_char VARCHAR(36)
        SELECT @frequency_as_char = CONVERT(VARCHAR(36), @frequency)
        RAISERROR(21405, 16, -1, @frequency_as_char, '@frequency', 5)
        RETURN (1)
    END

    IF (LEN(@new_name) = 0)  -- can't rename to an empty string
    BEGIN
      RAISERROR(21263, -1, -1, '@new_name')
      RETURN(1) -- Failure
    END    

    -- Remove any leading/trailing spaces from parameters
    SET @new_name            = LTRIM(RTRIM(@new_name))

    DECLARE @collection_set_name sysname
    DECLARE @is_system              bit
    DECLARE @is_running             bit
    DECLARE @collector_type_uid     uniqueidentifier
    DECLARE @collection_set_id      int
    SELECT @is_running = s.is_running,
           @is_system = s.is_system,
           @collection_set_name = s.name,
           @collector_type_uid = i.collector_type_uid,
           @collection_set_id = s.collection_set_id
    FROM dbo.syscollector_collection_sets s,
         dbo.syscollector_collection_items i
    WHERE s.collection_set_id = i.collection_set_id
    AND i.collection_item_id = @collection_item_id

    IF (@is_system = 1 AND (@new_name IS NOT NULL OR @parameters IS NOT NULL))
    BEGIN
        -- cannot update, delete, or add new collection items to a system collection set
        RAISERROR(14696, -1, -1);
        RETURN (1)
    END

    IF (@parameters IS NOT NULL)
    BEGIN
        EXEC @retVal = dbo.sp_syscollector_validate_xml @collector_type_uid = @collector_type_uid, @parameters = @parameters
        IF (@retVal <> 0)
            RETURN (@retVal)
    END

    -- if the collection item is running, stop it before update
    IF (@is_running = 1)
    BEGIN
        EXEC @retVal = sp_syscollector_stop_collection_set @collection_set_id = @collection_set_id
        IF (@retVal <> 0)
            RETURN(1)
    END

    -- all conditions go, perform the update
    EXEC @retVal = sp_syscollector_update_collection_item_internal     
                            @collection_item_id = @collection_item_id,
                            @name = @name,
                            @new_name = @new_name,
                            @frequency = @frequency,
                            @parameters = @parameters
                        
    -- if you stopped the collection set, restart it
    IF (@is_running = 1)
    BEGIN
        EXEC @retVal = sp_syscollector_start_collection_set @collection_set_id = @collection_set_id
        IF (@retVal <> 0)
            RETURN (1)
    END
    
    RETURN (0)
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_delete_collection_item_internal]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_delete_collection_item_internal]...'
    DROP PROCEDURE [dbo].[sp_syscollector_delete_collection_item_internal]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_delete_collection_item_internal]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_delete_collection_item_internal]
    @collection_item_id         int,
    @name                       sysname
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_delete_collection_item
    ELSE
        BEGIN TRANSACTION
    BEGIN TRY
        DELETE [dbo].[syscollector_collection_items_internal]
        WHERE collection_item_id = @collection_item_id
          AND name = @name

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_delete_collection_item

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_delete_collection_item]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_delete_collection_item]...'
    DROP PROCEDURE [dbo].[sp_syscollector_delete_collection_item]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_delete_collection_item]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_delete_collection_item]
    @collection_item_id        int = NULL,
    @name                    sysname = NULL
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN(1) -- Failure
    END

    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_item @collection_item_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    DECLARE @is_system          bit
    DECLARE @is_running         bit
    DECLARE @collection_set_id  int
    SELECT @is_running = s.is_running,
           @is_system = s.is_system,
           @collection_set_id = s.collection_set_id
    FROM dbo.syscollector_collection_sets s,
         dbo.syscollector_collection_items i
    WHERE i.collection_item_id = @collection_item_id
    AND s.collection_set_id = i.collection_set_id

    IF (@is_system = 1)
    BEGIN
        -- cannot update, delete, or add new collection items to a system collection set
        RAISERROR(14696, -1, -1);
        RETURN(1)
    END

    IF (@is_running = 1)
    BEGIN
        -- stop the collection set if it was running
        EXEC @retVal = sp_syscollector_stop_collection_set @collection_set_id = @collection_set_id
        IF (@retVal <> 0)
            RETURN (1)
    END

    -- all checks go, perform delete
    EXEC @retVal = sp_syscollector_delete_collection_item_internal @collection_item_id = @collection_item_id, @name = @name
    IF (@retVal <> 0)
        RETURN (1)
        
    RETURN (0)
END
GO

---------------------------------------------------------------
-- Collection Set runtime procedures
---------------------------------------------------------------

IF (NOT OBJECT_ID('dbo.sp_syscollector_start_collection_set', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_start_collection_set]...'
    DROP PROCEDURE [dbo].[sp_syscollector_start_collection_set]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_start_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_start_collection_set]
    @collection_set_id        int = NULL,
    @name                     sysname = NULL
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_start_collection_set
    ELSE
        BEGIN TRANSACTION

    BEGIN TRY

    -- Security check (role membership)
    EXECUTE AS CALLER;
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END
    REVERT;

    -- check if SQL Server Agent is enabled
    DECLARE @agent_enabled int
    SELECT @agent_enabled = CAST(value_in_use AS int) FROM sys.configurations WHERE name = N'Agent XPs'
    IF @agent_enabled <> 1
    BEGIN
        RAISERROR(14688, -1, -1)
        RETURN (1)
    END

    -- check if MDW is setup
    DECLARE @instance_name sysname
    SELECT @instance_name = CONVERT(sysname,parameter_value)
    FROM [msdb].[dbo].[syscollector_config_store_internal]
    WHERE parameter_name = N'MDWInstance'
    IF (@instance_name IS NULL)
    BEGIN
        RAISERROR(14689, -1, -1)
        RETURN (1)
    END    
    DECLARE @database_name sysname
    SELECT @database_name = CONVERT(sysname,parameter_value)
    FROM [msdb].[dbo].[syscollector_config_store_internal]
    WHERE parameter_name = N'MDWDatabase'
    IF (@database_name IS NULL)
    BEGIN
        RAISERROR(14689, -1, -1)
        RETURN (1)
    END

    -- Verify the input parameters
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    -- Check if the collection set does not have any collection items
    IF NOT EXISTS(
        SELECT i.collection_item_id 
        FROM [dbo].[syscollector_collection_sets] AS s
        INNER JOIN [dbo].[syscollector_collection_items] AS i
            ON(s.collection_set_id = i.collection_set_id)
        WHERE s.collection_set_id = @collection_set_id
    )
    BEGIN
        RAISERROR(14685, 10, -1, @name) -- Raise a warning message
        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END

    DECLARE @proxy_id int;
    DECLARE @collection_job_id uniqueidentifier
    DECLARE @upload_job_id uniqueidentifier
    DECLARE @schedule_uid uniqueidentifier;

    SELECT 
        @collection_job_id = collection_job_id, 
        @upload_job_id = upload_job_id, 
        @proxy_id = proxy_id,
        @schedule_uid = schedule_uid
    FROM [dbo].[syscollector_collection_sets_internal]
    WHERE collection_set_id = @collection_set_id;

    -- Check if the set does not have a proxy
    IF (@proxy_id IS NULL)
    BEGIN
        -- to start a collection set without a proxy, the caller has to be a sysadmin
        EXECUTE AS CALLER;
            IF (NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
            BEGIN
                REVERT;
                RAISERROR(14692, -1, -1, @name)
                RETURN (1)
            END
        REVERT;
    END

    -- Starting a collection set requires a schedule
    IF @schedule_uid IS NULL
    BEGIN
        RAISERROR(14693, -1, -1)
        RETURN (1)
    END

    -- Check if we have jobs created, and if not, create them
    IF (@collection_job_id IS NULL AND @upload_job_id IS NULL)
    BEGIN
        -- Jobs not created yet, go and crete them
        -- We need to get some data from collection_sets table 
        -- before we do that.
        DECLARE @collection_set_uid uniqueidentifier;
        DECLARE @schedule_id int;
        DECLARE @collection_mode int;

        SELECT 
            @collection_set_uid = collection_set_uid,
            @collection_mode = collection_mode
        FROM
            [dbo].[syscollector_collection_sets_internal]
        WHERE
            collection_set_id = @collection_set_id;
        
        -- Sanity check
        -- Make sure the proxy and schedule are still there, someone could have
        -- remove them between when the collection set was created and now.
        IF (@proxy_id IS NOT NULL)
        BEGIN
            DECLARE @proxy_name sysname
            
            -- this will throw if the id does not exist
            EXEC @retVal = sp_verify_proxy_identifiers '@proxy_name', '@proxy_id', @proxy_name OUTPUT, @proxy_id OUTPUT
            IF (@retVal <> 0)
                RETURN (1)
        END

        SELECT @schedule_id = schedule_id FROM sysschedules_localserver_view WHERE @schedule_uid = schedule_uid
        EXEC @retVal = sp_verify_schedule_identifiers  @name_of_name_parameter = '@schedule_name',
                                                       @name_of_id_parameter   = '@schedule_id',
                                                       @schedule_name          = NULL,
                                                       @schedule_id            = @schedule_id,
                                                       @owner_sid              = NULL,
                                                       @orig_server_id         = NULL 
        IF (@retVal <> 0)
            RETURN (1)

        -- Go add the jobs
        EXEC [dbo].[sp_syscollector_create_jobs]
            @collection_set_id    = @collection_set_id,
            @collection_set_uid = @collection_set_uid,
            @collection_set_name = @name,
            @proxy_id            = @proxy_id,
            @schedule_id        = @schedule_id,
            @collection_mode    = @collection_mode,
            @collection_job_id    = @collection_job_id OUTPUT,
            @upload_job_id        = @upload_job_id OUTPUT

        -- Finally, update the collection_sets table
        UPDATE [dbo].[syscollector_collection_sets_internal]
        SET
            upload_job_id        = @upload_job_id,
            collection_job_id    = @collection_job_id
        WHERE @collection_set_id = collection_set_id
    END

    -- Update the is_running column for this collection set
    -- There is a trigger defined for that table that turns on
    -- the collection and upload jobs in response to that bit
    -- changing.
    UPDATE [dbo].[syscollector_collection_sets_internal]
    SET is_running = 1
    WHERE collection_set_id = @collection_set_id

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)

    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_start_collection_set

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)        
    END CATCH
END
GO


IF (NOT OBJECT_ID('dbo.sp_syscollector_stop_collection_set', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_stop_collection_set]...'
    DROP PROCEDURE [dbo].[sp_syscollector_stop_collection_set]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_stop_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_stop_collection_set]
    @collection_set_id        int = NULL,
    @name                     sysname = NULL,
    @stop_collection_job      bit = 1           -- Do we need to stop the collection job, YES by default
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END

    -- Verify the input parameters
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    IF (@stop_collection_job = 1)
    BEGIN
        DECLARE @collection_mode INT
        DECLARE @collection_job_id UNIQUEIDENTIFIER

        SELECT  @collection_mode = collection_mode, @collection_job_id = collection_job_id
        FROM    dbo.syscollector_collection_sets
        WHERE   collection_set_id = @collection_set_id
       
        DECLARE @is_collection_job_running INT
        EXECUTE [dbo].[sp_syscollector_get_collection_set_execution_status]
                @collection_set_id = @collection_set_id,
                @is_collection_running = @is_collection_job_running OUTPUT

        -- Stop the collection job if we are in cached mode, this should signal the runtime to exit
        IF (@is_collection_job_running = 1      -- Collection job is running
            AND @collection_mode = 0
            AND @stop_collection_job = 1)
        BEGIN
            EXEC sp_stop_job @job_id = @collection_job_id
        END
    END


    -- Update the is_running column for this collection set
    -- There is a trigger defined for that table that turns off
    -- the collection and uplaod jobs in response to that bit
    -- changing.
    UPDATE [dbo].[syscollector_collection_sets_internal]
    SET is_running = 0
    WHERE collection_set_id = @collection_set_id

    RETURN (0)
END
GO


IF (NOT OBJECT_ID('[dbo].[sp_syscollector_get_collection_set_execution_status]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_get_collection_set_execution_status]...'
    DROP PROCEDURE [dbo].[sp_syscollector_get_collection_set_execution_status]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_get_collection_set_execution_status]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_get_collection_set_execution_status]
    @collection_set_id            int,
    @is_running                    int = NULL OUTPUT,
    @is_collection_running        int = NULL OUTPUT,
    @collection_job_state        int = NULL OUTPUT,
    @is_upload_running            int = NULL OUTPUT,
    @upload_job_state            int = NULL OUTPUT
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_get_execution_status
    ELSE
        BEGIN TRANSACTION

    BEGIN TRY

    DECLARE @xp_results TABLE (job_id             UNIQUEIDENTIFIER NOT NULL,
                            last_run_date         INT              NOT NULL,
                            last_run_time         INT              NOT NULL,
                            next_run_date         INT              NOT NULL,
                            next_run_time         INT              NOT NULL,
                            next_run_schedule_id  INT              NOT NULL,
                            requested_to_run      INT              NOT NULL, -- BOOL
                            request_source        INT              NOT NULL,
                            request_source_id     sysname          COLLATE database_default NULL,
                            running               INT              NOT NULL, -- BOOL
                            current_step          INT              NOT NULL,
                            current_retry_attempt INT              NOT NULL,
                            job_state             INT              NOT NULL)


    DECLARE @is_sysadmin INT
    SELECT @is_sysadmin = ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0)

    DECLARE @collection_job_id UNIQUEIDENTIFIER
    DECLARE @upload_job_id UNIQUEIDENTIFIER
    
    SELECT @collection_job_id = collection_job_id, @upload_job_id = upload_job_id 
    FROM dbo.syscollector_collection_sets WHERE collection_set_id = @collection_set_id

    DECLARE @agent_enabled int
    SELECT @agent_enabled = CAST(value_in_use AS int) FROM sys.configurations WHERE name = N'Agent XPs'

    IF (@agent_enabled <> 0)
    BEGIN
        INSERT  INTO @xp_results    
        EXECUTE master.dbo.xp_sqlagent_enum_jobs @is_sysadmin, N'', @upload_job_id

        INSERT  INTO @xp_results    
        EXECUTE master.dbo.xp_sqlagent_enum_jobs @is_sysadmin, N'', @collection_job_id
    END

    SELECT @is_running = is_running FROM dbo.syscollector_collection_sets WHERE collection_set_id = @collection_set_id
    SELECT @is_collection_running = running, @collection_job_state = job_state FROM @xp_results WHERE job_id = @collection_job_id
    SELECT @is_upload_running = running, @upload_job_state = job_state FROM @xp_results WHERE job_id = @upload_job_id

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)

    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_get_execution_status

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)        
    END CATCH
END
GO


IF (NOT OBJECT_ID('dbo.sp_syscollector_upload_collection_set', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_upload_collection_set]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_upload_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_upload_collection_set]
    @collection_set_id        int = NULL,
    @name                     sysname = NULL
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    EXECUTE AS CALLER;
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END
    REVERT;

    -- Verify the input parameters
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    -- Make sure the collection set is running and is in the right mode
    DECLARE @is_running bit
    DECLARE @collection_mode smallint

    SELECT 
        @is_running = is_running,
        @collection_mode = collection_mode            
    FROM [dbo].[syscollector_collection_sets]
    WHERE collection_set_id = @collection_set_id

    IF (@collection_mode <> 0)
    BEGIN
        RAISERROR(14694, -1, -1, @name)
        RETURN(1)
    END

    IF (@is_running = 0)
    BEGIN
        RAISERROR(14674, -1, -1, @name)
        RETURN(1)
    END

    -- Make sure the collector is enabled
    EXEC @retVal = [dbo].[sp_syscollector_verify_collector_state] @desired_state = 1
    IF (@retVal <> 0)
        RETURN (1)

    -- Check if the upload job is currently running
    DECLARE @is_upload_job_running INT
    EXECUTE [dbo].[sp_syscollector_get_collection_set_execution_status]
        @collection_set_id = @collection_set_id,
        @is_upload_running = @is_upload_job_running OUTPUT

    IF (@is_upload_job_running = 0)
    BEGIN
        -- Job is not running, we can trigger it now
        DECLARE @job_id UNIQUEIDENTIFIER
        SELECT @job_id = upload_job_id 
            FROM [dbo].[syscollector_collection_sets] 
            WHERE collection_set_id = @collection_set_id

        EXEC @retVal = sp_start_job @job_id = @job_id
        IF (@retVal <> 0)
            RETURN (1)
    END

    RETURN (0)
END
GO


IF (NOT OBJECT_ID('dbo.sp_syscollector_run_collection_set', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_run_collection_set]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_run_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_run_collection_set]
    @collection_set_id        int = NULL,
    @name                     sysname = NULL
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_run_collection_set
    ELSE
        BEGIN TRANSACTION

    BEGIN TRY


    -- Security check (role membership)
    EXECUTE AS CALLER;
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END
    REVERT;

    -- Verify the input parameters
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    -- Make sure the collection set is in the right mode
    DECLARE @collection_mode smallint
    DECLARE @collection_set_uid uniqueidentifier;

    SELECT 
        @collection_set_uid = collection_set_uid,
        @collection_mode = collection_mode            
    FROM [dbo].[syscollector_collection_sets]
    WHERE collection_set_id = @collection_set_id

    IF (@collection_mode <> 1)
    BEGIN
        RAISERROR(14695, -1, -1, @name)
        RETURN(1)
    END


    -- Make sure the collector is enabled
    EXEC @retVal = [dbo].[sp_syscollector_verify_collector_state] @desired_state = 1
    IF (@retVal <> 0)
        RETURN (1)

    -- check if SQL Server Agent is enabled
    DECLARE @agent_enabled int
    SELECT @agent_enabled = CAST(value_in_use AS int) FROM sys.configurations WHERE name = N'Agent XPs'
    IF @agent_enabled <> 1
    BEGIN
        RAISERROR(14688, -1, -1)
        RETURN (1)
    END

    -- check if MDW is setup
    DECLARE @instance_name sysname
    SELECT @instance_name = CONVERT(sysname,parameter_value)
    FROM [msdb].[dbo].[syscollector_config_store_internal]
    WHERE parameter_name = N'MDWInstance'
    IF (@instance_name IS NULL)
    BEGIN
        RAISERROR(14689, -1, -1)
        RETURN (1)
    END    
    DECLARE @database_name sysname
    SELECT @database_name = CONVERT(sysname,parameter_value)
    FROM [msdb].[dbo].[syscollector_config_store_internal]
    WHERE parameter_name = N'MDWDatabase'
    IF (@database_name IS NULL)
    BEGIN
        RAISERROR(14689, -1, -1)
        RETURN (1)
    END

    -- Make sure the jobs are created for the collection set
    -- Verify the input parameters
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    -- Check if the collection set does not have any collection items
    IF NOT EXISTS(
        SELECT i.collection_item_id 
        FROM [dbo].[syscollector_collection_sets] AS s
        INNER JOIN [dbo].[syscollector_collection_items] AS i
            ON(s.collection_set_id = i.collection_set_id)
        WHERE s.collection_set_id = @collection_set_id
    )
    BEGIN
        RAISERROR(14685, 10, -1, @name) -- Raise a warning message
        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END

    DECLARE @proxy_id int;
    DECLARE @collection_job_id uniqueidentifier
    DECLARE @upload_job_id uniqueidentifier

    SELECT @collection_job_id = collection_job_id, 
           @upload_job_id = upload_job_id, 
           @proxy_id = proxy_id
    FROM [dbo].[syscollector_collection_sets_internal]
    WHERE collection_set_id = @collection_set_id;

    -- Check if the set does not have a proxy
    IF (@proxy_id IS NULL)
    BEGIN
        -- to start a collection set without a proxy, the caller has to be a sysadmin
        EXECUTE AS CALLER;
            IF (NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
            BEGIN
                REVERT;
                RAISERROR(14692, -1, -1, @name)
                RETURN (1)
            END
        REVERT;
    END

    -- Check if we have jobs created, and if not, create them
    DECLARE @jobs_just_created bit
    SET @jobs_just_created = 0  -- False until further notice
    IF (@collection_job_id IS NULL AND @upload_job_id IS NULL)
    BEGIN
        DECLARE @schedule_id int;
        DECLARE @schedule_uid uniqueidentifier;

        SELECT 
            @schedule_uid = schedule_uid
        FROM [dbo].[syscollector_collection_sets_internal]
        WHERE collection_set_id = @collection_set_id;
        
        IF (@schedule_uid IS NOT NULL)
        BEGIN
            SELECT @schedule_id = schedule_id FROM sysschedules_localserver_view WHERE @schedule_uid = schedule_uid
        END

        -- Sanity check
        -- Make sure the proxy and schedule are still there, someone could have
        -- remove them between when the collection set was created and now.
        IF (@proxy_id IS NOT NULL)
        BEGIN
            DECLARE @proxy_name sysname
            
            -- this will throw an error of proxy_id does not exist
            EXEC @retVal = msdb.dbo.sp_verify_proxy_identifiers '@proxy_name', '@proxy_id', @proxy_name OUTPUT, @proxy_id OUTPUT
            IF (@retVal <> 0)
                RETURN (0)
        END

        IF (@schedule_uid IS NOT NULL)
        BEGIN
            EXEC @retVal = sp_verify_schedule_identifiers  @name_of_name_parameter = '@schedule_name',
                                                           @name_of_id_parameter   = '@schedule_id',
                                                           @schedule_name          = NULL,
                                                           @schedule_id            = @schedule_id,
                                                           @owner_sid              = NULL,
                                                           @orig_server_id         = NULL 
            IF (@retVal <> 0)
                RETURN (1)
        END

        -- Go add the jobs
        EXEC [dbo].[sp_syscollector_create_jobs]
            @collection_set_id    = @collection_set_id,
            @collection_set_uid = @collection_set_uid,
            @collection_set_name = @name,
            @proxy_id            = @proxy_id,
            @schedule_id        = @schedule_id,
            @collection_mode    = @collection_mode,
            @collection_job_id    = @collection_job_id OUTPUT,
            @upload_job_id        = @upload_job_id OUTPUT

        -- Finally, update the collection_sets table
        UPDATE [dbo].[syscollector_collection_sets_internal]
        SET
            upload_job_id        = @upload_job_id,
            collection_job_id    = @collection_job_id
        WHERE @collection_set_id = collection_set_id

        SET @jobs_just_created = 1  -- Record the fact that we have just created the job here
    END

    IF (@jobs_just_created = 1)
    BEGIN  -- We created the jobs here in this transaction, post a request for agent to start as soon as we commit
        EXEC @retVal = sp_start_job @job_id = @upload_job_id
        IF (@retVal <> 0)
            RETURN (1)
    END
    ELSE   
    BEGIN
        -- The jobs were created previously, we need to guard against it already executing by the schedule
        -- So, check if the job is currently running before asking agent to start it
        DECLARE @is_upload_job_running INT
        EXECUTE [dbo].[sp_syscollector_get_collection_set_execution_status]
            @collection_set_id = @collection_set_id,
            @is_upload_running = @is_upload_job_running OUTPUT

        IF (@is_upload_job_running = 0)
        BEGIN
            -- Job is not running, we can trigger it now
            -- We run only one job because for this (non-cached) mode there is only one job. The same id is stored
            -- as collection and upload job id
            EXEC @retVal = sp_start_job @job_id = @upload_job_id
            IF (@retVal <> 0)
                RETURN (1)
        END
    END

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)

    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_run_collection_set

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)        
    END CATCH
END
GO


PRINT 'Creating trigger [dbo].[syscollector_collection_set_is_running_update_trigger] on [dbo].[syscollector_collection_sets_internal]'
GO
IF NOT OBJECT_ID('dbo.syscollector_collection_set_is_running_update_trigger', 'TR') IS NULL
    DROP TRIGGER [dbo].[syscollector_collection_set_is_running_update_trigger]
GO

CREATE TRIGGER [dbo].[syscollector_collection_set_is_running_update_trigger] on [dbo].[syscollector_collection_sets_internal]
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
FOR UPDATE
AS
BEGIN
    DECLARE @collection_set_id INT
    DECLARE @is_running BIT
    DECLARE @old_is_running BIT
    DECLARE @collection_mode SMALLINT

    IF (NOT UPDATE (is_running))
       RETURN

    DECLARE @collector_enabled int
    SET @collector_enabled = CONVERT(int, (SELECT parameter_value FROM dbo.syscollector_config_store_internal
                            WHERE parameter_name = 'CollectorEnabled'))
    IF @collector_enabled = 0
    BEGIN
        -- flipping the is_running bit has no effect when the collector is disabled
        RAISERROR(14682, 10, -1) -- severity 10 emits a warning
    END
    ELSE
    BEGIN
        DECLARE inserted_cursor CURSOR LOCAL FOR 
            SELECT collection_set_id, is_running, collection_mode
            FROM inserted 

        OPEN inserted_cursor
        FETCH inserted_cursor INTO @collection_set_id, @is_running, @collection_mode
        
        WHILE @@FETCH_STATUS = 0 
        BEGIN
            SELECT @old_is_running = is_running FROM deleted WHERE collection_set_id = @collection_set_id

            -- If there is a change in the state, handle accordingly
            IF (@old_is_running <> @is_running)
            BEGIN
                IF (@is_running = 0)
                BEGIN
                    EXEC dbo.sp_syscollector_stop_collection_set_jobs @collection_set_id = @collection_set_id
                END
                ELSE IF (@is_running = 1)
                BEGIN              
                    EXEC dbo.sp_syscollector_start_collection_set_jobs @collection_set_id = @collection_set_id
                END
            END

            FETCH inserted_cursor INTO @collection_set_id, @is_running, @collection_mode
        END    

        CLOSE inserted_cursor
        DEALLOCATE inserted_cursor
    END
END
GO

PRINT ''
PRINT 'Creating stored procedure syscollector_stop_collection_set_jobs...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_stop_collection_set_jobs', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_stop_collection_set_jobs]
GO

CREATE PROCEDURE [dbo].[sp_syscollector_stop_collection_set_jobs]
    @collection_set_id    int
AS
BEGIN
    SET NOCOUNT ON

    -- Collection set stopped. Make sure the following happens:
    -- 1. Detach upload schedule
    -- 2. Collection job is stopped
    -- 3. Upload job is kicked once if it is not running now
    -- 4. Collection and upload jobs are disabled
    -- 5. Attach upload schedule
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_stop_collection_set_jobs
    ELSE
        BEGIN TRANSACTION
    
    BEGIN TRY
        DECLARE @collection_job_id    uniqueidentifier
        DECLARE @upload_job_id        uniqueidentifier
        DECLARE @schedule_uid        uniqueidentifier
        DECLARE @collection_mode    smallint

        SELECT    @collection_job_id = collection_job_id, 
                @upload_job_id = upload_job_id, 
                @collection_mode = collection_mode, 
                @schedule_uid = schedule_uid
        FROM dbo.syscollector_collection_sets
        WHERE collection_set_id = @collection_set_id

        DECLARE @schedule_id int
        IF (@collection_mode != 1)  -- detach schedule for continuous and snapshot modes
        BEGIN
            SELECT @schedule_id = schedule_id from sysschedules_localserver_view WHERE @schedule_uid = schedule_uid
            IF (@schedule_id IS NULL)
            BEGIN
                DECLARE @schedule_uid_as_char VARCHAR(36)
                SELECT @schedule_uid_as_char = CONVERT(VARCHAR(36), @schedule_uid)
                RAISERROR(14262, -1, -1, '@schedule_uid', @schedule_uid_as_char)
                RETURN (1)
            END

            -- Detach schedule
            EXEC dbo.sp_detach_schedule
                @job_id            = @upload_job_id,
                @schedule_id    = @schedule_id,
                @delete_unused_schedule = 0    -- do not delete schedule, might need to attach it back again
        END

        DECLARE @is_upload_job_running INT
        EXECUTE [dbo].[sp_syscollector_get_collection_set_execution_status]
                @collection_set_id = @collection_set_id,
                @is_upload_running = @is_upload_job_running OUTPUT

        -- Upload job (needs to be kicked off for continuous collection mode)
        IF (@is_upload_job_running = 0            -- If the upload job is not already in progress
            AND @collection_mode = 0)           -- don't do it for adhoc or snapshot, they will handle it on their own
        BEGIN
            EXEC sp_start_job @job_id = @upload_job_id, @error_flag = 0
        END

        -- Disable both jobs
        EXEC sp_update_job @job_id = @collection_job_id, @enabled = 0
        EXEC sp_update_job @job_id = @upload_job_id, @enabled = 0

        IF (@collection_mode != 1)    -- attach schedule for continuous and snapshot modes
        BEGIN
            -- Attach schedule
            EXEC dbo.sp_attach_schedule
                @job_id            = @upload_job_id,
                @schedule_id    = @schedule_id
        END

        -- Log the stop of the collection set
        EXEC sp_syscollector_event_oncollectionstop @collection_set_id = @collection_set_id

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_stop_collection_set_jobs

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO


PRINT ''
PRINT 'Creating stored procedure syscollector_start_collection_set_jobs...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_start_collection_set_jobs', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_start_collection_set_jobs]
GO

CREATE PROCEDURE [dbo].[sp_syscollector_start_collection_set_jobs]
    @collection_set_id    int
AS
BEGIN
    SET NOCOUNT ON

    -- Collection set started. Make sure the following happens:
    -- 1. Collection and upload jobs are enabled
    -- 2. Collection job is started if it is defined as running continously

    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_start_collection_set_jobs
    ELSE
        BEGIN TRANSACTION

    BEGIN TRY
        
        -- Log the start of the collection set
        DECLARE @log_id bigint
        EXEC sp_syscollector_event_oncollectionstart @collection_set_id = @collection_set_id, @log_id = @log_id OUTPUT

        -- Enable both jobs
        DECLARE @collection_job_id    uniqueidentifier
        DECLARE @upload_job_id        uniqueidentifier
        DECLARE @collection_mode    smallint

        SELECT    @collection_job_id = collection_job_id,
                @upload_job_id = upload_job_id,
                @collection_mode = collection_mode
        FROM dbo.syscollector_collection_sets
        WHERE collection_set_id = @collection_set_id

        EXEC sp_update_job @job_id = @collection_job_id, @enabled = 1
        EXEC sp_update_job @job_id = @upload_job_id, @enabled = 1

        -- Start the collection job if you are in ad hoc or continuous modes
        IF (@collection_mode = 1 OR @collection_mode = 0)
        BEGIN
            EXEC sp_start_job @job_id = @collection_job_id, @error_flag = 0
        END

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_start_collection_set_jobs

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO


---------------------------------------------------------------
-- Collection Set execution log                                        
---------------------------------------------------------------

IF (OBJECT_ID('dbo.syscollector_execution_log_internal', 'U') IS NULL)
BEGIN
    PRINT ''
    PRINT 'Creating table syscollector_execution_log_internal...'

    CREATE TABLE [dbo].[syscollector_execution_log_internal]
    (
        log_id                  bigint          IDENTITY(1,1) NOT NULL,     -- Unique log entry id. Each execution log gets a new one
        parent_log_id           bigint          NULL,                       -- Id of the parent execution context. NULL for the root node. 
        collection_set_id       int             NOT NULL,                   -- Id of the collection set that owns this entry
        collection_item_id      int             NULL,                       -- Collection item id
        start_time              datetime        NOT NULL,                   -- Collection set or package execution start time
        last_iteration_time     datetime        NULL,                       -- For continously running packages, last time an interation has been started
        finish_time             datetime        NULL,                       -- Collection set or package execution end time
        runtime_execution_mode  smallint        NULL,                       -- 0 - Collection, 1 - Upload
        status                  smallint        NOT NULL,                   -- 0 - Running, 1 - Finished, 2 - Error, 3 - Warning
        operator                nvarchar(128)   NOT NULL,                   -- Name of the user running the collection set or package
        package_id              uniqueidentifier NULL,                      -- Id of a package, NULL for collection sets
        package_execution_id    uniqueidentifier NULL,                      -- Execution Id generated by SSIS for each package execution. Use to join events from sysssislog. NULL for collection sets
        failure_message         nvarchar(2048)   NULL                       -- Message that indicates package failure. NULL if no failure

        CONSTRAINT [PK_syscollector_execution_log] PRIMARY KEY CLUSTERED (log_id ASC),
        CONSTRAINT [FK_syscollector_execution_log_collection_set_id] FOREIGN KEY (collection_set_id) 
            REFERENCES [dbo].[syscollector_collection_sets_internal] (collection_set_id),
    )
END
go

PRINT ''
PRINT 'Creating view syscollector_execution_log...'

IF (NOT OBJECT_ID('dbo.syscollector_execution_log', 'V') IS NULL)
    DROP VIEW [dbo].[syscollector_execution_log]
go

CREATE VIEW [dbo].[syscollector_execution_log] AS
    SELECT 
        log_id, 
        ISNULL(parent_log_id, 0) as parent_log_id, 
        collection_set_id, 
        collection_item_id,
        start_time,
        last_iteration_time,
        finish_time,
        runtime_execution_mode,
        [status],
        operator,
        package_id,
        msdb.dbo.fn_syscollector_get_package_path(package_id) as package_name,
        package_execution_id,
        failure_message
    FROM dbo.syscollector_execution_log_internal;
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_delete_jobs]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_delete_jobs]...'
    DROP PROCEDURE [dbo].[sp_syscollector_delete_jobs]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_delete_jobs]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_delete_jobs]
    @collection_job_id        uniqueidentifier,
    @upload_job_id            uniqueidentifier,
    @schedule_id            int = NULL,
    @collection_mode        smallint
AS
BEGIN
    -- delete the jobs corresponding to the collection set
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_syscollector_delete_jobs
    ELSE
        BEGIN TRANSACTION
    
    BEGIN TRY

    IF (@collection_mode = 1) -- non-cached mode
    BEGIN
        IF (@upload_job_id IS NOT NULL)
        BEGIN
            -- note, upload job id = collection job id in this mode
            IF (@schedule_id IS NOT NULL)
            BEGIN
                EXEC dbo.sp_detach_schedule
                    @job_id            = @upload_job_id, 
                    @schedule_id    = @schedule_id,
                    @delete_unused_schedule = 0
            END

            EXEC dbo.sp_delete_jobserver
                @job_id            = @upload_job_id,
                @server_name    = N'(local)'

            EXEC dbo.sp_delete_job 
                @job_id            = @upload_job_id
        END
    END
    ELSE -- cached mode
    BEGIN
        -- detach schedules, delete job servers, then delete jobs
        IF (@upload_job_id IS NOT NULL)
        BEGIN
            EXEC dbo.sp_detach_schedule
                @job_id            = @upload_job_id, 
                @schedule_id    = @schedule_id,
                @delete_unused_schedule = 0

            EXEC dbo.sp_delete_jobserver
                @job_id            = @upload_job_id,
                @server_name    = N'(local)'

            EXEC dbo.sp_delete_job 
                @job_id            = @upload_job_id
        END

        IF (@collection_job_id IS NOT NULL)
        BEGIN
            EXEC dbo.sp_detach_schedule
                @job_id            = @collection_job_id, 
                @schedule_name    = N'RunAsSQLAgentServiceStartSchedule',
                @delete_unused_schedule = 0

            EXEC dbo.sp_delete_jobserver
                @job_id            = @collection_job_id,
                @server_name    = N'(local)'

            EXEC dbo.sp_delete_job 
                @job_id            = @collection_job_id
        END
    END

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_syscollector_delete_jobs

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO


---------------------------------------------------------------
-- Collection Set execution stats                                      
---------------------------------------------------------------

IF (OBJECT_ID('dbo.syscollector_execution_stats_internal', 'U') IS NULL)
BEGIN
    PRINT ''
    PRINT 'Creating table syscollector_execution_stats_internal...'

    CREATE TABLE [dbo].[syscollector_execution_stats_internal]
    (
        log_id                  bigint          NOT NULL,                   -- Log_id of the package that inserts the row
        task_name               nvarchar(128)   NOT NULL,                   -- Name of the task/component in the package that reports the execution stats
        execution_row_count_in  int             NULL,                       -- Number of rows that entered the data flow from its source transformations
        execution_row_count_out int             NULL,                       -- Number of rows that exited the data flow into its destination transformations
        execution_row_count_errors int          NULL,                       -- Number of rows that were re-directed to an error output due to processing errors
        execution_time_ms       int             NULL,                       -- Execution time of the data flow
        log_time                datetime        NOT NULL                    -- Date and time when this entry was logged

        CONSTRAINT [PK_syscollector_execution_stats] PRIMARY KEY CLUSTERED (log_id ASC, task_name ASC, log_time DESC),
        CONSTRAINT [FK_syscollector_execution_stats_log_id] FOREIGN KEY (log_id) REFERENCES [dbo].[syscollector_execution_log_internal] (log_id) ON DELETE CASCADE
    )
END
go

PRINT ''
PRINT 'Creating view syscollector_execution_stats...'

IF (NOT OBJECT_ID('dbo.syscollector_execution_stats', 'V') IS NULL)
    DROP VIEW [dbo].[syscollector_execution_stats]
go

CREATE VIEW [dbo].[syscollector_execution_stats] AS
    SELECT
        log_id,
        task_name,
        execution_row_count_in,
        execution_row_count_out,
        execution_row_count_errors,
        execution_time_ms,
        log_time
    FROM dbo.syscollector_execution_stats_internal
go

---------------------------------------------------------------
-- Logging stored procedures  
---------------------------------------------------------------

PRINT ''
PRINT 'Creating stored procedure sp_syscollector_verify_event_log_id...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_verify_event_log_id', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_verify_event_log_id]
go

CREATE PROCEDURE [dbo].[sp_syscollector_verify_event_log_id]
    @log_id bigint,
    @allow_collection_set_id bit = 0
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @log_id_as_char VARCHAR(36)

    IF (@log_id IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@log_id')
        RETURN (1)
    END
    ELSE IF @allow_collection_set_id = 0
    BEGIN
        IF (NOT EXISTS (SELECT log_id FROM dbo.syscollector_execution_log WHERE log_id = @log_id AND package_id IS NOT NULL))
        BEGIN
            SELECT @log_id_as_char = CONVERT(VARCHAR(36), @log_id)

            RAISERROR(14262, -1, -1, '@log_id', @log_id_as_char)
            RETURN (1)
        END
    END
    ELSE
    BEGIN
        IF (NOT EXISTS (SELECT log_id FROM dbo.syscollector_execution_log WHERE log_id = @log_id))
        BEGIN
            SELECT @log_id_as_char = CONVERT(VARCHAR(36), @log_id)

            RAISERROR(14262, -1, -1, '@log_id', @log_id_as_char)
            RETURN (1)
        END
    END

    RETURN (0)
END
go

PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_oncollectionstart...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_oncollectionstart', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_oncollectionstart]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_oncollectionstart]
    @collection_set_id int,
    @operator nvarchar(128) = NULL,
    @log_id bigint OUTPUT
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END

    -- Verify parameters
    --

    -- Check the collection_set_id
    IF (@collection_set_id IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@collection_set_id')
        RETURN (1)
    END
    ELSE IF (NOT EXISTS (SELECT collection_set_id FROM dbo.syscollector_collection_sets WHERE collection_set_id = @collection_set_id))
    BEGIN
        DECLARE @collection_set_id_as_char VARCHAR(36)
        SELECT @collection_set_id_as_char = CONVERT(VARCHAR(36), @collection_set_id)

        RAISERROR(14262, -1, -1, '@collection_set_id', @collection_set_id_as_char)
        RETURN (1)
    END


    -- Default operator to currently logged in user
    SET @operator = NULLIF(LTRIM(RTRIM(@operator)), '')
    SET @operator = ISNULL(@operator, suser_sname())

    -- Insert the log record
    --
    INSERT INTO dbo.syscollector_execution_log_internal (
        parent_log_id, 
        collection_set_id, 
        start_time,
        last_iteration_time,
        finish_time,
        runtime_execution_mode,
        [status],
        operator,
        package_id,
        package_execution_id,
        failure_message
    ) VALUES (
        NULL,
        @collection_set_id,
        GETDATE(),
        NULL,
        NULL,
        NULL,
        0, -- Running
        @operator,
        NULL,
        NULL,
        NULL
    )

    SET @log_id = SCOPE_IDENTITY()                
    
    RETURN (0)
END
go

PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_oncollectionstop...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_oncollectionstop', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_oncollectionstop]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_oncollectionstop]
    @collection_set_id int
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    -- Check the collection_set_id
    IF (@collection_set_id IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@collection_set_id')
        RETURN (1)
    END
    ELSE IF (NOT EXISTS (SELECT collection_set_id FROM dbo.syscollector_collection_sets WHERE collection_set_id = @collection_set_id))
    BEGIN
        DECLARE @collection_set_id_as_char VARCHAR(36)
        SELECT @collection_set_id_as_char = CONVERT(VARCHAR(36), @collection_set_id)

        RAISERROR(14262, -1, -1, '@collection_set_id', @collection_set_id_as_char)
        RETURN (1)
    END

    -- Find the log_id
    -- It will be a log entry for the same collection set, with no parent and not finished
    DECLARE @log_id bigint
    SELECT TOP 1 @log_id = log_id FROM dbo.syscollector_execution_log_internal 
        WHERE collection_set_id = @collection_set_id 
        AND parent_log_id IS NULL
        AND finish_time IS NULL
        ORDER BY start_time DESC

    IF (@log_id IS NULL)
    BEGIN
        -- Raise a warning message
        RAISERROR(14606, 9, -1, '@log_id')
    END
    ELSE
    BEGIN
        -- Mark the log as finished
        UPDATE dbo.syscollector_execution_log_internal SET
            finish_time = GETDATE(),
            [status] = CASE
                WHEN [status] = 0 THEN 1 -- Mark complete if it was running
                ELSE [status]            -- Leave the error status unchanged
            END
        WHERE log_id = @log_id
    END

    RETURN (0)
END
go


PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_oncollectionbegin...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_oncollectionbegin', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_oncollectionbegin]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_oncollectionbegin]
    @collection_set_id int,
    @mode smallint = NULL,
    @operator nvarchar(128) = NULL,
    @log_id bigint OUTPUT
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    -- Verify parameters
    --

    -- Check the collection_set_id
    IF (@collection_set_id IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@collection_set_id')
        RETURN (1)
    END
    ELSE IF (NOT EXISTS (SELECT collection_set_id FROM dbo.syscollector_collection_sets WHERE collection_set_id = @collection_set_id))
    BEGIN
        DECLARE @collection_set_id_as_char VARCHAR(36)
        SELECT @collection_set_id_as_char = CONVERT(VARCHAR(36), @collection_set_id)

        RAISERROR(14262, -1, -1, '@collection_set_id', @collection_set_id_as_char)
        RETURN (1)
    END


    -- Default operator to currently logged in user
    SET @operator = NULLIF(LTRIM(RTRIM(@operator)), '')
    SET @operator = ISNULL(@operator, suser_sname())

    -- Default mode to Collection
    SET @mode = ISNULL(@mode, 0)

    -- Find the parent log id.
    -- It will be a log entry for the same collection set, with no parent and not finished
    DECLARE @parent_log_id bigint
    SELECT TOP 1 @parent_log_id = log_id FROM dbo.syscollector_execution_log_internal 
        WHERE collection_set_id = @collection_set_id 
        AND parent_log_id IS NULL
        AND (@mode = 1 OR finish_time IS NULL)
        ORDER BY start_time DESC

    -- Insert the log record
    --
    INSERT INTO dbo.syscollector_execution_log_internal (
        parent_log_id, 
        collection_set_id, 
        collection_item_id,
        start_time,
        last_iteration_time,
        finish_time,
        runtime_execution_mode,
        [status],
        operator,
        package_id,
        package_execution_id,
        failure_message
    ) VALUES (
        @parent_log_id,
        @collection_set_id,
        NULL,
        GETDATE(),
        NULL,
        NULL,
        @mode,
        0, -- Running
        @operator,
        NULL,
        NULL,
        NULL
    )

    SET @log_id = SCOPE_IDENTITY()                
    
    RETURN (0)
END
go

PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_oncollectionend...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_oncollectionend', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_oncollectionend]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_oncollectionend]
    @log_id bigint
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    -- Check the log_id
    DECLARE @retVal INT
    EXEC @retVal = dbo.sp_syscollector_verify_event_log_id @log_id, 1
    IF (@retVal <> 0)
        RETURN (@retVal)

    -- Mark the log as finished
    UPDATE dbo.syscollector_execution_log_internal SET
        finish_time = GETDATE(),
        [status] = CASE
            WHEN [status] = 0 THEN 1 -- Mark complete if it was running
            ELSE [status]            -- Leave the error status unchanged
        END
    WHERE log_id = @log_id

    RETURN (0)
END
go

PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_onpackagebegin...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_onpackagebegin', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_onpackagebegin]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_onpackagebegin]
    @parent_log_id bigint,
    @package_id uniqueidentifier,
    @package_execution_id uniqueidentifier,
    @collection_item_id int = NULL,
    @mode smallint = NULL,
    @operator nvarchar(128) = NULL,
    @log_id bigint OUTPUT
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    -- Verify parameters
    --

    -- Check the @parent_log_id
    IF (@parent_log_id IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@parent_log_id')
        RETURN (1)
    END
    ELSE IF (NOT EXISTS (SELECT log_id FROM dbo.syscollector_execution_log WHERE log_id = @parent_log_id))
    BEGIN
        DECLARE @parent_log_id_as_char VARCHAR(36)
        SELECT @parent_log_id_as_char = CONVERT(VARCHAR(36), @parent_log_id)

        RAISERROR(14262, -1, -1, '@parent_log_id', @parent_log_id_as_char)
        RETURN (1)
    END

    -- Check the @package_id
    IF (@package_id IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@package_id')
        RETURN (1)
    END
    -- The 84CEC861... package is an id of our special Master package that is allowed to start 
    -- the log without being saved to sysssispackages
    ELSE IF (@package_id != N'84CEC861-D619-433D-86FB-0BB851AF454A' AND NOT EXISTS (SELECT id FROM dbo.sysssispackages WHERE id = @package_id))
    BEGIN
        DECLARE @package_id_as_char VARCHAR(50)
        SELECT @package_id_as_char = CONVERT(VARCHAR(50), @package_id)

        RAISERROR(14262, -1, -1, '@package_id', @package_id_as_char)
        RETURN (1)
    END

    -- Default operator to currently logged in user
    SET @operator = NULLIF(LTRIM(RTRIM(@operator)), '')
    SET @operator = ISNULL(@operator, suser_sname())

    -- Default mode to Collection
    SET @mode = ISNULL(@mode, 0)

    -- Find out the collection_set_id from the parent
    DECLARE @collection_set_id INT
    SELECT @collection_set_id = collection_set_id FROM dbo.syscollector_execution_log WHERE log_id = @parent_log_id

    -- Check the @package_execution_id
    IF (@package_execution_id IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@package_execution_id')
        RETURN (1)
    END
    

    -- Insert the log record
    --
    INSERT INTO dbo.syscollector_execution_log_internal (
        parent_log_id, 
        collection_set_id, 
        collection_item_id,
        start_time,
        last_iteration_time,
        finish_time,
        runtime_execution_mode,
        [status],
        operator,
        package_id,
        package_execution_id,
        failure_message
    ) VALUES (
        @parent_log_id,
        @collection_set_id,
        @collection_item_id,        
        GETDATE(),
        NULL,
        NULL,
        @mode,
        0, -- Running
        @operator,
        @package_id,
        @package_execution_id,        
        NULL
    )

    SET @log_id = SCOPE_IDENTITY()                

    RETURN (0)
END
go


PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_onpackageend...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_onpackageend', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_onpackageend]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_onpackageend]
    @log_id bigint
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    -- Check the log_id
    DECLARE @retVal INT
    EXEC @retVal = dbo.sp_syscollector_verify_event_log_id @log_id
    IF (@retVal <> 0)
        RETURN (@retVal)

    -- Mark the log as finished
    UPDATE dbo.syscollector_execution_log_internal SET
        finish_time = GETDATE(),
        [status] = CASE
            WHEN [status] = 0 THEN 1 -- Mark complete if it was running
            ELSE [status]            -- Leave the error status unchanged
        END
    WHERE log_id = @log_id

    DECLARE @runtime_execution_mode smallint
    DECLARE @status smallint
    SELECT @status = [status], @runtime_execution_mode = runtime_execution_mode
    FROM dbo.syscollector_execution_log_internal
    WHERE log_id = @log_id

    -- status was successful and this is logged by an upload package
    IF @status = 1 AND @runtime_execution_mode = 1
    BEGIN
        -- if the package ended succesfully, update the top most log to warning if it had failure
        -- this is because if there were a previous upload failure but the latest upload were successful, 
        -- we want indicated a warning rather than a failure throughout the lifetime of this collection set
        DECLARE @parent_log_id BIGINT
        SELECT @parent_log_id = parent_log_id FROM dbo.syscollector_execution_log_internal WHERE log_id = @log_id;
        WHILE @parent_log_id IS NOT NULL
        BEGIN
            -- get the next parent
            SET @log_id = @parent_log_id
            SELECT @parent_log_id = parent_log_id FROM dbo.syscollector_execution_log_internal WHERE log_id = @log_id;
        END

        UPDATE dbo.syscollector_execution_log_internal SET
            [status] = CASE
                WHEN [status] = 2 THEN 3 -- Mark warning if it indicated a failure
                ELSE [status]            -- Leave the original status unchanged
            END
        WHERE
            log_id = @log_id
    END

    RETURN (0)
END
go


PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_onpackageupdate...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_onpackageupdate', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_onpackageupdate]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_onpackageupdate]
    @log_id bigint
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    -- Check the log_id
    DECLARE @retVal INT
    EXEC @retVal = dbo.sp_syscollector_verify_event_log_id @log_id
    IF (@retVal <> 0)
        RETURN (@retVal)

    -- Update the log
    UPDATE dbo.syscollector_execution_log_internal SET
        last_iteration_time = GETDATE()
    WHERE log_id = @log_id

    RETURN (0)
END
go


PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_onerror...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_onerror', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_onerror]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_onerror]
    @log_id bigint,
    @message nvarchar(2048) = NULL
AS
BEGIN
    SET NOCOUNT ON
    
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_event_onerror
    ELSE
        BEGIN TRANSACTION
    
    BEGIN TRY
    -- Check the log_id
    -- If @message is passed, we can allow to enter the error for a collection set
    -- otherwise we will rely on the entries in sysssislog table to get the error message.
    DECLARE @retVal INT
    IF (@message IS NULL)
    BEGIN
        EXEC @retVal = dbo.sp_syscollector_verify_event_log_id @log_id, 0
    END
    ELSE
    BEGIN
        EXEC @retVal = dbo.sp_syscollector_verify_event_log_id @log_id, 1
    END
    IF (@retVal <> 0)
        RETURN (@retVal)


    DECLARE
         @failure_message   NVARCHAR(2048)
        ,@execution_id        UNIQUEIDENTIFIER

    IF @message IS NULL 
    BEGIN
        -- If no message is provided, find the last task that has failed
        -- for this package in the sysssislog table.
        -- Store the message as the failure_message for our package log.
        SELECT 
            @execution_id = package_execution_id
        FROM dbo.syscollector_execution_log
        WHERE log_id = @log_id

        SELECT TOP 1 
            @failure_message = [message]
        FROM dbo.sysssislog
        WHERE executionid = @execution_id
            AND (UPPER([event] COLLATE SQL_Latin1_General_CP1_CS_AS) = 'ONERROR')
        ORDER BY endtime DESC
    END 
    ELSE 
    BEGIN
        -- Otherwise use the provided message
        SET @failure_message = @message
    END

    -- Update the execution log
    UPDATE dbo.syscollector_execution_log_internal SET
         [status] = 2                    -- Mark as Failed
        ,failure_message = @failure_message
    WHERE
        log_id = @log_id

    -- Update all parent logs with the failure status
    SELECT @log_id = parent_log_id FROM dbo.syscollector_execution_log_internal WHERE log_id = @log_id;
    WHILE @log_id IS NOT NULL
    BEGIN
        UPDATE dbo.syscollector_execution_log_internal SET
            [status] = 2                    -- Mark as Failed
        WHERE
            log_id = @log_id;

        -- get the next parent
        SELECT @log_id = parent_log_id FROM dbo.syscollector_execution_log_internal WHERE log_id = @log_id;
    END

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)

    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_event_onerror

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)    
    END CATCH
END
go
  
  
PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_onstatsupdate...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_onstatsupdate', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_onstatsupdate]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_onstatsupdate]
    @log_id bigint,
    @task_name nvarchar(128),
    @row_count_in int = NULL,
    @row_count_out int = NULL,
    @row_count_error int = NULL,
    @execution_time_ms int = NULL
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    -- Check the log_id
    DECLARE @retVal INT
    EXEC @retVal = dbo.sp_syscollector_verify_event_log_id @log_id
    IF (@retVal <> 0)
        RETURN (@retVal)
    
    -- Check task name
    IF (@task_name IS NOT NULL)
    BEGIN
        SET @task_name = NULLIF(LTRIM(RTRIM(@task_name)), N'')
    END
    IF (@task_name IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@task_name')
        RETURN (1)
    END
    
    -- Insert the log entry
    INSERT INTO dbo.syscollector_execution_stats_internal (
        log_id,
        task_name,
        execution_row_count_in,
        execution_row_count_out,
        execution_row_count_errors,
        execution_time_ms,
        log_time
    ) VALUES (
        @log_id,
        @task_name,
        @row_count_in,
        @row_count_out,
        @row_count_error,
        NULLIF(@execution_time_ms, 0),
        GETDATE()
    )

    RETURN (0)
END
go

---------------------------------------------------------------
-- Data Collector log viewing functions and views
---------------------------------------------------------------

-- [fn_syscollector_find_collection_set_root]
-- This function finds the first log entry for a given collection set
-- run. It retunrs log_id of that entry. 
PRINT ''
PRINT 'Creating function [dbo].[fn_syscollector_find_collection_set_root] ...'
IF (NOT OBJECT_ID(N'dbo.fn_syscollector_find_collection_set_root', 'FN') IS NULL)
  DROP FUNCTION [dbo].[fn_syscollector_find_collection_set_root]
go

CREATE FUNCTION [dbo].[fn_syscollector_find_collection_set_root]
(
    @log_id BIGINT
)
RETURNS BIGINT
WITH RETURNS NULL ON NULL INPUT
AS
BEGIN
    DECLARE @root_id BIGINT;

    -- Derive result using a CTE as the table is self-referencing
    WITH graph AS
    (
        -- select the anchor (specified) node
        SELECT log_id, parent_log_id FROM dbo.syscollector_execution_log WHERE log_id = @log_id
        UNION ALL
        -- select the parent node recursively
        SELECT node.log_id, node.parent_log_id FROM dbo.syscollector_execution_log node
        INNER JOIN graph AS leaf ON (node.log_id = leaf.parent_log_id)
    )
    SELECT @root_id = log_id FROM graph WHERE parent_log_id = 0;
    
    --Return result
    RETURN ISNULL(@root_id, @log_id)
END 
go

-- [fn_syscollector_get_execution_log_tree]
-- This function returns a set of log entries related to a single run 
-- of a collection set. The entries are ordered in a hierarchy, starting from
-- the collection set first log entry and then down through all packages 
-- that were executed as part of the collection set.
PRINT ''
PRINT 'Creating function [dbo].[fn_syscollector_get_execution_log_tree] ...'
IF (NOT OBJECT_ID(N'dbo.fn_syscollector_get_execution_log_tree', 'IF') IS NULL)
    DROP FUNCTION [dbo].[fn_syscollector_get_execution_log_tree]
go

CREATE FUNCTION [dbo].[fn_syscollector_get_execution_log_tree] 
(
     @log_id                BIGINT,
     @from_collection_set    BIT = 1
) 
RETURNS TABLE
AS
RETURN
(
    -- Derive result using a CTE as the table is self-referencing
    WITH graph AS 
    (
        -- select the anchor (specified) node
        SELECT 
            log_id,
            parent_log_id,
            collection_set_id,
            start_time,
            last_iteration_time,
            finish_time,
            runtime_execution_mode,
            operator,
            [status],
            package_id,
            package_execution_id,
            failure_message,
            0 AS depth 
        FROM dbo.syscollector_execution_log
        WHERE log_id = CASE @from_collection_set
            WHEN 1 THEN dbo.fn_syscollector_find_collection_set_root(@log_id)
            ELSE @log_id
        END 
        -- select the child nodes recursively
        UNION ALL
        SELECT 
            leaf.log_id,
            leaf.parent_log_id,
            leaf.collection_set_id,
            leaf.start_time,
            leaf.last_iteration_time,
            leaf.finish_time,
            leaf.runtime_execution_mode,
            leaf.operator,
            leaf.[status],
            leaf.package_id,
            leaf.package_execution_id,
            leaf.failure_message,
            node.depth + 1 AS depth
        FROM dbo.syscollector_execution_log AS leaf
        INNER JOIN graph AS node ON (node.log_id = leaf.parent_log_id)
    )
    SELECT 
        log_id,
        parent_log_id,
        collection_set_id,
        start_time,
        last_iteration_time,
        finish_time,
        CASE 
            WHEN finish_time IS NOT NULL THEN DATEDIFF(ss, start_time, finish_time) 
            WHEN last_iteration_time IS NOT NULL THEN DATEDIFF(ss, start_time, last_iteration_time) 
            ELSE 0
        END AS duration,
        runtime_execution_mode,
        operator,
        [status],
        package_id,
        package_execution_id,
        failure_message,
        depth 
    FROM graph
) 
go


-- [fn_syscollector_get_execution_stats]
-- This function returns stats for each execution of a package.
-- The stats should be logged for each iteration within the package.
PRINT ''
PRINT 'Creating function [dbo].[fn_syscollector_get_execution_stats] ...'
IF (NOT OBJECT_ID(N'dbo.fn_syscollector_get_execution_stats', 'IF') IS NULL)
    DROP FUNCTION [dbo].[fn_syscollector_get_execution_stats]
go

CREATE FUNCTION [dbo].[fn_syscollector_get_execution_stats] 
(
     @log_id                BIGINT
) 
RETURNS TABLE
AS
RETURN
(
    SELECT 
        log_id,
        task_name,
        AVG(execution_row_count_in) AS avg_row_count_in,
        MIN(execution_row_count_in) AS min_row_count_in,
        MAX(execution_row_count_in) AS max_row_count_in,
        AVG(execution_row_count_out) AS avg_row_count_out,
        MIN(execution_row_count_out) AS min_row_count_out,
        MAX(execution_row_count_out) AS max_row_count_out,
        AVG(execution_row_count_errors) AS avg_row_count_errors,
        MIN(execution_row_count_errors) AS min_row_count_errors,
        MAX(execution_row_count_errors) AS max_row_count_errors,
        AVG(execution_time_ms) AS avg_duration,
        MIN(execution_time_ms) AS min_duration,
        MAX(execution_time_ms) AS max_duration
    FROM dbo.syscollector_execution_stats
    WHERE log_id = @log_id
    GROUP BY log_id, task_name
)
go


-- [fn_syscollector_get_execution_details]
-- This function returns detailed log entries from SSIS log for each package
-- execution. The log id passed as input is an id of a log entry for that package
-- from syscollector_execution_log view.
PRINT ''
PRINT 'Creating function [dbo].[fn_syscollector_get_execution_details] ...'
IF (NOT OBJECT_ID(N'dbo.fn_syscollector_get_execution_details', 'IF') IS NULL)
    DROP FUNCTION [dbo].[fn_syscollector_get_execution_details]
go
CREATE FUNCTION [dbo].[fn_syscollector_get_execution_details] 
(
     @log_id                BIGINT
)
RETURNS TABLE
AS
RETURN
(
    SELECT TOP (100) PERCENT
        l.source,
        l.event,
        l.message,
        l.starttime AS start_time,
        l.endtime AS finish_time,
        l.datacode,
        l.databytes
    FROM sysssislog l
    JOIN dbo.syscollector_execution_log e ON (e.package_execution_id = l.executionid)
    WHERE e.log_id = @log_id
    ORDER BY l.starttime
)
go            


-- [syscollector_execution_log_full]
-- An expanded log view that shows the log entries in a hierarchical order.
PRINT ''
PRINT 'Creating view syscollector_execution_log_full...'
IF (NOT OBJECT_ID('dbo.syscollector_execution_log_full', 'V') IS NULL)
    DROP VIEW [dbo].[syscollector_execution_log_full]
go

CREATE VIEW [dbo].[syscollector_execution_log_full]
AS
SELECT 
        t.log_id,
        ISNULL(t.parent_log_id, 0) as parent_log_id,
        CASE 
            WHEN t.package_id IS NULL THEN SPACE(t.depth * 4) + c.name
            WHEN t.package_id = N'84CEC861-D619-433D-86FB-0BB851AF454A' THEN SPACE(t.depth * 4) + N'Master'
            ELSE SPACE(t.depth * 4) + p.name 
        END AS [name],
        t.[status],
        t.runtime_execution_mode,
        t.start_time,
        t.last_iteration_time,
        t.finish_time,
        t.duration,
        t.failure_message,
        t.operator,
        t.package_execution_id,
        t.collection_set_id
    FROM dbo.syscollector_execution_log_internal l
    CROSS APPLY dbo.fn_syscollector_get_execution_log_tree(l.log_id, 0) t
    LEFT OUTER JOIN dbo.syscollector_collection_sets c ON( c.collection_set_id = t.collection_set_id)
    LEFT OUTER JOIN dbo.sysssispackages p ON (p.id = t.package_id AND p.id != N'84CEC861-D619-433D-86FB-0BB851AF454A')
    WHERE l.parent_log_id IS NULL
go
        
---------------------------------------------------------------
-- Data Collection log clean-up
---------------------------------------------------------------

-- [sp_syscollector_delete_execution_log_tree]
-- This stored procedure removes all log entries related to a single
-- run of a collection set. It also removes corresponding log entries
-- from SSIS log tables.
PRINT ''
IF (NOT OBJECT_ID(N'dbo.sp_syscollector_delete_execution_log_tree', 'P') IS NULL)
BEGIN
  PRINT 'Dropping procedure [dbo].[sp_syscollector_delete_execution_log_tree] ...'
  DROP PROCEDURE [dbo].[sp_syscollector_delete_execution_log_tree]
END
go

PRINT 'Creating procedure [dbo].[sp_syscollector_delete_execution_log_tree] ...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_delete_execution_log_tree]
    @log_id BIGINT,
    @from_collection_set    BIT = 1
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END

    SET NOCOUNT ON;
    CREATE TABLE #log_ids (log_id BIGINT);
    
    WITH graph AS
    (
        SELECT log_id FROM dbo.syscollector_execution_log
        WHERE log_id = CASE @from_collection_set
            WHEN 1 THEN dbo.fn_syscollector_find_collection_set_root(@log_id)
            ELSE @log_id
        END
        UNION ALL
        SELECT leaf.log_id FROM dbo.syscollector_execution_log AS leaf
        INNER JOIN graph AS node ON (node.log_id = leaf.parent_log_id)
    )
    INSERT INTO #log_ids
    SELECT log_id
    FROM graph
    
    -- Delete all ssis log records pertaining to the selected logs
    DELETE FROM dbo.sysssislog
        FROM dbo.sysssislog AS s
        INNER JOIN dbo.syscollector_execution_log_internal AS l ON (l.package_execution_id = s.executionid)
        INNER JOIN #log_ids AS i ON i.log_id = l.log_id
        
    -- Then delete the actual logs
    DELETE FROM syscollector_execution_log_internal
        FROM syscollector_execution_log_internal AS l
        INNER Join #log_ids AS i ON i.log_id = l.log_id

    DROP TABLE #log_ids
    RETURN (0)
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_delete_collection_set_internal]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_delete_collection_set_internal]...'
    DROP PROCEDURE [dbo].[sp_syscollector_delete_collection_set_internal]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_delete_collection_set_internal]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_delete_collection_set_internal]
    @collection_set_id      int,
    @name                   sysname,
    @collection_job_id      uniqueidentifier,
    @upload_job_id          uniqueidentifier,
    @collection_mode        smallint
AS
BEGIN
    DECLARE @TranCounter int
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_delete_collection_set
    ELSE
        BEGIN TRANSACTION
    
    BEGIN TRY
        -- clean log before deleting collection set
        DECLARE @log_id bigint
        SET @log_id = (SELECT TOP(1) log_id  FROM dbo.syscollector_execution_log WHERE collection_set_id = @collection_set_id)
        WHILE (@log_id IS NOT NULL)
        BEGIN
            EXEC dbo.sp_syscollector_delete_execution_log_tree @log_id = @log_id
            SET @log_id = (SELECT TOP(1) log_id  FROM dbo.syscollector_execution_log WHERE collection_set_id = @collection_set_id)
        END

        DECLARE @schedule_id    int
        SELECT @schedule_id = schedule_id
        FROM dbo.syscollector_collection_sets cs JOIN sysschedules_localserver_view sv
        ON (cs.schedule_uid = sv.schedule_uid)
        WHERE collection_set_id = @collection_set_id

        DELETE [dbo].[syscollector_collection_sets_internal]
        WHERE collection_set_id = @collection_set_id

        EXEC dbo.sp_syscollector_delete_jobs 
            @collection_job_id        = @collection_job_id,
            @upload_job_id            = @upload_job_id,
            @schedule_id            = @schedule_id,
            @collection_mode        = @collection_mode

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_delete_collection_set

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)    
    END CATCH
END
GO

-- This is a stored procedure of collection_set, but it is created here because it 
-- makes references to the collection item view, execution log, 
-- and the [sp_syscollector_delete_execution_log_tree] stored proc
IF (NOT OBJECT_ID('[dbo].[sp_syscollector_delete_collection_set]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_delete_collection_set]...'
    DROP PROCEDURE [dbo].[sp_syscollector_delete_collection_set]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_delete_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_delete_collection_set]
    @collection_set_id            int = NULL,
    @name                        sysname = NULL
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    -- Security check (role membership)
    EXECUTE AS CALLER;
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN (1)
    END
    REVERT;

    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    DECLARE @is_system            bit
    DECLARE @is_running            bit
    DECLARE @upload_job_id        uniqueidentifier
    DECLARE @collection_job_id    uniqueidentifier
    DECLARE @collection_mode    smallint
    SELECT    @is_running = is_running,
            @is_system = is_system,
            @upload_job_id = upload_job_id, 
            @collection_job_id = collection_job_id,
            @collection_mode = collection_mode
    FROM [dbo].[syscollector_collection_sets]
    WHERE collection_set_id = @collection_set_id

    IF (@is_system = 1)
    BEGIN
        -- cannot update, delete, or add new collection items to a system collection set
        RAISERROR(14696, -1, -1);
        RETURN (1)
    END

    IF (@is_running = 1)
    BEGIN
        EXEC @retVal = sp_syscollector_stop_collection_set @collection_set_id = @collection_set_id
        IF (@retVal <> 0)
            RETURN (1)
    END

    -- All checks are go
    -- Do the actual delete
    EXEC @retVal = sp_syscollector_delete_collection_set_internal
                        @collection_set_id = @collection_set_id, 
                        @name = @name,
                        @collection_job_id = @collection_job_id,
                        @upload_job_id = @upload_job_id,
                        @collection_mode = @collection_mode
    RETURN (0)
END
GO

IF (NOT OBJECT_ID('dbo.sp_syscollector_purge_collection_logs', 'P') IS NULL)
BEGIN
    PRINT ''
    PRINT 'Dropping stored procedure sp_syscollector_purge_collection_logs...'
    DROP PROCEDURE [dbo].[sp_syscollector_purge_collection_logs]
END
GO

-- [sp_syscollector_purge_collection_logs]
-- The sp cleans any log record with an expired finish date
-- Expiration is measured from the date provided to the sp
-- and defaults to TODAY.
PRINT ''
PRINT 'Creating stored procedure sp_syscollector_purge_collection_logs...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_purge_collection_logs]
    @reference_date datetime = NULL
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    IF (@reference_date IS NULL)
    BEGIN
        SET @reference_date = GETDATE()
    END
    
    -- An expired log record is any record of a collection set that is older than 
    -- the reference date minus the collection set's days_until_expiration
    CREATE TABLE #purged_log_ids (log_id BIGINT)
    
    INSERT INTO #purged_log_ids
    SELECT log_id
    FROM syscollector_execution_log_internal as l
    INNER JOIN syscollector_collection_sets s ON l.collection_set_id = s.collection_set_id
    WHERE s.days_until_expiration > 0
    AND @reference_date >= DATEADD(DAY, s.days_until_expiration, l.finish_time)

    -- Delete all ssis log records pertaining to expired logs
    DELETE FROM dbo.sysssislog
        FROM dbo.sysssislog AS s
        INNER JOIN dbo.syscollector_execution_log_internal AS l ON (l.package_execution_id = s.executionid)
        INNER JOIN #purged_log_ids AS i ON i.log_id = l.log_id
        
    -- Then delete the actual logs
    DELETE FROM syscollector_execution_log_internal
        FROM syscollector_execution_log_internal AS l
        INNER Join #purged_log_ids AS i ON i.log_id = l.log_id

    DROP TABLE #purged_log_ids
    -- Go for another round to cleanup the orphans
    -- Ideally, the log heirarchy guarantees that a finish time by a parent log will always
    -- be higher than the finish time of any of its descendants.
    -- The purge step however does not delete log records with a null finish time
    -- A child log can have a null finish time while its parent is closed if there is an
    -- error in execution that causes the log to stay open.
    -- If such a child log exists, its parent will be purged leaving it as an orphan
    
    -- get orphan records and all their descendants in a cursor and purge them
    DECLARE orphaned_log_cursor INSENSITIVE CURSOR FOR
            SELECT log_id 
            FROM syscollector_execution_log_internal
            WHERE parent_log_id NOT IN (
                SELECT log_id FROM syscollector_execution_log_internal
            )
            FOR READ ONLY
            
    DECLARE @log_id BIGINT

    -- for every orphan, delete all its remaining tree
    -- this is supposedly a very small fraction of the entire log
    OPEN orphaned_log_cursor    
    FETCH orphaned_log_cursor INTO @log_id
    WHILE @@FETCH_STATUS = 0
    BEGIN
        EXEC sp_syscollector_delete_execution_log_tree @log_id = @log_id, @from_collection_set = 0
        FETCH orphaned_log_cursor INTO @log_id
    END
    
    CLOSE orphaned_log_cursor
    DEALLOCATE orphaned_log_cursor
END
GO

---------------------------------------------------------------
-- Start and stop Data Collector
---------------------------------------------------------------

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_enable_collector]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_enable_collector]...'
    DROP PROCEDURE [dbo].[sp_syscollector_enable_collector]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_enable_collector]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_enable_collector]
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    -- Security check (role membership)
    EXECUTE AS CALLER;
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END
    REVERT;

    BEGIN TRANSACTION

    DECLARE @was_enabled int;

    SELECT @was_enabled = ISNULL(CONVERT(int, parameter_value),0)
    FROM [dbo].[syscollector_config_store_internal]
    WHERE parameter_name = 'CollectorEnabled'

    IF (@was_enabled = 0)
    BEGIN

        UPDATE [dbo].[syscollector_config_store_internal]
        SET parameter_value = 1
        WHERE parameter_name = 'CollectorEnabled'

        DECLARE @collection_set_id int

        DECLARE collection_set_cursor CURSOR LOCAL FOR
            SELECT collection_set_id
            FROM dbo.syscollector_collection_sets
            WHERE is_running = 1

        OPEN collection_set_cursor
        FETCH collection_set_cursor INTO @collection_set_id

        WHILE @@FETCH_STATUS = 0 
        BEGIN
            EXEC dbo.sp_syscollector_start_collection_set_jobs @collection_set_id = @collection_set_id
            FETCH collection_set_cursor INTO @collection_set_id
        END

        CLOSE collection_set_cursor
        DEALLOCATE collection_set_cursor

    END

    COMMIT TRANSACTION

END
GO


IF (NOT OBJECT_ID('[dbo].[sp_syscollector_disable_collector]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_disable_collector]...'
    DROP PROCEDURE [dbo].[sp_syscollector_disable_collector]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_disable_collector]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_disable_collector]
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    -- Security check (role membership)
    EXECUTE AS CALLER;
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END
    REVERT;

    BEGIN TRANSACTION

    DECLARE @was_enabled int;

    SELECT @was_enabled = ISNULL(CONVERT(int, parameter_value),0)
    FROM [dbo].[syscollector_config_store_internal]
    WHERE parameter_name = 'CollectorEnabled'

    IF (@was_enabled <> 0)
    BEGIN

        UPDATE [dbo].[syscollector_config_store_internal]
        SET parameter_value = 0
        WHERE parameter_name = 'CollectorEnabled'

        DECLARE @collection_set_id INT
        DECLARE @collection_mode SMALLINT
        DECLARE @collection_job_id UNIQUEIDENTIFIER

        DECLARE collection_set_cursor CURSOR LOCAL FOR
            SELECT collection_set_id, collection_mode, collection_job_id
            FROM dbo.syscollector_collection_sets
            WHERE is_running = 1

        OPEN collection_set_cursor
        FETCH collection_set_cursor INTO @collection_set_id, @collection_mode, @collection_job_id

        WHILE @@FETCH_STATUS = 0 
        BEGIN
            -- If this collection set is running in cached mode, and the collection job is running, we need to stop the job explicitly here
            DECLARE @is_collection_job_running INT
            EXECUTE [dbo].[sp_syscollector_get_collection_set_execution_status]
                    @collection_set_id = @collection_set_id,
                    @is_collection_running = @is_collection_job_running OUTPUT    

            IF (@is_collection_job_running = 1
                AND @collection_mode = 0)           -- Cached mode
            BEGIN
                EXEC sp_stop_job @job_id = @collection_job_id
            END

            -- Now, disable the jobs and detach them from the upload schedules
            EXEC dbo.sp_syscollector_stop_collection_set_jobs @collection_set_id = @collection_set_id
            FETCH collection_set_cursor INTO @collection_set_id, @collection_mode, @collection_job_id
        END
        CLOSE collection_set_cursor
        DEALLOCATE collection_set_cursor

    END

    COMMIT TRANSACTION

END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_get_trace_info]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_get_trace_info]'
    DROP PROCEDURE [dbo].[sp_syscollector_get_trace_info]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_get_trace_info]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_get_trace_info]
    @trace_path  nvarchar(512),
    @use_default int
AS
BEGIN
    SELECT 
        CONVERT(nvarchar(30), t.start_time, 126) as start_time,
        CASE t.status 
            WHEN 1 THEN 1 
            ELSE 0 
        END AS is_running, 
        ISNULL(t.dropped_event_count,0) as dropped_event_count,
        t.id
    FROM sys.traces t
    WHERE (@use_default=1 and t.is_default=1)
          OR (@use_default=0 AND t.path LIKE (@trace_path + N'%.trc'))
END
GO

---------------------------------------------------------------
-- Data Collector: Helper procedures
---------------------------------------------------------------

-- Procedure to retrieve a query plan from cache
IF (NOT OBJECT_ID('[dbo].[sp_syscollector_text_query_plan_lookpup]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_text_query_plan_lookpup]'
    DROP PROCEDURE [dbo].[sp_syscollector_text_query_plan_lookpup]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_text_query_plan_lookpup]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_text_query_plan_lookpup]
    @plan_handle varbinary(64),
    @statement_start_offset int,
    @statement_end_offset int
AS
BEGIN
    SET NOCOUNT ON
    SELECT    
        @plan_handle AS plan_handle,
        @statement_start_offset AS statement_start_offset,
        @statement_end_offset AS statement_end_offset,
        [dbid] AS database_id,
        [objectid] AS object_id,
        OBJECT_NAME(objectid, dbid) AS object_name,
        [query_plan] AS query_plan
    FROM    
        [sys].[dm_exec_text_query_plan](@plan_handle, @statement_start_offset, @statement_end_offset) dm
END
GO

-- Procedure to retrieve a query text from cache
IF (NOT OBJECT_ID('[dbo].[sp_syscollector_sql_text_lookup]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_sql_text_lookup]'
    DROP PROCEDURE [dbo].[sp_syscollector_sql_text_lookup]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_sql_text_lookup]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_sql_text_lookup]
    @sql_handle varbinary(64)
AS
BEGIN
    SET NOCOUNT ON
    SELECT    
        @sql_handle as sql_handle,
        dm.[dbid] AS database_id,
        dm.[objectid] AS object_id,
        OBJECT_NAME(objectid, dbid) AS object_name,
        CASE dm.[encrypted]
            WHEN 1 THEN N'Query SQL Text Encrypted'
            ELSE dm.[text]
        END AS sql_text
        FROM    
            [sys].[dm_exec_sql_text](@sql_handle) dm
END
GO

---------------------------------------------------------------
-- Install out-of-the-box objects
---------------------------------------------------------------
PRINT 'Installing out of the box Collector objects'
PRINT ''

-- We need agent XP's to be on for many parts of the coming installation script
-- Enable them here once and return them to their original state when done

DECLARE @advopt_old_value int
DECLARE @comp_old_value int
EXEC #sp_enable_component 'Agent XPs', @advopt_old_value out, @comp_old_value out

-- We need the old values to endure beyond batches
-- insert them into temp tables

IF (OBJECT_ID('tempdb..#advopt_old_value', 'U') IS NOT NULL)
BEGIN
	DROP TABLE #advopt_old_value
END

SELECT @advopt_old_value AS advopt_old_value
INTO #advopt_old_value

IF (OBJECT_ID('tempdb..#comp_old_value', 'U') IS NOT NULL)
BEGIN
	DROP TABLE #comp_old_value
END

SELECT @comp_old_value AS comp_old_value
INTO #comp_old_value
GO

-- disable the collector first
EXEC sp_syscollector_disable_collector
GO

---------------------------------------------------------------
-- Out-of-the-box SSIS folders for Data Collector
---------------------------------------------------------------

PRINT 'Creating SSIS folders...'
-- create 'Data Collector' folder under the root
IF(NOT EXISTS(SELECT * 
                FROM dbo.sysssispackagefolders
                WHERE folderid = '8877FE4B-A938-4a51-84B9-C5BDAD74B0AD'))
BEGIN
    EXEC dbo.sp_ssis_addfolder
        @parentfolderid = '00000000-0000-0000-0000-000000000000',
        @name = 'Data Collector',
        @folderid = '8877FE4B-A938-4a51-84B9-C5BDAD74B0AD'
END
GO
-- create 'Generated' folder under 'Data Collector'
IF(NOT EXISTS(SELECT * 
                FROM dbo.sysssispackagefolders
                WHERE folderid = '39163C42-602B-42C9-B4F7-1843614F9625'))
BEGIN
    EXEC dbo.sp_ssis_addfolder
        @parentfolderid = '8877FE4B-A938-4a51-84B9-C5BDAD74B0AD',
        @name = 'Generated',
        @folderid = '39163C42-602B-42c9-B4F7-1843614F9625'
END
GO

---------------------------------------------------------------
-- Loading instmdw.sql
---------------------------------------------------------------

-- a data collector table to store BLOB
IF (OBJECT_ID(N'[dbo].[syscollector_blobs_internal]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [dbo].[syscollector_blobs_internal]...'
    CREATE TABLE [dbo].[syscollector_blobs_internal] (
        parameter_name                nvarchar(128) NOT NULL,
        parameter_value               varbinary(max) NOT NULL,
        CONSTRAINT [PK_syscollector_blobs_internal_paremeter_name] PRIMARY KEY CLUSTERED (parameter_name ASC)
        )
END
GO

-- an SP to read a parameter value from the syscollector BLOB table
-- this stored procedure is called by the wizard to retrieve the instmdw.sql when setting up MDW
IF (NOT OBJECT_ID('[dbo].[sp_syscollector_get_instmdw]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_get_instmdw]'
    DROP PROCEDURE [dbo].[sp_syscollector_get_instmdw]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_get_instmdw]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_get_instmdw]
AS
BEGIN
    -- only dc_admin and dbo can setup MDW
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14712, -1, -1) WITH LOG
        RETURN(1) -- Failure
    END

    -- if the script has not been loaded, load it now
    IF (NOT EXISTS(SELECT parameter_name 
                   FROM syscollector_blobs_internal
                   WHERE parameter_name = N'InstMDWScript'))
    BEGIN
        EXECUTE sp_syscollector_upload_instmdw
    END
               
    SELECT cast(parameter_value as nvarchar(max)) 
    FROM syscollector_blobs_internal
    WHERE parameter_name = N'InstMDWScript'
END
GO

-- the script that would upload or update instmdw.sql
-- this is used when a hotfix, service pack, or upgrade is issued in instmdw.sql
-- user can specify the path to the new instmdw.sql to be installed or updated to.
IF (NOT OBJECT_ID('[dbo].[sp_syscollector_upload_instmdw]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_upload_instmdw]'
    DROP PROCEDURE [dbo].[sp_syscollector_upload_instmdw]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_upload_instmdw]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_upload_instmdw]
    @installpath              nvarchar(2048) = NULL
AS
BEGIN
    -- only dc_admin and dbo can setup MDW
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14712, -1, -1) WITH LOG
        RETURN(1) -- Failure
    END

    IF (@installpath IS NULL)
    BEGIN
        EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\Setup', N'SQLPath', @installpath OUTPUT;
    
        IF RIGHT(@installpath, 1) != N'\' set @installpath = @installpath + N'\'
        SET @installpath  = @installpath + N'Install\'
    END

    DECLARE @filename nvarchar(2048);
    SET @filename = @installpath + N'instmdw.sql'
    PRINT 'Uploading instmdw.sql from disk: ' + @filename

    CREATE TABLE #bulkuploadinstmdwscript
        (
            [instmdwscript] nvarchar(max) NOT NULL
        );


    DECLARE @stmt_bulkinsert nvarchar(2048);
    SET @stmt_bulkinsert = N'
    BULK INSERT #bulkuploadinstmdwscript
    FROM ' 
        -- Escape any embedded single quotes (we can't use QUOTENAME here b/c it can't handle strings > 128 chars)
        + '''' + REPLACE(@filename COLLATE database_default, N'''', N'''''') + '''
    WITH
        (
            DATAFILETYPE = ''char'',
            FIELDTERMINATOR = ''dc:stub:ft'',
            ROWTERMINATOR = ''dc:stub:rt'',
            CODEPAGE = ''RAW''
        );
    ';

    EXECUTE sp_executesql @stmt_bulkinsert;

    DECLARE @bytesLoaded int
    SELECT @bytesLoaded = ISNULL (DATALENGTH ([instmdwscript]), 0) FROM #bulkuploadinstmdwscript
    PRINT 'Loaded ' + CONVERT (nvarchar, @bytesLoaded)
            + ' bytes from ' + '''' + REPLACE(@filename COLLATE database_default, N'''', N'''''') + ''''

    DECLARE @scriptdata varbinary(max)
    SELECT @scriptdata = convert(varbinary(max), [instmdwscript]) FROM #bulkuploadinstmdwscript;

    IF (EXISTS(SELECT * FROM [dbo].[syscollector_blobs_internal]
        WHERE parameter_name = N'InstMDWScript'))
    BEGIN
        UPDATE [dbo].[syscollector_blobs_internal]
        SET parameter_value = @scriptdata
        WHERE parameter_name = N'InstMDWScript'
    END
    ELSE
    BEGIN
        INSERT INTO [dbo].[syscollector_blobs_internal] (
            parameter_name, 
            parameter_value
        )
        VALUES
        (
            N'InstMDWScript',
            @scriptdata
        )
    END

    DROP TABLE #bulkuploadinstmdwscript
END
GO


---------------------------------------------------------------
-- Out-of-the-box data collector packages - loading SSIS packages
---------------------------------------------------------------

CREATE PROCEDURE #syscollector_upload_package_from_file 
    @filename nvarchar(2048),
    @packagename sysname,
    @packageid uniqueidentifier,
    @versionid uniqueidentifier
AS
BEGIN
    RAISERROR ('Uploading data collector package from disk: %s', 0, 1, @filename) WITH NOWAIT;

    CREATE TABLE #bulkpackage
        (
            [packagexml] xml NOT NULL
        );

    DECLARE @stmt_bulkinsert nvarchar(2048);
    SET @stmt_bulkinsert = N'
    BULK INSERT #bulkpackage
    FROM ' 
    -- Escape any embedded single quotes (we can't use QUOTENAME here b/c it can't handle strings > 128 chars)
    + '''' + REPLACE(@filename COLLATE database_default, N'''', N'''''') + '''
    WITH
        (
            DATAFILETYPE = ''char'',
            FIELDTERMINATOR = ''dc:stub:ft'',
            ROWTERMINATOR = ''dc:stub:rt'',
            CODEPAGE = ''RAW''
        );
    ';

    EXECUTE sp_executesql @stmt_bulkinsert;

    DECLARE @bytesLoaded int
    SELECT @bytesLoaded = ISNULL (DATALENGTH ([packagexml]), 0) FROM #bulkpackage
    PRINT 'Loaded ' + CONVERT (varchar, @bytesLoaded)
            + ' bytes from ' 
            + '''' + REPLACE(@filename COLLATE database_default, N'''', N'''''') + ''''

    DECLARE @packagebin varbinary(max);
    SELECT @packagebin = convert(varbinary(max),[packagexml]) FROM #bulkpackage;

    DECLARE @loadtime datetime;
    SET @loadtime = getdate();

    DROP TABLE #bulkpackage

    EXECUTE sp_ssis_putpackage
            @name = @packagename
        ,    @id = @packageid
        ,    @description = N'System Data Collector Package'
        ,    @createdate = @loadtime
        ,    @folderid = '8877FE4B-A938-4a51-84B9-C5BDAD74B0AD'
        ,    @packagedata = @packagebin
        ,    @packageformat = 1
        ,    @packagetype = 5 -- DTSPKT_DTSDESIGNER100
        ,    @vermajor = 1
        ,    @verminor = 0
        ,    @verbuild = 0
        ,    @vercomments = N''
        ,    @verid = @versionid
    ;
END;
GO

CREATE PROCEDURE #syscollector_upload_package 
    @packagename sysname,
    @packageid uniqueidentifier,
    @versionid uniqueidentifier
AS
BEGIN
    DECLARE @installpath nvarchar(2048);
    EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\Setup', N'SQLPath', @installpath OUTPUT;
    IF RIGHT(@installpath,1) != N'\' set @installpath = @installpath + N'\'
    SET @installpath  = @installpath + N'Install\'

    DECLARE @filename nvarchar(2048);
    SET @filename = @installpath + @packagename + N'.dtsx'
    RAISERROR ('Uploading data collector package from disk: %s', 0, 1, @filename) WITH NOWAIT;

    EXEC #syscollector_upload_package_from_file @filename=@filename, @packagename=@packagename, @packageid=@packageid, @versionid=@versionid;    
END;
GO

--
-- Data Collector placeholder comment, dont move or remove.
-- 875f7de1-9e83-4be1-8b96-2df4a8533e88
--

-- Load SSIS packages needed by collector types

-- Temporarily enable the 'Agent XPs' config option so that sp_ssis_putpackage can 
-- succeed when SQLAgent is stopped. 

EXECUTE #syscollector_upload_package 
        @packagename='SqlTraceCollect'
    ,    @packageid='0E149FC9-1046-4DE6-98BF-4B22ED6F6C42'
    ,    @versionid='244F0904-5CC6-49B8-AE90-905AEEA8BAF3';

EXECUTE #syscollector_upload_package 
        @packagename='SqlTraceUpload'
    ,    @packageid='F389A8E6-5A17-4056-ABFD-C8B823F2092E'
    ,    @versionid='2D32AB4C-9929-4A85-A94E-7A01D8F40016';

EXECUTE #syscollector_upload_package 
        @packagename='TSQLQueryCollect'
    ,    @packageid='292B1476-0F46-4490-A9B7-6DB724DE3C0B'
    ,    @versionid='E24C6D00-94C6-457B-BED4-1F9F018F3273';

EXECUTE #syscollector_upload_package 
        @packagename='TSQLQueryUpload'
    ,    @packageid='6EB73801-39CF-489C-B682-497350C939F0'
    ,    @versionid='DA1210BC-C31B-43C6-B255-D8DDEB288CA1';

EXECUTE #syscollector_upload_package 
        @packagename='PerfCountersCollect'
    ,    @packageid='C2EAABC1-5BF3-4127-BEB3-26E94D026E7D'
    ,    @versionid='09A5B959-21B3-44E1-A37F-4A62BE5D6244';

EXECUTE #syscollector_upload_package 
        @packagename='PerfCountersUpload'
    ,    @packageid='08D854CB-0D45-4E96-92C6-227A5DCD7066'
    ,    @versionid='22A676DD-2025-493A-AD6B-C0186ABD556F';

EXECUTE #syscollector_upload_package 
        @packagename='QueryActivityCollect'
    ,    @packageid='0B68FC9D-23DC-48F3-A937-90A0A8943D0E'
    ,    @versionid='75A3A143-2059-433B-A11C-C8E0C80A83CF';

EXECUTE #syscollector_upload_package 
        @packagename='QueryActivityUpload'
    ,    @packageid='833DB628-8E19-47A3-92C5-FB1779B52E76'
    ,    @versionid='B1D79132-C6E6-46AA-8B14-E0AE4C4BA7BB';
GO

-- Cleanup the temp stored proc that we used to upload the SSIS packages
DROP PROCEDURE #syscollector_upload_package_from_file
DROP PROCEDURE #syscollector_upload_package
GO

---------------------------------------------------------------
-- Out-of-the-box collector type objects - definition for types
---------------------------------------------------------------

-- Stop system collection sets 
PRINT 'Stopping system collection sets...'

DECLARE @collection_set_id INT;
DECLARE system_collection_sets_cursor CURSOR LOCAL
    FOR SELECT collection_set_id FROM syscollector_collection_sets WHERE is_system = 1;

OPEN system_collection_sets_cursor;
FETCH system_collection_sets_cursor INTO @collection_set_id;

WHILE @@FETCH_STATUS = 0 
BEGIN
    -- stop the collection set if is running
    RAISERROR ('Stopping collection set with ID %d...', 0, 1, @collection_set_id) WITH NOWAIT
    EXEC sp_syscollector_stop_collection_set @collection_set_id = @collection_set_id
    
    FETCH system_collection_sets_cursor INTO @collection_set_id;
END

CLOSE system_collection_sets_cursor;
DEALLOCATE system_collection_sets_cursor;
GO

PRINT 'Creating or updating Collection Types...'
GO
-- Performance counters collector type
DECLARE @collector_type_uid uniqueidentifier
DECLARE @name sysname
DECLARE @parameter_schema xml
DECLARE @parameter_formatter xml
DECLARE @collection_package_id uniqueidentifier
DECLARE @upload_package_id uniqueidentifier

SET @collector_type_uid = '294605dd-21de-40b2-b20f-f3e170ea1ec3'
SET @name = 'Performance Counters Collector Type'
SET @parameter_schema =
		'<?xml version="1.0" encoding="utf-8"?>
			<xs:schema targetNamespace="DataCollectorType" xmlns:xs="http://www.w3.org/2001/XMLSchema">
			 <xs:element name="PerformanceCountersCollector">
			  <xs:complexType>
				  <xs:sequence>
					  <xs:element minOccurs="0" maxOccurs="unbounded" name="PerformanceCounters">
						  <xs:complexType>
							  <xs:attribute name="Objects" type="xs:string" use="required" />
							  <xs:attribute name="Counters" type="xs:string" use="required" />
							  <xs:attribute name="Instances" type="xs:string" use="optional" />
						  </xs:complexType>
					  </xs:element>
				  </xs:sequence>
			  	  <xs:attribute name="StoreLocalizedCounterNames" type="xs:boolean" use="optional" default="false" />
			  </xs:complexType>
			</xs:element>
			</xs:schema>
		'
SET @parameter_formatter = 
        N'<xsl:stylesheet 
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            version="1.0"
            xmlns:z="#RowsetSchema"
            >
        <xsl:template match="/PerformanceCountersCollector">
            <HTML>
            <HEAD>
            <TITLE></TITLE>
            </HEAD>
            <BODY>
            <UL>
            <xsl:apply-templates select="PerformanceCounters"/>
            </UL>
            <HR/>
            </BODY>
            </HTML>
        </xsl:template>
        <xsl:template match="PerformanceCounters">
            <LI>
            \<xsl:value-of select="@Objects"/>
            <xsl:if test="@Instances">(<xsl:value-of select="@Instances"/>)</xsl:if>
            \<xsl:value-of select="@Counters"/>
            </LI>
        </xsl:template>
        </xsl:stylesheet>'
SET @collection_package_id = 'C2EAABC1-5BF3-4127-BEB3-26E94D026E7D'
SET @upload_package_id = '08D854CB-0D45-4E96-92C6-227A5DCD7066'

IF(EXISTS (SELECT * FROM dbo.syscollector_collector_types 
            WHERE collector_type_uid = @collector_type_uid))
BEGIN
    PRINT 'Updating Performance counters collector type'
    EXEC sp_syscollector_update_collector_type 
            @collector_type_uid = @collector_type_uid,
            @name = @name,
            @parameter_schema = @parameter_schema,
            @parameter_formatter = @parameter_formatter,
            @collection_package_id = @collection_package_id,
            @upload_package_id = @upload_package_id
END
ELSE
BEGIN
    PRINT 'Creating Performance Counters collector type'
    EXEC sp_syscollector_create_collector_type
            @collector_type_uid = @collector_type_uid,
            @name = @name,
            @parameter_schema = @parameter_schema,
            @parameter_formatter = @parameter_formatter,
            @collection_package_id = @collection_package_id,
            @upload_package_id = @upload_package_id
END

UPDATE syscollector_collector_types 
SET is_system = 1
WHERE collector_type_uid = @collector_type_uid
GO

-- TSQL query collector type
DECLARE @collector_type_uid uniqueidentifier
DECLARE @name sysname
DECLARE @parameter_schema xml
DECLARE @parameter_formatter xml
DECLARE @collection_package_id uniqueidentifier
DECLARE @upload_package_id uniqueidentifier

SET @collector_type_uid = '302E93D1-3424-4be7-AA8E-84813ECF2419'
SET @name = 'Generic T-SQL Query Collector Type'
SET @parameter_schema = '<?xml version="1.0" encoding="utf-8"?>
        <xs:schema targetNamespace="DataCollectorType" xmlns:xs="http://www.w3.org/2001/XMLSchema">
         <xs:element name="TSQLQueryCollector">
          <xs:complexType>
           <xs:sequence>
            <xs:element name="Query" minOccurs="1" maxOccurs="unbounded">
             <xs:complexType>
              <xs:sequence>
               <xs:element name="Value" type="xs:string" />
               <xs:element name="OutputTable" type="xs:string" />
              </xs:sequence>
             </xs:complexType>
            </xs:element>
            <xs:element name="Databases" minOccurs="0" maxOccurs="1">
             <xs:complexType>
              <xs:sequence>
               <xs:element name="Database" minOccurs="0" maxOccurs="unbounded" type="xs:string" />
              </xs:sequence>
              <xs:attribute name="UseSystemDatabases" type="xs:boolean" use="optional" />
              <xs:attribute name="UseUserDatabases" type="xs:boolean" use="optional" />
             </xs:complexType>
            </xs:element>
           </xs:sequence>
          </xs:complexType>
         </xs:element>
        </xs:schema>'
SET @parameter_formatter = 
        N'<xsl:stylesheet 
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            version="1.0"
            xmlns:z="#RowsetSchema"
            >
        <xsl:template match="/TSQLQueryCollector">
            <HTML>
            <HEAD>
            <TITLE></TITLE>
            </HEAD>
            <BODY>
            <xsl:apply-templates select="Query"/>
            </BODY>
            </HTML>
        </xsl:template>
        <xsl:template match="Query">
            <I>
            <xsl:value-of select="OutputTable"/>
            </I> <BR/>
            <PRE>
            <xsl:value-of select="Value"/>
            </PRE>
            <HR/>
        </xsl:template>
        </xsl:stylesheet>'
SET @collection_package_id = '292B1476-0F46-4490-A9B7-6DB724DE3C0B'
SET @upload_package_id = '6EB73801-39CF-489C-B682-497350C939F0'

IF(EXISTS (SELECT * FROM dbo.syscollector_collector_types 
            WHERE collector_type_uid = @collector_type_uid))
BEGIN
    PRINT 'Updating TSQL Query collector type'
    EXEC sp_syscollector_update_collector_type 
            @collector_type_uid = @collector_type_uid,
            @name = @name,
            @parameter_schema = @parameter_schema,
            @parameter_formatter = @parameter_formatter,
            @collection_package_id = @collection_package_id,
            @upload_package_id = @upload_package_id
END
ELSE
BEGIN
    PRINT 'Creating T-SQL Query collector type'
    EXEC sp_syscollector_create_collector_type
            @collector_type_uid = @collector_type_uid,
            @name = @name,
            @parameter_schema = @parameter_schema,
            @parameter_formatter = @parameter_formatter,
            @collection_package_id = @collection_package_id,
            @upload_package_id = @upload_package_id
END

-- mark the collector type as system
UPDATE syscollector_collector_types 
SET is_system = 1
WHERE collector_type_uid = @collector_type_uid
GO

---------------------------------------------------------------
-- Database objects for TSQL query collector type 
---------------------------------------------------------------

IF (OBJECT_ID(N'[dbo].[syscollector_tsql_query_collector]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [dbo].[syscollector_tsql_query_collector]...'
    CREATE TABLE [dbo].[syscollector_tsql_query_collector] (
        collection_set_uid            uniqueidentifier NOT NULL,
        collection_set_id            int NOT NULL,
        collection_item_id            int NOT NULL,
        collection_package_id        uniqueidentifier NOT NULL,
        upload_package_id            uniqueidentifier NOT NULL,
        )
    ALTER TABLE syscollector_tsql_query_collector
        ADD CONSTRAINT [FK_syscollector_tsql_query_collector_syscollector_collection_items_internal] FOREIGN KEY(collection_set_id, collection_item_id)
        REFERENCES syscollector_collection_items_internal (collection_set_id, collection_item_id) ON DELETE CASCADE
END
GO

IF (OBJECT_ID('dbo.syscollector_collection_item_parameter_update_trigger', 'TR') IS NOT NULL)
BEGIN
    PRINT 'Dropping trigger [dbo].[syscollector_collection_item_parameter_update_trigger] on [dbo].[syscollector_collection_items_internal]'
    DROP TRIGGER [dbo].[syscollector_collection_item_parameter_update_trigger]
END
GO

PRINT 'Creating trigger [dbo].[syscollector_collection_item_parameter_update_trigger] on [dbo].[syscollector_collection_items_internal]'
GO
CREATE TRIGGER [dbo].[syscollector_collection_item_parameter_update_trigger] on [dbo].[syscollector_collection_items_internal]
FOR UPDATE
AS
BEGIN
    DECLARE @collection_set_id int
    DECLARE @collection_item_id int

    -- remove the TSQL query collection item that was updated so packages will be regenerated 
    -- base on the new parameters
    IF (NOT UPDATE (parameters))
       RETURN

    -- clean up the SSIS packages that are left behind
    DECLARE inserted_cursor CURSOR LOCAL FOR
        SELECT collection_set_id, collection_item_id
        FROM inserted
    
    OPEN inserted_cursor
    FETCH inserted_cursor INTO @collection_set_id, @collection_item_id

    WHILE @@FETCH_STATUS = 0
    BEGIN
        DELETE FROM dbo.syscollector_tsql_query_collector 
        WHERE collection_set_id = @collection_set_id
        AND collection_item_id = @collection_item_id

        FETCH inserted_cursor INTO @collection_set_id, @collection_item_id
    END

    CLOSE inserted_cursor
    DEALLOCATE inserted_cursor
END
GO

IF (OBJECT_ID('dbo.syscollector_tsql_query_collector_delete_trigger', 'TR') IS NOT NULL)
BEGIN
    PRINT 'Dropping trigger [dbo].[syscollector_tsql_query_collector_delete_trigger] on [dbo].[syscollector_tsql_query_collector]'
    DROP TRIGGER [dbo].[syscollector_tsql_query_collector_delete_trigger]
END
GO

PRINT 'Creating trigger [dbo].[syscollector_tsql_query_collector_delete_trigger] on [dbo].[syscollector_tsql_query_collector]'
GO
CREATE TRIGGER [dbo].[syscollector_tsql_query_collector_delete_trigger] on [dbo].[syscollector_tsql_query_collector]
FOR DELETE
AS
BEGIN
    -- remove the SSIS packages left behind when the collection item is deleted 
    DECLARE @collection_package_id uniqueidentifier
    DECLARE @collection_package_folderid uniqueidentifier
    DECLARE @collection_package_name sysname

    DECLARE @upload_package_id  uniqueidentifier
    DECLARE @upload_package_folderid  uniqueidentifier
    DECLARE @upload_package_name  sysname

    DECLARE deleted_cursor CURSOR LOCAL FOR
        SELECT collection_package_id, upload_package_id
        FROM deleted
    
    OPEN deleted_cursor
    FETCH deleted_cursor INTO @collection_package_id, @upload_package_id

    WHILE @@FETCH_STATUS = 0
    BEGIN
        SELECT 
            @collection_package_name = name,
            @collection_package_folderid = folderid
        FROM sysssispackages
        WHERE @collection_package_id = id

        SELECT 
            @upload_package_name = name,
            @upload_package_folderid = folderid
        FROM sysssispackages
        WHERE @upload_package_id = id

        EXEC dbo.sp_ssis_deletepackage
            @name = @collection_package_name,
            @folderid = @collection_package_folderid

        EXEC dbo.sp_ssis_deletepackage
            @name = @upload_package_name,
            @folderid = @upload_package_folderid

        FETCH deleted_cursor INTO @collection_package_id, @upload_package_id
    END

    CLOSE deleted_cursor
    DEALLOCATE deleted_cursor
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_create_tsql_query_collector]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_create_tsql_query_collector]...'
    DROP PROCEDURE [dbo].[sp_syscollector_create_tsql_query_collector]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_create_tsql_query_collector]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_create_tsql_query_collector]
    @collection_set_uid            uniqueidentifier,
    @collection_item_id            int,
    @collection_package_id        uniqueidentifier,
    @upload_package_id            uniqueidentifier
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND 
        NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND 
        NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_operator'' or ''dc_proxy')
        RETURN(1) -- Failure
    END

    DECLARE @errMsg VARCHAR(256)
    DECLARE @collection_set_id int
    SELECT @collection_set_id = s.collection_set_id
    FROM dbo.syscollector_collection_items i, dbo.syscollector_collection_sets s
    WHERE i.collection_item_id = @collection_item_id
    AND i.collector_type_uid = '302E93D1-3424-4be7-AA8E-84813ECF2419'
    AND s.collection_set_uid = @collection_set_uid

    -- Verify that the collection item exists of the correct type
    IF (@collection_set_id IS NULL)
    BEGIN        
        SELECT @errMsg = CONVERT(VARCHAR(36), @collection_set_uid) + ', ' + CONVERT(VARCHAR(36), @collection_item_id)
        RAISERROR(14262, -1, -1, '@collection_set_uid, @collection_item_id', @errMsg)
        RETURN(1)
    END

    -- Get the names and folder ids for the generated packages
    DECLARE @upload_package_name sysname
    DECLARE @upload_package_folder_id uniqueidentifier
    SELECT @upload_package_name = name, @upload_package_folder_id = folderid
    FROM sysssispackages
    WHERE id = @upload_package_id
    
    IF (@upload_package_name IS NULL) 
    BEGIN
        SELECT @errMsg = @upload_package_name + ', ' + CONVERT(VARCHAR(36), @upload_package_folder_id)
        RAISERROR(14262, -1, -1, '@upload_package_name, @upload_package_folder_id', @errMsg)
        RETURN(1)
    END

    DECLARE @collection_package_name sysname
    DECLARE @collection_package_folder_id uniqueidentifier
    SELECT @collection_package_name = name, @collection_package_folder_id = folderid
    FROM sysssispackages
    WHERE id = @collection_package_id
    
    IF (@collection_package_name IS NULL) 
    BEGIN
        SELECT @errMsg = @collection_package_name + ', ' + CONVERT(VARCHAR(36), @collection_package_folder_id)
        RAISERROR(14262, -1, -1, '@collection_package_name, @collection_package_folder_id', @errMsg)
        RETURN(1)
    END

    -- we need to allow dc_admin to delete these packages along with the collection set when 
    -- the set is deleted
    EXEC sp_ssis_setpackageroles @name = @upload_package_name, @folderid = @upload_package_folder_id, @readrole = NULL, @writerole = N'dc_admin'
    EXEC sp_ssis_setpackageroles @name = @collection_package_name, @folderid = @collection_package_folder_id, @readrole = NULL, @writerole = N'dc_admin'

    INSERT INTO [dbo].[syscollector_tsql_query_collector]
    (
        collection_set_uid,
        collection_set_id, 
        collection_item_id,
        collection_package_id,
        upload_package_id
    )
    VALUES
    (
        @collection_set_uid,
        @collection_set_id,
        @collection_item_id,
        @collection_package_id,
        @upload_package_id
    )
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_get_tsql_query_collector_package_ids]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_get_tsql_query_collector_package_ids]...'
    DROP PROCEDURE [dbo].[sp_syscollector_get_tsql_query_collector_package_ids]
END
GO

-- get and return the collection and upload package IDs
-- if they do not exist, return empty IDs
PRINT 'Creating procedure [dbo].[sp_syscollector_get_tsql_query_collector_package_ids]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_get_tsql_query_collector_package_ids]
    @collection_set_uid            uniqueidentifier,
    @collection_item_id            int,
    @collection_package_id        uniqueidentifier OUTPUT,
    @upload_package_id            uniqueidentifier OUTPUT,
    @collection_package_name    sysname OUTPUT,
    @upload_package_name        sysname OUTPUT    
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND 
        NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND
        NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_operator'' or ''dc_proxy')
        RETURN(1) -- Failure
    END

    SELECT @collection_package_id = collection_package_id,
        @upload_package_id = upload_package_id
    FROM dbo.syscollector_tsql_query_collector
    WHERE @collection_item_id = collection_item_id 
      AND @collection_set_uid = collection_set_uid

    IF(@collection_package_id IS NOT NULL AND @upload_package_id IS NOT NULL)
    BEGIN
        SELECT @collection_package_name = name
        FROM dbo.sysssispackages
        WHERE @collection_package_id = id

        SELECT @upload_package_name = name
        FROM dbo.sysssispackages
        WHERE @upload_package_id = id
    END
END
GO

-- SQLTrace collector type
DECLARE @collector_type_uid uniqueidentifier
DECLARE @name sysname
DECLARE @parameter_schema xml
DECLARE @parameter_formatter xml
DECLARE @collection_package_id uniqueidentifier
DECLARE @upload_package_id uniqueidentifier

SET @collector_type_uid = '0E218CF8-ECB5-417B-B533-D851C0251271'
SET @name = 'Generic SQL Trace Collector Type'
SET @parameter_schema = '<?xml version="1.0" encoding="utf-8"?>
                        <xs:schema targetNamespace="DataCollectorType" xmlns:xs="http://www.w3.org/2001/XMLSchema">
                         <xs:element name="SqlTraceCollector">
                          <xs:complexType>
                           <xs:sequence>
                            <xs:element name="Events">
                             <xs:complexType>
                              <xs:sequence>
                               <xs:element minOccurs="0" maxOccurs="unbounded" name="EventType">
                                <xs:complexType>
                                 <xs:sequence>
                                  <xs:element maxOccurs="unbounded" name="Event">
                                   <xs:complexType>
                                    <xs:attribute name="id" type="xs:unsignedByte" use="required" />
                                    <xs:attribute name="name" type="xs:string" use="required" />
                                    <xs:attribute name="columnslist" type="xs:string" use="optional" />
                                   </xs:complexType>
                                  </xs:element>
                                 </xs:sequence>
                                 <xs:attribute name="id" type="xs:unsignedByte" use="optional" />
                                 <xs:attribute name="name" type="xs:string" use="required" />
                                </xs:complexType>
                               </xs:element>
                              </xs:sequence>
                             </xs:complexType>
                            </xs:element>
                            <xs:element name="Filters">
                             <xs:complexType>
                              <xs:sequence>
                               <xs:element name="Filter" minOccurs="0" maxOccurs="unbounded">
                                <xs:complexType>
                                 <xs:attribute name="columnid" type="xs:unsignedByte" use="required" />
                                 <xs:attribute name="columnname" type="xs:string" use="required" />
                                 <xs:attribute name="logical_operator" type="xs:string" use="required" />
                                 <xs:attribute name="comparison_operator" type="xs:string" use="required" />
                                 <xs:attribute name="value" type="xs:string" use="required" />
                                </xs:complexType>
                               </xs:element>
                              </xs:sequence>
                             </xs:complexType>
                            </xs:element>
                           </xs:sequence>
                           <xs:attribute name="use_default" type="xs:boolean" />
                          </xs:complexType>
                         </xs:element>
                        </xs:schema>'
SET @parameter_formatter = 
        N'<xsl:stylesheet 
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            version="1.0"
            xmlns:z="#RowsetSchema"
            >
        <xsl:template match="/SqlTraceCollector">
            <HTML>
            <HEAD>
            <TITLE></TITLE>
            </HEAD>
            <BODY>
            <xsl:apply-templates select="Events"/>
            <HR/>
            <xsl:apply-templates select="Filters"/>
            </BODY>
            </HTML>
        </xsl:template>
        <xsl:template match="Events">
            <xsl:apply-templates select="EventType"/>
            <BR/>
        </xsl:template>
        <xsl:template match="EventType">
            <I> <PRE> ID = <xsl:value-of select="@id"/> - <xsl:value-of select="@name"/> </PRE> </I> 
            <UL>
            <xsl:apply-templates select="Event"/>
            </UL>
        </xsl:template>
        <xsl:template match="Event">
            <LI>
            <PRE> ID = <xsl:value-of select="@id"/> - <xsl:value-of select="@name"/> </PRE>
            </LI>
        </xsl:template>
        <xsl:template match="Filters">
            <UL>
            <xsl:apply-templates select="Filter" />
            </UL>
        </xsl:template>
        <xsl:template match="Filter">
            <PRE> <xsl:value-of select="@logical_operator"/> - <xsl:value-of select="@columnname"/> - <xsl:value-of select="@comparison_operator"/> - <xsl:value-of select="@value"/> </PRE>
        </xsl:template>
        </xsl:stylesheet>'
SET @collection_package_id = '0E149FC9-1046-4DE6-98BF-4B22ED6F6C42'
SET @upload_package_id = 'F389A8E6-5A17-4056-ABFD-C8B823F2092E' 

IF(EXISTS (SELECT * FROM dbo.syscollector_collector_types 
            WHERE collector_type_uid = @collector_type_uid))
BEGIN
    PRINT 'Updating SQL Trace collector type'
    EXEC sp_syscollector_update_collector_type 
            @collector_type_uid = @collector_type_uid,
            @name = @name,
            @parameter_schema = @parameter_schema,
            @parameter_formatter = @parameter_formatter,
            @collection_package_id = @collection_package_id,
            @upload_package_id = @upload_package_id
END
ELSE
BEGIN
    PRINT 'Creating SQL Trace collector type'
    EXEC sp_syscollector_create_collector_type
            @collector_type_uid = @collector_type_uid,
            @name = @name,
            @parameter_schema = @parameter_schema,
            @parameter_formatter = @parameter_formatter,
            @collection_package_id = @collection_package_id,
            @upload_package_id = @upload_package_id
END

-- mark the collector type as system
UPDATE syscollector_collector_types 
SET is_system = 1
WHERE collector_type_uid = @collector_type_uid
GO

-- Query Activity Collector Type
DECLARE @collector_type_uid uniqueidentifier
DECLARE @name sysname
DECLARE @collection_package_id uniqueidentifier
DECLARE @upload_package_id uniqueidentifier

SET @collector_type_uid = '14AF3C12-38E6-4155-BD29-F33E7966BA23'
SET @name = 'Query Activity Collector Type'
SET @collection_package_id = '0B68FC9D-23DC-48F3-A937-90A0A8943D0E'
SET @upload_package_id = '833DB628-8E19-47A3-92C5-FB1779B52E76'

IF(EXISTS (SELECT * FROM dbo.syscollector_collector_types 
            WHERE collector_type_uid = @collector_type_uid))
BEGIN
    PRINT 'Updating Query Activity Collector Type'
    EXEC sp_syscollector_update_collector_type
        @collector_type_uid = @collector_type_uid,
        @name = @name,
        @parameter_schema = NULL,
        @parameter_formatter = NULL,
        @collection_package_id = @collection_package_id,
        @upload_package_id = @upload_package_id
END
ELSE
BEGIN
    PRINT 'Creating Query Activity Collector Type'
    EXEC sp_syscollector_create_collector_type
        @collector_type_uid = @collector_type_uid,
        @name = @name,
        @parameter_schema = NULL,
        @parameter_formatter = NULL,
        @collection_package_id = @collection_package_id,
        @upload_package_id = @upload_package_id
END

-- mark the collector type as system
UPDATE syscollector_collector_types
SET is_system = 1
WHERE collector_type_uid = @collector_type_uid
GO

---------------------------------------------------------------
-- Out-of-the-box collector objects - Generic schedules for system collection sets
---------------------------------------------------------------
PRINT 'Creating data collector schedules'

DECLARE @schedule_name sysname
SET @schedule_name = N'CollectorSchedule_Every_5min'
IF  NOT EXISTS (SELECT name FROM msdb.dbo.sysschedules_localserver_view WHERE name = @schedule_name)
BEGIN
    EXEC dbo.sp_add_schedule
        @schedule_name = @schedule_name,    -- Schedule name
        @freq_type = 4,                        -- Daily
        @freq_interval = 1,                    -- Recurs every 1 day
        @freq_subday_type = 0x4,            -- Frequency type is "minutes"
        @freq_subday_interval = 5            -- Occurs every 5 minutes
END

SET @schedule_name = N'CollectorSchedule_Every_10min'
IF  NOT EXISTS (SELECT name FROM msdb.dbo.sysschedules_localserver_view WHERE name = @schedule_name)
BEGIN
    EXEC dbo.sp_add_schedule
        @schedule_name = @schedule_name,    -- Schedule name
        @freq_type = 4,                        -- Daily
        @freq_interval = 1,                    -- Recurs every 1 day
        @freq_subday_type = 0x4,            -- Frequency type is "minutes"
        @freq_subday_interval = 10            -- Occurs every 10 minutes
END

SET @schedule_name = N'CollectorSchedule_Every_15min'
IF  NOT EXISTS (SELECT name FROM msdb.dbo.sysschedules_localserver_view WHERE name = @schedule_name)
BEGIN
    EXEC dbo.sp_add_schedule
        @schedule_name = @schedule_name,    -- Schedule name
        @freq_type = 4,                        -- Daily
        @freq_interval = 1,                    -- Recurs every 1 day
        @freq_subday_type = 0x4,            -- Frequency type is "minutes"
        @freq_subday_interval = 15            -- Occurs every 15 minutes
END

SET @schedule_name = N'CollectorSchedule_Every_30min'
IF  NOT EXISTS (SELECT name FROM msdb.dbo.sysschedules_localserver_view WHERE name = @schedule_name)
BEGIN
    EXEC dbo.sp_add_schedule
        @schedule_name = @schedule_name,    -- Schedule name
        @freq_type = 4,                        -- Daily
        @freq_interval = 1,                    -- Recurs every 1 day
        @freq_subday_type = 0x4,            -- Frequency type is "minutes"
        @freq_subday_interval = 30            -- Occurs every 30 minutes
END

SET @schedule_name = N'CollectorSchedule_Every_60min'
IF  NOT EXISTS (SELECT name FROM msdb.dbo.sysschedules_localserver_view WHERE name = @schedule_name)
BEGIN
    EXEC dbo.sp_add_schedule
        @schedule_name = @schedule_name,    -- Schedule name
        @freq_type = 4,                        -- Daily
        @freq_interval = 1,                    -- Recurs every 1 day
        @freq_subday_type = 0x4,            -- Frequency type is "minutes"
        @freq_subday_interval = 60            -- Occurs every 60 minutes
END

SET @schedule_name = N'CollectorSchedule_Every_6h'
IF  NOT EXISTS (SELECT name FROM msdb.dbo.sysschedules_localserver_view WHERE name = @schedule_name)
BEGIN
    EXEC dbo.sp_add_schedule
        @schedule_name = @schedule_name,    -- Schedule name
        @freq_type = 4,                        -- Daily
        @freq_interval = 1,                    -- Recurs every 1 day
        @freq_subday_type = 0x8,            -- Frequency type is "hours"
        @freq_subday_interval = 6            -- Occurs every 6 hours
END
GO

---------------------------------------------------------------
-- Out-of-the-box system Collection Sets 
---------------------------------------------------------------
PRINT 'Creating system Collection Sets...'

------------------------------------------------
-- System collection set: Disk Usage
------------------------------------------------
DECLARE @collection_set_name NVARCHAR(128);
DECLARE @description NVARCHAR(4000);
DECLARE @collection_set_id int;
DECLARE @collection_set_uid uniqueidentifier;
DECLARE @collection_mode smallint;
DECLARE @schedule_name sysname;
DECLARE @days_until_expiration smallint;
DECLARE @name_id int;
DECLARE @description_id int;

-- The GUID below identifies this collection set and is used to locate the data collected by this collection set. 
-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection set detils (syscollector_collection_sets) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14701;
SET @description_id = 14700;
SET @collection_set_uid = N'7B191952-8ECF-4E12-AEB2-EF646EF79FEF';
SET @collection_mode = 1; -- Non-cached
SET @schedule_name = N'CollectorSchedule_Every_6h';
SET @days_until_expiration = 730;

SET @description = FORMATMESSAGE(@description_id);
SET @collection_set_name = ISNULL (FORMATMESSAGE(@name_id), 'Disk Usage');

IF EXISTS (SELECT * FROM dbo.syscollector_collection_sets_internal WHERE collection_set_uid = @collection_set_uid)
BEGIN
    RAISERROR ('Updating system Collection Set "%s"...', 0, 1, @collection_set_name) WITH NOWAIT;
    -- We are updating an existing collection set -- get its ID 
    SELECT @collection_set_id = collection_set_id
    FROM syscollector_collection_sets 
    WHERE collection_set_uid = @collection_set_uid;
    
    -- Temporarily clear the is_system flag so that we can modify the collection set definition
    UPDATE syscollector_collection_sets
    SET is_system = 0
    WHERE collection_set_id = @collection_set_id
    
    -- Don't override the current expiration period or schedule settings, since the user may have customized these
    EXEC dbo.sp_syscollector_update_collection_set
        @collection_set_id = @collection_set_id, 
        @new_name = @collection_set_name, 
        @schedule_name = @schedule_name, -- avoid mismatch in uid if schedule was deleted and recreated
        @collection_mode = @collection_mode, 
        @logging_level = 0,
        @description = @description;
END
ELSE
BEGIN    
    RAISERROR ('Creating system Collection Set "%s"...', 0, 1, @collection_set_name) WITH NOWAIT;
    EXEC dbo.sp_syscollector_create_collection_set
        @collection_set_uid = @collection_set_uid,
        @name = @collection_set_name, 
        @schedule_name = @schedule_name, 
        @collection_mode = @collection_mode, 
        @days_until_expiration = @days_until_expiration, 
        @description = @description,
        @logging_level = 0, 
        @collection_set_id = @collection_set_id OUTPUT;
END

-- for localization of collection set name and description
UPDATE syscollector_collection_sets_internal
SET name_id = @name_id, description_id = @description_id
WHERE collection_set_uid = @collection_set_uid;

-- Add collection items
DECLARE @collection_item_name NVARCHAR(128);
DECLARE @collection_item_old_name NVARCHAR(128);
DECLARE @collection_item_id int;
DECLARE @frequency int;

-- Item 1: disk_usage DMV query
DECLARE @parameters xml;
SELECT @parameters = convert(xml, N'<ns:TSQLQueryCollector xmlns:ns="DataCollectorType">
<Query>
<Value>
DECLARE @dbsize bigint 
DECLARE @logsize bigint 
DECLARE @ftsize bigint 
DECLARE @reservedpages bigint 
DECLARE @pages bigint 
DECLARE @usedpages bigint

SELECT @dbsize = SUM(convert(bigint,case when type = 0 then size else 0 end)) 
      ,@logsize = SUM(convert(bigint,case when type = 1 then size else 0 end)) 
      ,@ftsize = SUM(convert(bigint,case when type = 4 then size else 0 end)) 
FROM sys.database_files

SELECT @reservedpages = SUM(a.total_pages) 
       ,@usedpages = SUM(a.used_pages) 
       ,@pages = SUM(CASE 
                        WHEN it.internal_type IN (202,204) THEN 0 
                        WHEN a.type != 1 THEN a.used_pages 
                        WHEN p.index_id &lt; 2 THEN a.data_pages 
                        ELSE 0 
                     END) 
FROM sys.partitions p  
JOIN sys.allocation_units a ON p.partition_id = a.container_id 
LEFT JOIN sys.internal_tables it ON p.object_id = it.object_id 

SELECT 
        @dbsize as ''dbsize'',
        @logsize as ''logsize'',
        @ftsize as ''ftsize'',
        @reservedpages as ''reservedpages'',
        @usedpages as ''usedpages'',
        @pages as ''pages''
</Value>
<OutputTable>disk_usage</OutputTable>
</Query>
<Databases UseSystemDatabases="true" UseUserDatabases="true" />
</ns:TSQLQueryCollector>');

-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection item detils (syscollector_collection_items) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14702;
SET @collection_item_name = FORMATMESSAGE(@name_id);
SET @collection_item_old_name = 'Disk Usage - Data Files';
SET @frequency = 60;  -- Ignored (this collection set uses non-cached collection mode)

IF ISNULL (@collection_item_name, '') = '' 
BEGIN
    SET @collection_item_name = @collection_item_old_name;
END;

SET @collection_item_id = NULL;
SELECT @collection_item_id = collection_item_id
FROM syscollector_collection_items_internal 
WHERE collection_set_id = @collection_set_id 
    AND (name = @collection_item_name OR name = @collection_item_old_name);

IF (@collection_item_id IS NOT NULL)
BEGIN
    RAISERROR ('Updating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_update_collection_item 
        @collection_item_id = @collection_item_id, 
        @new_name = @collection_item_name, 
        @frequency = @frequency, 
        @parameters = @parameters;
END
ELSE
BEGIN
    RAISERROR ('Creating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_create_collection_item
        @collection_set_id = @collection_set_id,
        @collector_type_uid = N'302E93D1-3424-4BE7-AA8E-84813ECF2419',
        @name = @collection_item_name,
        @parameters = @parameters,
        @frequency = @frequency, 
        @collection_item_id = @collection_item_id output;
END;

-- for localization of collection item name
UPDATE syscollector_collection_items_internal
SET name_id = @name_id
WHERE collection_item_id = @collection_item_id;

-- Item 2: log_usage DMV query
SELECT @parameters = convert(xml, N'<ns:TSQLQueryCollector xmlns:ns="DataCollectorType">
<Query>
<Value>
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
DECLARE @tran_log_space_usage table( 
        database_name sysname
,       log_size_mb float
,       log_space_used float
,       status int
); 
INSERT INTO @tran_log_space_usage 
EXEC(''DBCC SQLPERF (LOGSPACE) WITH NO_INFOMSGS'');
 
SELECT 
    database_name,
    log_size_mb,
    log_space_used,
    status    
FROM @tran_log_space_usage
</Value>
<OutputTable>log_usage</OutputTable>
</Query>
</ns:TSQLQueryCollector>');

-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection item detils (syscollector_collection_items) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14703;
SET @collection_item_name = FORMATMESSAGE(@name_id);
SET @collection_item_old_name = 'Disk Usage - Log Files';
SET @frequency = 60;  -- Ignored (this collection set uses non-cached collection mode)

IF ISNULL (@collection_item_name, '') = '' 
BEGIN
    SET @collection_item_name = @collection_item_old_name;
END;

SET @collection_item_id = NULL;
SELECT @collection_item_id = collection_item_id
FROM syscollector_collection_items_internal 
WHERE collection_set_id = @collection_set_id 
    AND (name = @collection_item_name OR name = @collection_item_old_name);

IF (@collection_item_id IS NOT NULL)
BEGIN
    RAISERROR ('Updating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_update_collection_item 
        @collection_item_id = @collection_item_id, 
        @new_name = @collection_item_name, 
        @frequency = @frequency, 
        @parameters = @parameters;
END
ELSE
BEGIN
    RAISERROR ('Creating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_create_collection_item
        @collection_set_id = @collection_set_id,
        @collector_type_uid = N'302E93D1-3424-4BE7-AA8E-84813ECF2419',
        @name = @collection_item_name,
        @parameters = @parameters,
        @frequency = @frequency, 
        @collection_item_id = @collection_item_id output;
END;

-- for localization of collection item name
UPDATE syscollector_collection_items_internal
SET name_id = @name_id
WHERE collection_item_id = @collection_item_id;

-- Turn the is_system flag on so users can't change the definition of this collection set 
UPDATE syscollector_collection_sets
SET is_system = 1
WHERE collection_set_id = @collection_set_id
GO


------------------------------------------------
-- System collection set: Server Activity
------------------------------------------------
DECLARE @collection_set_name NVARCHAR(128);
DECLARE @description NVARCHAR(4000);
DECLARE @collection_set_id int;
DECLARE @collection_set_uid uniqueidentifier;
DECLARE @collection_mode smallint;
DECLARE @schedule_name sysname;
DECLARE @days_until_expiration smallint;
DECLARE @name_id int;
DECLARE @description_id int;

-- The GUID below identifies this collection set and is used to locate the data collected by this collection set. 
-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection set detils (syscollector_collection_sets) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14705;
SET @description_id = 14704;
SET @collection_set_uid = N'49268954-4FD4-4EB6-AA04-CD59D9BB5714';
SET @collection_mode = 0; -- Cached
SET @schedule_name = N'CollectorSchedule_Every_15min';
SET @days_until_expiration = 14;

SET @description = FORMATMESSAGE(@description_id);
SET @collection_set_name = ISNULL (FORMATMESSAGE(@name_id), 'Server Activity');

IF EXISTS (SELECT * FROM dbo.syscollector_collection_sets_internal WHERE collection_set_uid = @collection_set_uid)
BEGIN
    RAISERROR ('Updating system Collection Set "%s"...', 0, 1, @collection_set_name) WITH NOWAIT;
    -- We are updating an existing collection set -- get its ID 
    SELECT @collection_set_id = collection_set_id
    FROM syscollector_collection_sets 
    WHERE collection_set_uid = @collection_set_uid;
    
    -- Temporarily clear the is_system flag so that we can modify the collection set definition
    UPDATE syscollector_collection_sets
    SET is_system = 0
    WHERE collection_set_id = @collection_set_id
    
    -- Don't override the current expiration period or schedule settings, since the user may have customized these
    EXEC dbo.sp_syscollector_update_collection_set
        @collection_set_id = @collection_set_id, 
        @new_name = @collection_set_name, 
        @schedule_name = @schedule_name, -- avoid mismatch in uid if schedule was deleted and recreated
        @collection_mode = @collection_mode, 
        @logging_level = 0,
        @description = @description;
END
ELSE
BEGIN    
    RAISERROR ('Creating system Collection Set "%s"...', 0, 1, @collection_set_name) WITH NOWAIT;
    EXEC dbo.sp_syscollector_create_collection_set
        @collection_set_uid = @collection_set_uid,
        @name = @collection_set_name, 
        @schedule_name = @schedule_name, 
        @collection_mode = @collection_mode, 
        @days_until_expiration = @days_until_expiration, 
        @description = @description, 
        @logging_level = 0,
        @collection_set_id = @collection_set_id OUTPUT;
END

-- for localization of collection set name and description
UPDATE syscollector_collection_sets_internal
SET name_id = @name_id, description_id = @description_id
WHERE collection_set_uid = @collection_set_uid;

-- Add collection items
DECLARE @collection_item_name NVARCHAR(128);
DECLARE @collection_item_old_name NVARCHAR(128);
DECLARE @collection_item_id int;
DECLARE @frequency int;
DECLARE @parameters xml;

-- Item 1 - DMV SNAPSHOTS
SELECT @parameters = convert(xml, N'<ns:TSQLQueryCollector xmlns:ns="DataCollectorType">
<Query>
<Value>
SET NOCOUNT ON
SELECT 
    LEFT (wait_type, 45) AS wait_type, 
    SUM (waiting_tasks_count) AS waiting_tasks_count, 
    SUM (wait_time_ms) AS wait_time_ms, 
    SUM (signal_wait_time_ms) AS signal_wait_time_ms
FROM 
(
    SELECT 
        LEFT (wait_type, 45) AS wait_type, 
    waiting_tasks_count, 
    wait_time_ms,  
    signal_wait_time_ms
FROM sys.dm_os_wait_stats 
WHERE waiting_tasks_count &gt; 0 OR wait_time_ms &gt; 0 OR signal_wait_time_ms &gt; 0
    UNION ALL 
    SELECT 
        LEFT (wait_type, 45) AS wait_type, 
        1 AS waiting_tasks_count, 
        wait_duration_ms AS wait_time_ms, 
        0 AS signal_wait_time_ms
    FROM sys.dm_os_waiting_tasks
    WHERE wait_duration_ms &gt; 60000
) AS merged_wait_stats
GROUP BY wait_type
</Value>
<OutputTable>os_wait_stats</OutputTable>
</Query>
<Query>
<Value>
SET NOCOUNT ON
SELECT 
  LEFT(latch_class,45) as latch_class,
  waiting_requests_count,
  wait_time_ms
FROM sys.dm_os_latch_stats 
WHERE waiting_requests_count &gt; 0 OR wait_time_ms &gt; 0
</Value>
<OutputTable>os_latch_stats</OutputTable>
</Query>
<Query>
<Value>
SET NOCOUNT ON
SELECT 
    pm.physical_memory_in_use_kb            AS sql_physical_memory_in_use_kb, 
    pm.large_page_allocations_kb            AS sql_large_page_allocations_kb, 
    pm.locked_page_allocations_kb           AS sql_locked_page_allocations_kb, 
    pm.total_virtual_address_space_kb       AS sql_total_virtual_address_space_kb, 
    pm.virtual_address_space_reserved_kb    AS sql_virtual_address_space_reserved_kb, 
    pm.virtual_address_space_committed_kb   AS sql_virtual_address_space_committed_kb, 
    pm.virtual_address_space_available_kb   AS sql_virtual_address_space_available_kb, 
    pm.page_fault_count                     AS sql_page_fault_count, 
    pm.memory_utilization_percentage        AS sql_memory_utilization_percentage, 
    pm.available_commit_limit_kb            AS sql_available_commit_limit_kb, 
    pm.process_physical_memory_low          AS sql_process_physical_memory_low, 
    pm.process_virtual_memory_low           AS sql_process_virtual_memory_low, 
    
    sm.total_physical_memory_kb             AS system_total_physical_memory_kb, 
    sm.available_physical_memory_kb         AS system_available_physical_memory_kb, 
    sm.total_page_file_kb                   AS system_total_page_file_kb, 
    sm.available_page_file_kb               AS system_available_page_file_kb, 
    sm.system_cache_kb                      AS system_cache_kb, 
    sm.kernel_paged_pool_kb                 AS system_kernel_paged_pool_kb, 
    sm.kernel_nonpaged_pool_kb              AS system_kernel_nonpaged_pool_kb, 
    sm.system_high_memory_signal_state      AS system_high_memory_signal_state, 
    sm.system_low_memory_signal_state       AS system_low_memory_signal_state, 
    
    si.bpool_commit_target                  AS bpool_commit_target, 
    si.bpool_committed                      AS bpool_committed, 
    si.bpool_visible                        AS bpool_visible
FROM sys.dm_os_process_memory AS pm
CROSS JOIN sys.dm_os_sys_memory AS sm   -- single-row DMV
CROSS JOIN sys.dm_os_sys_info AS si;    -- single-row DMV
</Value>
<OutputTable>sql_process_and_system_memory</OutputTable>
</Query>
<Query>
<Value>
SET NOCOUNT ON
SELECT 
    memory_node_id, 
    virtual_address_space_reserved_kb, 
    virtual_address_space_committed_kb, 
    locked_page_allocations_kb, 
    single_pages_kb, 
    multi_pages_kb, 
    shared_memory_reserved_kb, 
    shared_memory_committed_kb
FROM sys.dm_os_memory_nodes
</Value>
<OutputTable>os_memory_nodes</OutputTable>
</Query>
<Query>
<Value>
SET NOCOUNT ON
SELECT 
    type,
    memory_node_id as memory_node_id,
    SUM(single_pages_kb) as single_pages_kb,
    SUM(multi_pages_kb) as multi_pages_kb,
    SUM(virtual_memory_reserved_kb) as virtual_memory_reserved_kb,
    SUM(virtual_memory_committed_kb) as virtual_memory_committed_kb,
    SUM(awe_allocated_kb) as awe_allocated_kb,
    SUM(shared_memory_reserved_kb) as shared_memory_reserved_kb,
    SUM(shared_memory_committed_kb) as shared_memory_committed_kb
FROM sys.dm_os_memory_clerks
GROUP BY type, memory_node_id</Value>
<OutputTable>os_memory_clerks</OutputTable>
</Query>
<Query>
<Value>
SET NOCOUNT ON
SELECT 
    [parent_node_id],
    [scheduler_id],
    [cpu_id],
    [status],
    [is_idle],
    [preemptive_switches_count],
    [context_switches_count],
    [yield_count],
    [current_tasks_count],
    [runnable_tasks_count],
    [work_queue_count],
    [pending_disk_io_count]
FROM sys.dm_os_schedulers
WHERE scheduler_id &lt; 128
</Value>
<OutputTable>os_schedulers</OutputTable>
</Query>
<Query>
<Value>
SELECT 
    DB_NAME (f.database_id) AS database_name, f.database_id, f.name AS logical_file_name, f.[file_id], f.type_desc, 
    CAST (CASE 
        -- Handle UNC paths (e.g. ''\\fileserver\readonlydbs\dept_dw.ndf'' --&gt; ''\\fileserver\readonlydbs'')
        WHEN LEFT (LTRIM (f.physical_name), 2) = ''\\'' 
            THEN LEFT (LTRIM (f.physical_name), CHARINDEX (''\'', LTRIM (f.physical_name), CHARINDEX (''\'', LTRIM (f.physical_name), 3) + 1) - 1)
        -- Handle local paths (e.g. ''C:\Program Files\...\master.mdf'' --&gt; ''C:'') 
        WHEN CHARINDEX (''\'', LTRIM(f.physical_name), 3) &gt; 0 
            THEN UPPER (LEFT (LTRIM (f.physical_name), CHARINDEX (''\'', LTRIM (f.physical_name), 3) - 1))
        ELSE f.physical_name
    END AS nvarchar(255)) AS logical_disk, 
    fs.num_of_reads, fs.num_of_bytes_read, fs.io_stall_read_ms, fs.num_of_writes, fs.num_of_bytes_written, 
    fs.io_stall_write_ms, fs.size_on_disk_bytes
FROM sys.dm_io_virtual_file_stats (default, default) AS fs
INNER JOIN sys.master_files AS f ON fs.database_id = f.database_id AND fs.[file_id] = f.[file_id]
</Value>
<OutputTable>io_virtual_file_stats</OutputTable>
</Query>
</ns:TSQLQueryCollector>');

-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection item detils (syscollector_collection_items) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14706;
SET @collection_item_name = FORMATMESSAGE(@name_id);
SET @collection_item_old_name = 'Server Activity - DMV Snapshots';
SET @frequency = 60;  

IF ISNULL (@collection_item_name, '') = '' 
BEGIN
    SET @collection_item_name = @collection_item_old_name;
END;

SET @collection_item_id = NULL;
SELECT @collection_item_id = collection_item_id
FROM syscollector_collection_items_internal 
WHERE collection_set_id = @collection_set_id 
    AND (name = @collection_item_name OR name = @collection_item_old_name);

IF (@collection_item_id IS NOT NULL)
BEGIN
    RAISERROR ('Updating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_update_collection_item 
        @collection_item_id = @collection_item_id, 
        @new_name = @collection_item_name, 
        @frequency = @frequency, 
        @parameters = @parameters;
END
ELSE
BEGIN
    RAISERROR ('Creating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_create_collection_item
        @collection_set_id = @collection_set_id,
        @collector_type_uid = N'302E93D1-3424-4BE7-AA8E-84813ECF2419',
        @name = @collection_item_name,
        @parameters = @parameters,
        @frequency = @frequency, 
        @collection_item_id = @collection_item_id output;
END;

-- for localization of collection item name
UPDATE syscollector_collection_items_internal
SET name_id = @name_id
WHERE collection_item_id = @collection_item_id;

-- Item 2 - PERFORMANCE COUNTERS
SELECT @parameters = convert(xml, N'<ns:PerformanceCountersCollector xmlns:ns="DataCollectorType">
 <PerformanceCounters Objects="Memory" Counters="% Committed Bytes In Use" />
 <PerformanceCounters Objects="Memory" Counters="Available Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Cache Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Cache Faults/sec" />
 <PerformanceCounters Objects="Memory" Counters="Committed Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Free &amp; Zero Page List Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Modified Page List Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Pages/sec" />
 <PerformanceCounters Objects="Memory" Counters="Page Reads/sec" />
 <PerformanceCounters Objects="Memory" Counters="Page Write/sec" />
 <PerformanceCounters Objects="Memory" Counters="Page Faults/sec" />
 <PerformanceCounters Objects="Memory" Counters="Pool Nonpaged Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Pool Paged Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Standby Cache Core Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Standby Cache Normal Priority Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Standby Cache Reserve Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Pool Paged Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Write Copies/sec" />
 <PerformanceCounters Objects="Process" Counters="*" Instances="_Total" />
 <PerformanceCounters Objects="Process" Counters="*" Instances="$(TARGETPROCESS)" />
 <PerformanceCounters Objects="Process" Counters="Thread Count" Instances="*" />
 <PerformanceCounters Objects="Process" Counters="% Processor Time" Instances="*" />
 <PerformanceCounters Objects="Process" Counters="IO Read Bytes/sec" Instances="*" />
 <PerformanceCounters Objects="Process" Counters="IO Write Bytes/sec" Instances="*" />
 <PerformanceCounters Objects="Process" Counters="Private Bytes" Instances="*" />
 <PerformanceCounters Objects="Process" Counters="Working Set" Instances="*" />
 <PerformanceCounters Objects="Processor" Counters="% Processor Time" Instances="*" />
 <PerformanceCounters Objects="Processor" Counters="% User Time" Instances="*" />
 <PerformanceCounters Objects="Processor" Counters="% Privileged Time" Instances="*" />
 <PerformanceCounters Objects="Server Work Queues" Counters="Queue Length" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="% Disk Time" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Avg. Disk Queue Length" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Avg. Disk Read Queue Length" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Avg. Disk Write Queue Length" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Avg. Disk sec/Read" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Avg. Disk sec/Write" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Avg. Disk sec/Transfer" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Disk Reads/sec" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Disk Bytes/sec" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Disk Writes/sec" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Split IO/sec" Instances="*" />
 <PerformanceCounters Objects="System" Counters="Processor Queue Length" />
 <PerformanceCounters Objects="System" Counters="File Read Operations/sec" />
 <PerformanceCounters Objects="System" Counters="File Write Operations/sec" />
 <PerformanceCounters Objects="System" Counters="File Control Operations/sec" />
 <PerformanceCounters Objects="System" Counters="File Read Bytes/sec" />
 <PerformanceCounters Objects="System" Counters="File Write Bytes/sec" />
 <PerformanceCounters Objects="System" Counters="File Control Bytes/sec" />
 <PerformanceCounters Objects="Network Interface" Counters="Bytes Total/sec" Instances="*" />
 <PerformanceCounters Objects="Network Interface" Counters="Output Queue Length" Instances="*" />
 <PerformanceCounters Objects="$(INSTANCE):Buffer Manager" Counters="Stolen pages"/>
 <PerformanceCounters Objects="$(INSTANCE):Buffer Manager" Counters="Page life expectancy"/>
 <PerformanceCounters Objects="$(INSTANCE):Memory Manager" Counters="Memory Grants Outstanding"/>
 <PerformanceCounters Objects="$(INSTANCE):Memory Manager" Counters="Memory Grants Pending"/>
 <PerformanceCounters Objects="$(INSTANCE):Databases" Counters="Transactions/sec"  Instances="_Total"/>
 <PerformanceCounters Objects="$(INSTANCE):Databases" Counters="Transactions/sec"  Instances="tempdb"/>
 <PerformanceCounters Objects="$(INSTANCE):Databases" Counters="Active Transactions"  Instances="*"/>
 <PerformanceCounters Objects="$(INSTANCE):General Statistics" Counters="Logins/sec"  />
 <PerformanceCounters Objects="$(INSTANCE):General Statistics" Counters="Logouts/sec"  />
 <PerformanceCounters Objects="$(INSTANCE):General Statistics" Counters="User Connections"  />
 <PerformanceCounters Objects="$(INSTANCE):General Statistics" Counters="Logical Connections"  />
 <PerformanceCounters Objects="$(INSTANCE):General Statistics" Counters="Transactions"  />
 <PerformanceCounters Objects="$(INSTANCE):General Statistics" Counters="Processes blocked"  />
 <PerformanceCounters Objects="$(INSTANCE):General Statistics" Counters="Active Temp Tables"  />
 <PerformanceCounters Objects="$(INSTANCE):SQL Statistics" Counters="Batch Requests/sec" />
 <PerformanceCounters Objects="$(INSTANCE):SQL Statistics" Counters="SQL Compilations/sec" />
 <PerformanceCounters Objects="$(INSTANCE):SQL Statistics" Counters="SQL Re-Compilations/sec" />
 <PerformanceCounters Objects="$(INSTANCE):SQL Statistics" Counters="SQL Attention rate" />
 <PerformanceCounters Objects="$(INSTANCE):SQL Statistics" Counters="Auto-Param Attempts/sec" />
 <PerformanceCounters Objects="$(INSTANCE):SQL Statistics" Counters="Failed Auto-Params/sec" />
 <PerformanceCounters Objects="$(INSTANCE):Plan Cache" Counters="Cache Hit Ratio" Instances="_Total" />
 <PerformanceCounters Objects="$(INSTANCE):Plan Cache" Counters="Cache Hit Ratio" Instances="Object Plans" />
 <PerformanceCounters Objects="$(INSTANCE):Plan Cache" Counters="Cache Hit Ratio" Instances="SQL Plans" />
 <PerformanceCounters Objects="$(INSTANCE):Plan Cache" Counters="Cache Hit Ratio" Instances="Temporary Tables &amp; Table Variables" />
 <PerformanceCounters Objects="$(INSTANCE):Transactions" Counters="Free Space in tempdb (KB)"/>
 <PerformanceCounters Objects="$(INSTANCE):Workload Group Stats" Counters="Active requests" Instances="*"/>
 <PerformanceCounters Objects="$(INSTANCE):Workload Group Stats" Counters="Blocked tasks" Instances="*"/>
 <PerformanceCounters Objects="$(INSTANCE):Workload Group Stats" Counters="CPU usage %" Instances="*"/>
</ns:PerformanceCountersCollector>');

-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection item detils (syscollector_collection_items) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14707;
SET @collection_item_name = FORMATMESSAGE(@name_id);
SET @collection_item_old_name = 'Server Activity - Performance Counters';
SET @frequency = 60;  

IF ISNULL (@collection_item_name, '') = '' 
BEGIN
    SET @collection_item_name = @collection_item_old_name;
END;

SET @collection_item_id = NULL;
SELECT @collection_item_id = collection_item_id
FROM syscollector_collection_items_internal 
WHERE collection_set_id = @collection_set_id 
    AND (name = @collection_item_name OR name = @collection_item_old_name);

IF (@collection_item_id IS NOT NULL)
BEGIN
    RAISERROR ('Updating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_update_collection_item 
        @collection_item_id = @collection_item_id, 
        @new_name = @collection_item_name, 
        @frequency = @frequency, 
        @parameters = @parameters;
END
ELSE
BEGIN
    RAISERROR ('Creating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_create_collection_item
        @collection_set_id = @collection_set_id,
        @collector_type_uid = N'294605DD-21DE-40B2-B20F-F3E170EA1EC3',
        @name = @collection_item_name,
        @parameters = @parameters,
        @frequency = @frequency, 
        @collection_item_id = @collection_item_id output;
END;

-- for localization of collection item name
UPDATE syscollector_collection_items_internal
SET name_id = @name_id
WHERE collection_item_id = @collection_item_id;

-- Turn the is_system flag on so users can't change the definition of this collection set 
UPDATE syscollector_collection_sets
SET is_system = 1
WHERE collection_set_id = @collection_set_id
GO


------------------------------------------------
-- System collection set: Query Statistics
------------------------------------------------
DECLARE @collection_set_name NVARCHAR(128);
DECLARE @description NVARCHAR(4000);
DECLARE @collection_set_id int;
DECLARE @collection_set_uid uniqueidentifier;
DECLARE @collection_mode smallint;
DECLARE @schedule_name sysname;
DECLARE @days_until_expiration smallint;
DECLARE @name_id int;
DECLARE @description_id int;

-- The GUID below identifies this collection set and is used to locate the data collected by this collection set. 
-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection set detils (syscollector_collection_sets) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14709;
SET @description_id = 14708;
SET @collection_set_uid = N'2DC02BD6-E230-4C05-8516-4E8C0EF21F95';
SET @collection_mode = 0; -- Cached
SET @schedule_name = N'CollectorSchedule_Every_15min';
SET @days_until_expiration = 14;

SET @description = FORMATMESSAGE(@description_id);
SET @collection_set_name = ISNULL (FORMATMESSAGE(@name_id), 'Query Statistics');

IF EXISTS (SELECT * FROM dbo.syscollector_collection_sets_internal WHERE collection_set_uid = @collection_set_uid)
BEGIN
    RAISERROR ('Updating system Collection Set "%s"...', 0, 1, @collection_set_name) WITH NOWAIT;
    -- We are updating an existing collection set -- get its ID 
    SELECT @collection_set_id = collection_set_id
    FROM syscollector_collection_sets 
    WHERE collection_set_uid = @collection_set_uid;
    
    -- Temporarily clear the is_system flag so that we can modify the collection set definition
    UPDATE syscollector_collection_sets
    SET is_system = 0
    WHERE collection_set_id = @collection_set_id
    
    -- Don't override the current expiration period or schedule settings, since the user may have customized these
    EXEC dbo.sp_syscollector_update_collection_set
        @collection_set_id = @collection_set_id, 
        @new_name = @collection_set_name, 
        @schedule_name = @schedule_name, -- avoid mismatch in uid if schedule was deleted and recreated
        @collection_mode = @collection_mode, 
        @logging_level = 0,
        @description = @description;
END
ELSE
BEGIN    
    RAISERROR ('Creating system Collection Set "%s"...', 0, 1, @collection_set_name) WITH NOWAIT;
    EXEC dbo.sp_syscollector_create_collection_set
        @collection_set_uid = @collection_set_uid,
        @name = @collection_set_name, 
        @schedule_name = @schedule_name,
        @collection_mode = @collection_mode, 
        @days_until_expiration = @days_until_expiration, 
        @description = @description, 
        @logging_level = 0,
        @collection_set_id = @collection_set_id OUTPUT;
END

-- for localization of collection set name and description
UPDATE syscollector_collection_sets_internal
SET name_id = @name_id, description_id = @description_id
WHERE collection_set_uid = @collection_set_uid;

-- Add collection items
DECLARE @collection_item_name NVARCHAR(128);
DECLARE @collection_item_old_name NVARCHAR(128);
DECLARE @collection_item_id int;
DECLARE @frequency int;
DECLARE @parameters xml;

-- Item 1 - Query activity collection type
-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection item detils (syscollector_collection_items) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14710;
SET @collection_item_name = FORMATMESSAGE(@name_id);
SET @collection_item_old_name = 'Query Statistics - Query Activity';
SET @frequency = 10;  

IF ISNULL (@collection_item_name, '') = '' 
BEGIN
    SET @collection_item_name = @collection_item_old_name;
END;

SET @collection_item_id = NULL;
SELECT @collection_item_id = collection_item_id
FROM syscollector_collection_items_internal 
WHERE collection_set_id = @collection_set_id 
    AND (name = @collection_item_name OR name = @collection_item_old_name);

IF (@collection_item_id IS NOT NULL)
BEGIN
    RAISERROR ('Updating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_update_collection_item 
        @collection_item_id = @collection_item_id, 
        @new_name = @collection_item_name, 
        @frequency = @frequency, 
        @parameters = @parameters;
END
ELSE
BEGIN
    RAISERROR ('Creating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_create_collection_item
        @collection_set_id = @collection_set_id,
        @collector_type_uid = N'14AF3C12-38E6-4155-BD29-F33E7966BA23',
        @name = @collection_item_name,
        @parameters = @parameters,
        @frequency = @frequency, 
        @collection_item_id = @collection_item_id output;
END;

-- for localization of collection item name
UPDATE syscollector_collection_items_internal
SET name_id = @name_id
WHERE collection_item_id = @collection_item_id;

-- Turn the is_system flag on so users can't change the definition of this collection set 
UPDATE syscollector_collection_sets
SET is_system = 1
WHERE collection_set_id = @collection_set_id
GO

-- End of installation of out-of-the-box collector components
-- Restore agent xp settings to original state
DECLARE @advopt_old_value int
DECLARE @comp_old_value int
SELECT @advopt_old_value = advopt_old_value FROM #advopt_old_value
SELECT @comp_old_value = comp_old_value FROM #comp_old_value

EXECUTE #sp_restore_component_state 'Agent XPs', @advopt_old_value, @comp_old_value

DROP TABLE #advopt_old_value
DROP TABLE #comp_old_value
---------------------------------------------------------------
-- Data Collector: Security: Permissions
---------------------------------------------------------------

PRINT ''
PRINT 'Granting permissions to data collector roles...'

GRANT SELECT  ON [dbo].[syscollector_config_store]                                TO [dc_operator], [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_enable_collector]                        TO [dc_operator]
GRANT EXECUTE ON [dbo].[sp_syscollector_disable_collector]                        TO [dc_operator]
GRANT EXECUTE ON [dbo].[sp_syscollector_set_warehouse_instance_name]            TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_set_warehouse_database_name]            TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_set_cache_window]                        TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_set_cache_directory]                    TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_get_warehouse_connection_string]        TO [dc_proxy]
GRANT EXECUTE ON [dbo].[fn_syscollector_highest_incompatible_mdw_version]		TO [dc_admin], [dc_proxy]

GRANT SELECT  ON [dbo].[syscollector_collector_types]                            TO [dc_operator], [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_create_collector_type]                    TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_delete_collector_type]                    TO [dc_admin]

GRANT SELECT  ON [dbo].[syscollector_collection_sets]                            TO [dc_operator], [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_create_collection_set]                    TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_delete_collection_set]                    TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_update_collection_set]                    TO [dc_operator]
GRANT EXECUTE ON [dbo].[sp_syscollector_start_collection_set]                    TO [dc_operator]
GRANT EXECUTE ON [dbo].[sp_syscollector_stop_collection_set]                    TO [dc_operator]
GRANT EXECUTE ON [dbo].[sp_syscollector_upload_collection_set]                    TO [dc_operator]
GRANT EXECUTE ON [dbo].[sp_syscollector_run_collection_set]                        TO [dc_operator]

GRANT SELECT  ON [dbo].[syscollector_collection_items]                            TO [dc_operator], [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_create_collection_item]                    TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_delete_collection_item]                    TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_update_collection_item]                    TO [dc_operator]

GRANT SELECT  ON [dbo].[syscollector_execution_log]                                TO [dc_operator]
GRANT SELECT  ON [dbo].[syscollector_execution_log_full]                        TO [dc_operator]
GRANT SELECT  ON [dbo].[syscollector_execution_stats]                            TO [dc_operator]

GRANT EXECUTE ON [dbo].[fn_syscollector_find_collection_set_root]                TO [dc_operator]
GRANT SELECT  ON [dbo].[fn_syscollector_get_execution_log_tree]                    TO [dc_operator]
GRANT SELECT  ON [dbo].[fn_syscollector_get_execution_stats]                    TO [dc_operator]
GRANT SELECT  ON [dbo].[fn_syscollector_get_execution_details]                    TO [dc_operator]
GRANT EXECUTE ON [dbo].[sp_syscollector_delete_execution_log_tree]                TO [dc_operator]

GRANT EXECUTE ON [dbo].[sp_syscollector_event_oncollectionbegin]                TO [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_event_oncollectionend]                    TO [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_event_onpackagebegin]                    TO [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_event_onpackageend]                        TO [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_event_onpackageupdate]                    TO [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_event_onerror]                            TO [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_event_onstatsupdate]                    TO [dc_proxy]

GRANT EXECUTE ON [dbo].[sp_syscollector_create_tsql_query_collector]            TO [dc_operator], [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_get_tsql_query_collector_package_ids]   TO [dc_operator], [dc_proxy]

GRANT EXECUTE ON [dbo].[sp_verify_subsystems]                                   TO [dc_operator]

---------------------------------------------------------------
-- Relational storage for DMF objects
---------------------------------------------------------------

CREATE TABLE #objects_to_drop(
    type nvarchar(128),
    name sysname
)

-- List of shared registered server objects to be deleted from msdb. Note: order is important!
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_verify_shared_server_type]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_update_shared_registered_server]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_rename_shared_registered_server]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_update_shared_server_group]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_rename_shared_server_group]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_move_shared_registered_server]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_delete_shared_registered_server]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_move_shared_server_group]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_delete_shared_server_group]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_add_shared_registered_server]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_add_shared_server_group]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',              '[dbo].[sysmanagement_shared_registered_servers]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',              '[dbo].[sysmanagement_shared_server_groups]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',         '[dbo].[sysmanagement_delete_shared_server_group_trigger]')

-- List of policy management objects to be deleted from msdb. Note: order is important!
INSERT INTO #objects_to_drop VALUES ('TRIGGER',           '[dbo].[syspolicy_insert_target_set_level_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',           '[dbo].[syspolicy_update_target_set_level_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',           '[dbo].[syspolicy_insert_target_set_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',           '[dbo].[syspolicy_delete_target_set_trigger]' )
INSERT INTO #objects_to_drop VALUES ('FUNCTION',        '[dbo].[syspolicy_fn_filter_complete]' )
INSERT INTO #objects_to_drop VALUES ('FUNCTION',        '[dbo].[syspolicy_fn_eventing_filter]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_add_target_set_condition_reference]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_update_target_set_condition_reference]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',             '[dbo].[syspolicy_target_set_condition_references]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_add_target_set_level]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_update_target_set_level]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',             '[dbo].[syspolicy_target_set_levels]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_verify_object_set_identifiers]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_verify_object_set_references]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_add_target_set]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_delete_target_set]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_update_target_set]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_add_object_set]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_delete_object_set]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',             '[dbo].[syspolicy_target_sets]' )

INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_events_reader]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_dispatch_event]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_delete_policy_execution_history]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_log_policy_execution_detail]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_log_policy_execution_end]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_log_policy_execution_start]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',      		'[dbo].[syspolicy_policy_execution_history_details]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',      		'[dbo].[syspolicy_policy_execution_history]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',    		'[dbo].[syspolicy_update_system_health_state]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',      		'[dbo].[syspolicy_system_health_state]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_delete_policy_category_subscription]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_update_policy_category_subscription]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_add_policy_category_subscription]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',      		'[dbo].[syspolicy_policy_category_subscriptions]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_delete_policy]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_update_policy]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_rename_policy]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_verify_policy_identifiers]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_add_policy]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_rename_policy_category]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_update_policy_category]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_delete_policy_category]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_add_policy_category]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_verify_policy_category_identifiers]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',      		'[dbo].[syspolicy_policies]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_create_job]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_delete_job_delete_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_update_policy_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_insert_policy_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_update_job_update_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_insert_job_create_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_instead_delete_policy_trigger]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',      		'[dbo].[syspolicy_policy_categories]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_rename_condition]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_delete_condition]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_update_condition]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_verify_condition_identifiers]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_add_condition]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',      		'[dbo].[syspolicy_conditions]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',    		'[dbo].[syspolicy_insert_condition_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',    		'[dbo].[syspolicy_update_condition_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_for_update_condition_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_after_update_condition_trigger]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',     		'[dbo].[syspolicy_object_sets]' )
INSERT INTO #objects_to_drop VALUES ('FUNCTION',  		'[dbo].[syspolicy_fn_get_type_name]' )
INSERT INTO #objects_to_drop VALUES ('INLINE FUNCTION',  		'[dbo].[syspolicy_fn_get_bad_filters]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  		'[dbo].[sp_syspolicy_check_membership]' )
INSERT INTO #objects_to_drop VALUES ('FUNCTION',  		'[dbo].[fn_syspolicy_get_ps_command]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',            '[dbo].[syspolicy_configuration]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_configure]' )
INSERT INTO #objects_to_drop VALUES ('FUNCTION',  	    '[dbo].[fn_syspolicy_is_automation_enabled]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_set_config_enabled]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_repair_policy_automation]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_purge_history]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_set_config_history_retention]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',  	    '[dbo].[syspolicy_validate_events]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_create_purge_job]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_purge_health_state]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_set_log_on_success]' )

GO

DECLARE @object_name sysname
DECLARE @object_type nvarchar(128)

DECLARE object_to_drop_cursor CURSOR LOCAL FOR SELECT type, name FROM #objects_to_drop

OPEN object_to_drop_cursor
FETCH object_to_drop_cursor INTO @object_type, @object_name

WHILE @@FETCH_STATUS = 0 
BEGIN
    DECLARE @stmt nvarchar(128)
    DECLARE @object_kind nvarchar(2)

    SET @object_kind = 
        CASE @object_type
            WHEN 'VIEW'       THEN 'V'
            WHEN 'PROCEDURE'  THEN 'P'
            WHEN 'TRIGGER'    THEN 'TR'
            WHEN 'FUNCTION'    THEN 'FN'
            WHEN 'INLINE FUNCTION'    THEN 'IF'
            WHEN 'TABLE TYPE'    THEN 'TT'
            ELSE NULL
        END

    IF @object_kind IS NULL
    BEGIN
        DECLARE @errtxt nvarchar(max)
        SET @errtxt = 'Incorrect object type "' + @object_type + '" for object "' + @object_name + '", check "INSERT INTO #objects_to_drop..." statement'
        RAISERROR(@errtxt, 20, 127) WITH LOG
    END

    IF (OBJECT_ID(@object_name, @object_kind) IS NULL)
        PRINT 'Object "' + @object_name + N'" does not exist, will not drop'
    ELSE
    BEGIN
        IF( @object_type = 'INLINE FUNCTION')
            SET @object_type = 'FUNCTION'
        SET @stmt = N'drop ' + @object_type + N' ' + @object_name
        PRINT 'Executing "' + @stmt + N'"'
        EXECUTE(@stmt)
    END
    FETCH object_to_drop_cursor INTO @object_type, @object_name
END

CLOSE object_to_drop_cursor
DEALLOCATE object_to_drop_cursor
DROP TABLE #objects_to_drop

PRINT 'Done dropping all DMF and Shared Registered Server procedures'
GO


--------------------------------------------------------------
-- This section contains shared registered servers information
--------------------------------------------------------------

IF NOT EXISTS (SELECT * FROM sys.tables where name ='sysmanagement_shared_server_groups_internal')
BEGIN
	PRINT 'Creating table [msdb].[dbo].[sysmanagement_shared_server_groups_internal]...'
	CREATE TABLE [msdb].[dbo].[sysmanagement_shared_server_groups_internal]
	(
		server_group_id INT NOT NULL IDENTITY PRIMARY KEY NONCLUSTERED,
		name sysname NOT NULL,
		description NVARCHAR (2048) NOT NULL,
		-- You can only have a registered server of the same type within a group of the same type
		-- So the group needs to have knowledge of its type
		server_type INT NOT NULL,
		-- Explicitly allow NULLs for this column, so we are independent of server configuration
		parent_id INT NULL,
		-- this flag indicates whether the group is a system builtin group
		is_system_object BIT DEFAULT 0,
		-- Make sure each name is unique per parent
		CONSTRAINT [UQ_sysmanagement_unique_group_name_per_parent] UNIQUE(parent_id, name)
	);

    CREATE CLUSTERED    INDEX [IX_sysmanagement_shared_server_groups_clustParentGroupID] ON
                [dbo].[sysmanagement_shared_server_groups_internal] (parent_id);
    CREATE NONCLUSTERED INDEX [IX_sysmanagement_shared_server_groups_name] ON
                [dbo].[sysmanagement_shared_server_groups_internal] (name)

    -- populate with the builtin server groups
    -- Note: server_type_id values must match the ServerType enumeration
    INSERT INTO [msdb].[dbo].[sysmanagement_shared_server_groups_internal] (name, description, server_type, parent_id, is_system_object)
    VALUES (N'DatabaseEngineServerGroup', N'Builtin group that contains the DatabaseEngine servers', 0, null, 1);

    INSERT INTO [msdb].[dbo].[sysmanagement_shared_server_groups_internal] (name, description, server_type, parent_id, is_system_object)
    VALUES (N'AnalysisServicesServerGroup', N'Builtin group that contains the AnalysisServices servers', 1, null, 1);

    INSERT INTO [msdb].[dbo].[sysmanagement_shared_server_groups_internal] (name, description, server_type, parent_id, is_system_object)
    VALUES (N'ReportingServicesServerGroup', N'Builtin group that contains the ReportingServices servers', 2, null, 1);

    INSERT INTO [msdb].[dbo].[sysmanagement_shared_server_groups_internal] (name, description, server_type, parent_id, is_system_object)
    VALUES (N'IntegrationServicesServerGroup', N'Builtin group that contains the IntegrationServices servers', 3, null, 1);

    INSERT INTO [msdb].[dbo].[sysmanagement_shared_server_groups_internal] (name, description, server_type, parent_id, is_system_object)
    VALUES (N'SqlServerCompactEditionServerGroup', N'Builtin group that contains the SqlServerCompactEdition servers', 4, null, 1);

END
ELSE
BEGIN
	UPDATE 
		[msdb].[dbo].[sysmanagement_shared_server_groups_internal]
	SET 
		name = N'SqlServerCompactEditionServerGroup',
		description = N'Builtin group that contains the SqlServerCompactEdition servers'
	WHERE
		name = N'SqlServerEverywhereServerGroup' and
		server_type = 4 and
		is_system_object = 1;
END

GO
PRINT 'Creating trigger [sysmanagement_delete_shared_server_group_trigger]...'
GO
CREATE TRIGGER [sysmanagement_delete_shared_server_group_trigger] on [msdb].[dbo].[sysmanagement_shared_server_groups_internal] 
FOR DELETE
AS
BEGIN
    -- system server groups should not be deleted
    IF EXISTS (SELECT * FROM deleted where is_system_object = 1)
    BEGIN
        RAISERROR (35008, 1, 1)
        ROLLBACK TRANSACTION
    END
END
GO

IF NOT EXISTS ( SELECT * FROM sys.tables WHERE name = 'sysmanagement_shared_registered_servers_internal')
BEGIN
    PRINT 'Creating table [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]...'
    CREATE TABLE [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]
    (
        server_id INT NOT NULL IDENTITY PRIMARY KEY NONCLUSTERED,
        server_group_id INT FOREIGN KEY REFERENCES [msdb].[dbo].[sysmanagement_shared_server_groups_internal] (server_group_id) ON DELETE CASCADE,
        name sysname NOT NULL,
        server_name sysname NOT NULL,
        description NVARCHAR(2048) NOT NULL,
        -- While the server group has the knowledge of the server type,
        -- you also need the Server Type here, because you can have a root registered server with no parent group
        server_type INT NOT NULL,
        -- Make sure each registered name is unique in each group
        CONSTRAINT [UQ_sysmanagement_unique_server_name_per_group] UNIQUE(server_group_id, name)
    )

    CREATE CLUSTERED    INDEX [IX_sysmanagement_shared_registered_servers_clustGroupID] ON
                [dbo].[sysmanagement_shared_registered_servers_internal] (server_group_id);
    CREATE NONCLUSTERED INDEX [IX_sysmanagement_shared_registered_servers_name] ON
                [dbo].[sysmanagement_shared_registered_servers_internal] (name)
END
GO

PRINT 'Creating view [dbo].[sysmanagement_shared_server_groups]...'
GO
CREATE VIEW [dbo].[sysmanagement_shared_server_groups]
AS
(
    SELECT server_group_id, name, description, server_type, parent_id, is_system_object,
    (select COUNT(*) from [msdb].[dbo].[sysmanagement_shared_server_groups_internal] sgChild where sgChild.parent_id = sg.server_group_id) as num_server_group_children,
    (select COUNT(*) from [msdb].[dbo].[sysmanagement_shared_registered_servers_internal] rsChild where rsChild.server_group_id = sg.server_group_id) as num_registered_server_children
    FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] sg
)
GO

PRINT 'Creating view [dbo].[sysmanagement_shared_registered_servers]...'
GO
CREATE VIEW [dbo].[sysmanagement_shared_registered_servers]
AS
(
    SELECT server_id, server_group_id, name, server_name, description, server_type
    FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]
)
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_verify_shared_server_type]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_verify_shared_server_type]
    @server_type INT
AS
BEGIN
    IF (@server_type IS NULL)
    BEGIN
        RAISERROR (35009, -1, -1)
        RETURN(1)
    END
    
    -- 0 --> DatabaseEngineServerGroup, 1 --> AnalysisServicesServerGroup, 2 --> ReportingServicesServerGroup, 3 --> IntegrationServicesServerGroup, 4 --> SqlServerCompactEditionServerGroup
    IF (@server_type < 0 OR @server_type > 4)
    BEGIN
        RAISERROR (35010, -1, -1, @server_type)
        RETURN (1)
    END
    
    RETURN (0)
END
GO


PRINT 'Creating procedure [dbo].[sp_sysmanagement_add_shared_server_group]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_add_shared_server_group]
    @name sysname,
    @description NVARCHAR (2048) = N'',
    @parent_id INT,
    @server_type INT,
    @server_group_id INT OUTPUT
AS
BEGIN
    DECLARE @retval INT

    EXECUTE @retval = sp_sysmanagement_verify_shared_server_type @server_type

    IF (@retval <> 0)
        RETURN(1) -- Failure
    
    -- user created server groups should have a valid parent 
    IF( (@parent_id IS NULL) OR 
        (@parent_id NOT IN (SELECT sg.server_group_id FROM msdb.dbo.sysmanagement_shared_server_groups_internal sg)))
    BEGIN
        RAISERROR (35001, -1, -1)
        RETURN (1)
    END

    IF EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal]  sg 
                WHERE @parent_id = sg.server_group_id AND @server_type <> sg.server_type)
    BEGIN
        RAISERROR (35002, -1, -1)
        RETURN (1)
    END    
    
    INSERT INTO [msdb].[dbo].[sysmanagement_shared_server_groups_internal]
        (name, description, parent_id, server_type)
    VALUES
        (
        @name, 
        @description, 
        @parent_id, 
        @server_type
        )

    SELECT @server_group_id = SCOPE_IDENTITY()
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_add_shared_registered_server]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_add_shared_registered_server]
    @name sysname,
    @server_group_id INT,
    @server_name sysname,
    @description NVARCHAR(2048) = N'',
    @server_type INT,
    @server_id INT OUTPUT
AS
BEGIN
    DECLARE @retval INT

    EXECUTE @retval = sp_sysmanagement_verify_shared_server_type @server_type

    IF (@retval <> 0)
        RETURN(1) -- Failure
    
    IF( (@server_group_id IS NULL) OR 
        (@server_group_id NOT IN (SELECT sg.server_group_id FROM msdb.dbo.sysmanagement_shared_server_groups_internal sg)))
    BEGIN
        RAISERROR (35001, -1, -1)
        RETURN (1)
    END

    IF EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal]  sg 
                WHERE @server_group_id = sg.server_group_id AND @server_type <> sg.server_type)
    BEGIN
        RAISERROR (35002, -1, -1)
        RETURN (1)
    END    

    IF (@server_name IS NULL)
    BEGIN
        RAISERROR(14618, -1, 1, '@server_name')
        RETURN(1)   
    END

    set @server_name = LTRIM(@server_name)
    set @server_name = RTRIM(@server_name)

    -- Disallow relative names
    IF ('.' = @server_name) OR
        (1 = CHARINDEX(N'.\', @server_name)) OR
        (1 = CHARINDEX(N'LOCALHOST\', UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS))) OR
        (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = 'LOCALHOST') OR
        (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = '(LOCAL)')
    BEGIN
        RAISERROR (35011, -1, -1)
        RETURN (1)
    END

    IF (UPPER(@@SERVERNAME collate SQL_Latin1_General_CP1_CS_AS) = UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS))
    BEGIN
        RAISERROR (35012, -1, -1)
        RETURN (1)
    END

    INSERT INTO [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]
        (server_group_id, name, server_name, description, server_type)
    VALUES
        (@server_group_id, @name, @server_name, @description, @server_type)
        
    SELECT @server_id = SCOPE_IDENTITY()
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_delete_shared_server_group]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_delete_shared_server_group]
    @server_group_id INT
AS
BEGIN
    IF NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @server_group_id)
    BEGIN
        RAISERROR (35004, -1, -1)
        RETURN(1)
    END;

    WITH ChildGroups (parent_id, server_group_id, name, server_type, server_level)
    AS
    (
        -- Anchor
        SELECT g.parent_id, g.server_group_id, g.name, g.server_type, 0 AS server_level
        FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] AS g
        WHERE g.server_group_id = @server_group_id
        UNION ALL
        -- Recursive definition
        SELECT r.parent_id, r.server_group_id, r.name, r.server_type, server_level + 1
        FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] AS r
        INNER JOIN ChildGroups AS children ON r.parent_id = children.server_group_id
    )
    -- Execute CTE to delete the hierarchy of server groups
    DELETE FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal]
        FROM ChildGroups children
        JOIN [msdb].[dbo].[sysmanagement_shared_server_groups_internal] ServerGroups
            ON children.server_group_id = ServerGroups.server_group_id
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_delete_shared_registered_server]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_delete_shared_registered_server]
    @server_id INT
AS
BEGIN
    IF NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal] WHERE server_id = @server_id)
    BEGIN
        RAISERROR (35007, -1, -1)
        RETURN(1)
    END
    
    DELETE FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]
    WHERE
            server_id = @server_id
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_move_shared_server_group]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_move_shared_server_group]
    @server_group_id INT,
    @new_parent_id INT
AS
BEGIN
    IF (@new_parent_id IS NULL) OR 
        NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @new_parent_id)
    BEGIN
        RAISERROR (35001, -1, -1)
        RETURN(1)
    END

    IF (@new_parent_id IS NOT NULL)
        AND ((SELECT server_type FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @new_parent_id)
                <>
             (SELECT server_type FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @server_group_id))
    BEGIN
        RAISERROR (35002, -1, -1)
        RETURN(1)
    END

    DECLARE @DeletedGroups TABLE
    (
        server_group_id int
    );

    -- Check if the destination group you're moving to isn't already a descendant of the current group
    WITH ChildGroups (parent_id, server_group_id, server_level)
    AS
    (
        -- Anchor
        SELECT g.parent_id, g.server_group_id, 0 AS server_level
        FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] AS g
        WHERE g.server_group_id = @server_group_id
        UNION ALL
        -- Recursive definition
        SELECT r.parent_id, r.server_group_id, server_level + 1
        FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] AS r
        INNER JOIN ChildGroups AS children ON r.parent_id = children.server_group_id
    )
    -- Execute CTE
    INSERT INTO @DeletedGroups
    SELECT server_group_id FROM ChildGroups

    IF (SELECT COUNT(*) FROM @DeletedGroups WHERE server_group_id = @new_parent_id) > 0
    BEGIN
        RAISERROR (35003, -1, -1)
        RETURN(1)
    END
    
    UPDATE [msdb].[dbo].[sysmanagement_shared_server_groups_internal]
        SET parent_id = @new_parent_id
    WHERE
        server_group_id = @server_group_id
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_move_shared_registered_server]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_move_shared_registered_server]
    @server_id INT,
    @new_parent_id INT
AS
BEGIN
    IF (@server_id IS NULL)
    BEGIN
        RAISERROR (35006, -1, -1)
        RETURN(1)
    END
    
    IF NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal] WHERE server_id = @server_id)
    BEGIN
        RAISERROR (35007, -1, -1)
        RETURN(1)
    END
    
    IF (@new_parent_id IS NULL) OR 
        NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @new_parent_id)
    BEGIN
        RAISERROR (35001, -1, -1)
        RETURN(1)
    END

    IF (@new_parent_id IS NOT NULL)
        AND ((SELECT server_type FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @new_parent_id)
                <>
             (SELECT server_type FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal] WHERE server_id = @server_id))
    BEGIN
        RAISERROR (35002, -1, -1)
        RETURN(1)
    END
    
    UPDATE [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]
        SET server_group_id = @new_parent_id
    WHERE
        server_id = @server_id
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_update_shared_server_group]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_update_shared_server_group]
    @server_group_id INT,
    @description NVARCHAR (2048) = NULL
AS
BEGIN
    IF (@server_group_id IS NULL)
    BEGIN
        RAISERROR (35005, -1, -1)
        RETURN(1)
    END

    IF NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @server_group_id)
    BEGIN
        RAISERROR (35004, -1, -1)
        RETURN(1)
    END

    UPDATE [msdb].[dbo].[sysmanagement_shared_server_groups_internal]
        SET description = ISNULL(@description, description)
    WHERE
        server_group_id = @server_group_id
    
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_update_shared_registered_server]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_update_shared_registered_server]
    @server_id INT,
    @server_name sysname = NULL,
    @description NVARCHAR(2048) = NULL
AS
BEGIN
    IF (@server_id IS NULL)
    BEGIN
        RAISERROR (35006, -1, -1)
        RETURN(1)
    END
    
    IF NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal] WHERE server_id = @server_id)
    BEGIN
        RAISERROR (35007, -1, -1)
        RETURN(1)
    END

    IF (@server_name IS NULL)
    BEGIN
        SET @server_name = (select server_name FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal] WHERE server_id = @server_id)
    END

    set @server_name = LTRIM(@server_name)
    set @server_name = RTRIM(@server_name)

    -- Disallow relative names
    IF ('.' = @server_name) OR
        (1 = CHARINDEX(N'.\', @server_name)) OR
        (1 = CHARINDEX(N'LOCALHOST\', UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS))) OR
        (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = 'LOCALHOST') OR
        (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = '(LOCAL)')
    BEGIN
        RAISERROR (35011, -1, -1)
        RETURN (1)
    END

    IF (UPPER(@@SERVERNAME collate SQL_Latin1_General_CP1_CS_AS) = UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS))
    BEGIN
        RAISERROR (35012, -1, -1)
        RETURN (1)
    END

    UPDATE [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]
        SET server_name = ISNULL(@server_name, server_name),
            description = ISNULL(@description, description)
    WHERE
        server_id = @server_id
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_rename_shared_server_group]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_rename_shared_server_group]
    @server_group_id INT,
    @new_name sysname
AS
BEGIN
    IF (@server_group_id IS NULL)
    BEGIN
        RAISERROR (35006, -1, -1)
        RETURN(1)
    END
    
    IF NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @server_group_id)
    BEGIN
        RAISERROR (35007, -1, -1)
        RETURN(1)
    END
    
    IF (@new_name IS NULL or LEN(@new_name) = 0)
    BEGIN
      RAISERROR(21263, -1, -1, '@new_name')
      RETURN(1) -- Failure
    END

    UPDATE [msdb].[dbo].[sysmanagement_shared_server_groups_internal]
        SET name = @new_name
    WHERE
        server_group_id = @server_group_id

    RETURN (0)
END
GO


PRINT 'Creating procedure [dbo].[sp_sysmanagement_rename_shared_registered_server]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_rename_shared_registered_server]
    @server_id INT,
    @new_name sysname
AS
BEGIN
    IF (@server_id IS NULL)
    BEGIN
        RAISERROR (35006, -1, -1)
        RETURN(1)
    END
    
    IF NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal] WHERE server_id = @server_id)
    BEGIN
        RAISERROR (35007, -1, -1)
        RETURN(1)
    END
    
    IF (@new_name IS NULL or LEN(@new_name) = 0)
    BEGIN
      RAISERROR(21263, -1, -1, '@new_name')
      RETURN(1) -- Failure
    END

    UPDATE [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]
        SET name = @new_name
    WHERE
        server_id = @server_id

    RETURN (0)
END
GO


-----------------------------------------------------------
-- This section contains facet information
-----------------------------------------------------------
IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_management_facets')
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_management_facets]...';
    CREATE TABLE [dbo].[syspolicy_management_facets] (
        management_facet_id int NOT NULL IDENTITY PRIMARY KEY,
        name nvarchar(MAX) NOT NULL,    -- this is the name of the management facet
        execution_mode int NOT NULL
    );

END

GO

-- Create a temp table for the facets that are supposed to ship out of the box
-- Later the script will use the temp table to add any new facets to the syspolicy_management_facets table
declare @temp_syspolicy_management_facets TABLE(
    name nvarchar(MAX) NOT NULL,    -- this is the name of the management facet
    execution_mode int NOT NULL);
                
-- populate the temp table with facets shipping out of the box
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ApplicationRole', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('AsymmetricKey', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Audit', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('BackupDevice', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('BrokerPriority', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('BrokerService', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Certificate', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Credential', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('CryptographicProvider', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Database', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('DatabaseAuditSpecification', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('DatabaseDdlTrigger', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('DatabaseRole', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('DataFile', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Default', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Endpoint', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('FileGroup', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('FullTextCatalog', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('FullTextIndex', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('FullTextStopList', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IDatabaseMaintenanceFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IDatabaseOptions', 6);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IDatabasePerformanceFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IDatabaseSecurityFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ILoginOptions', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IMultipartNameFacet', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('INameFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ITableOptions', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IUserOptions', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IViewOptions', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Index', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerAuditFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerConfigurationFacet', 6);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerInformation', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerPerformanceFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerSecurityFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerSetupFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerSettings', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ISurfaceAreaConfigurationForAnalysisServer', 0);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ISurfaceAreaConfigurationForReportingServices', 0);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ISurfaceAreaFacet', 6);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('LinkedServer', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('LogFile', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Login', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('MessageType', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('PartitionFunction', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('PartitionScheme', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('PlanGuide', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('RemoteServiceBinding', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ResourceGovernor', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ResourcePool', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Rule', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Schema', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Server', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ServerAuditSpecification', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ServerDdlTrigger', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ServiceContract', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ServiceQueue', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ServiceRoute', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Statistic', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('StoredProcedure', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('SymmetricKey', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Synonym', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Table', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Trigger', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('User', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('UserDefinedAggregate', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('UserDefinedDataType', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('UserDefinedFunction', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('UserDefinedTableType', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('UserDefinedType', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('View', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('WorkloadGroup', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('XmlSchemaCollection', 4);

-- Facets can be updated and inserted, however deleting a facet is dangerous because of references from conditions

-- Update the modes on the facets
UPDATE facets
SET facets.execution_mode = tempFacets.execution_mode 
FROM @temp_syspolicy_management_facets tempFacets, [msdb].[dbo].[syspolicy_management_facets] facets
WHERE tempFacets.name = facets.name AND tempFacets.execution_mode <> facets.execution_mode;

-- Now populate the syspolicy_management_facets table with the facets that are missing in the table
INSERT [msdb].[dbo].[syspolicy_management_facets] (name, execution_mode)
    SELECT tempFacets.name, tempFacets.execution_mode
    FROM @temp_syspolicy_management_facets tempFacets
    WHERE tempFacets.name NOT IN (SELECT distinct facets.name FROM [msdb].[dbo].[syspolicy_management_facets] facets)
    


GO

---------------------------------------------------------------
-- Relational storage for policy objects
---------------------------------------------------------------
IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_facet_events')
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_facet_events]...';
    CREATE TABLE [dbo].[syspolicy_facet_events] (
        management_facet_id int NOT NULL REFERENCES [dbo].[syspolicy_management_facets],
        event_name sysname NOT NULL,                -- this is the name of the event
        target_type sysname NOT NULL,                -- type of the target (TABLE, PROCEDURE etc.)
        target_type_alias sysname NOT NULL);        -- this is an alias for the type of the target
                                                    -- it can happen that the same target type 
                                                    -- shows up as different strings in the event

    CREATE CLUSTERED INDEX [IX_facet_events_target_type_alias] ON 
        [dbo].[syspolicy_facet_events] (target_type_alias);
    CREATE UNIQUE INDEX [UX_facet_events] ON 
        [dbo].[syspolicy_facet_events] (management_facet_id, event_name, target_type, target_type_alias);
END
GO
-- The facet events are unique to an installation, thus delete what is there and then insert the new events
-- This technique will work for both a new install and an upgrade.
DELETE FROM [msdb].[dbo].[syspolicy_facet_events]

-- this is a pseudo event, so we're inserting it before the
-- consistency check is in place
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'SAC_ENDPOINT_CHANGE',N'SERVER',N'SERVER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ISurfaceAreaFacet'
 

GO
CREATE TRIGGER [syspolicy_validate_events] on [dbo].[syspolicy_facet_events]
AFTER INSERT, UPDATE
AS
BEGIN
    -- make sure that caller is dbo and all events inserted are real events.
    IF  (USER_ID() != 1) OR
        EXISTS (SELECT event_name FROM inserted 
                    WHERE event_name NOT IN(SELECT type_name from sys.event_notification_event_types))
    BEGIN
        RAISERROR(N'Unknown event name inserted into [dbo].[syspolicy_facet_events]', 1,1)
        ROLLBACK TRANSACTION
    END
END
GO

-- Insert the facet events               
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_APPLICATION_ROLE',N'APPLICATION ROLE',N'APPLICATION ROLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ApplicationRole'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_APPLICATION_ROLE',N'APPLICATION ROLE',N'APPLICATION ROLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ApplicationRole'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_ASYMMETRIC_KEY',N'ASYMMETRICKEY',N'ASYMMETRIC KEY'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'AsymmetricKey'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_ASYMMETRIC_KEY',N'ASYMMETRICKEY',N'ASYMMETRIC KEY'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'AsymmetricKey'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'ASYMMETRICKEY',N'ASYMMETRIC KEY'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'AsymmetricKey'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_ROLE',N'ROLE',N'ROLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'DatabaseRole'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_ROLE',N'ROLE',N'ROLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'DatabaseRole'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'ROLE',N'ROLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'DatabaseRole'

INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_ENDPOINT',N'ENDPOINT',N'ENDPOINT'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Endpoint'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_ENDPOINT',N'ENDPOINT',N'ENDPOINT'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Endpoint'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_SERVER',N'ENDPOINT',N'ENDPOINT'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Endpoint'
   
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_DATABASE',N'DATABASE',N'DATABASE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IDatabaseOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_DATABASE',N'DATABASE',N'DATABASE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IDatabaseOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'DATABASE',N'DATABASE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IDatabaseOptions'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_FUNCTION',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_FUNCTION',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_PROCEDURE',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_PROCEDURE',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_SYNONYM',N'SYNONYM',N'SYNONYM'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_TABLE',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_TABLE',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_TYPE',N'TYPE',N'TYPE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'TYPE',N'TYPE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_VIEW',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_VIEW',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_XML_SCHEMA_COLLECTION',N'XMLSCHEMACOLLECTION',N'XMLSCHEMACOLLECTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_XML_SCHEMA_COLLECTION',N'XMLSCHEMACOLLECTION',N'XMLSCHEMACOLLECTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'XMLSCHEMACOLLECTION',N'XMLSCHEMACOLLECTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'SYNONYM',N'SYNONYM'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'TYPE',N'TYPE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'XMLSCHEMACOLLECTION',N'XMLSCHEMACOLLECTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'AUDIT_SERVER_OPERATION_EVENT',N'SERVER',N'SERVER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IServerConfigurationFacet'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'AUDIT_SERVER_OPERATION_EVENT',N'SERVER',N'SERVER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ISurfaceAreaFacet'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_ENDPOINT',N'SERVER',N'SERVER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ISurfaceAreaFacet'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_ENDPOINT',N'SERVER',N'SERVER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ISurfaceAreaFacet'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'DROP_ENDPOINT',N'SERVER',N'SERVER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ISurfaceAreaFacet'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_LOGIN',N'LOGIN',N'LOGIN'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ILoginOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_LOGIN',N'LOGIN',N'LOGIN'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ILoginOptions'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_RESOURCE_POOL',N'RESOURCEPOOL',N'RESOURCE POOL'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ResourcePool'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_RESOURCE_POOL',N'RESOURCEPOOL',N'RESOURCE POOL'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ResourcePool'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'SCHEMA',N'SCHEMA'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Schema'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_SCHEMA',N'SCHEMA',N'SCHEMA'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Schema'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'SCHEMA',N'SCHEMA'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Schema'
  
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_PROCEDURE',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'StoredProcedure'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_PROCEDURE',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'StoredProcedure'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'StoredProcedure'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'StoredProcedure'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'StoredProcedure'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_TABLE',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ITableOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_TABLE',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ITableOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ITableOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ITableOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ITableOptions'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_USER',N'USER',N'ASYMMETRIC KEY USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_USER',N'USER',N'CERTIFICATE USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_USER',N'USER',N'GROUP USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_USER',N'USER',N'SQL USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_USER',N'USER',N'WINDOWS USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_USER',N'USER',N'ASYMMETRIC KEY USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_USER',N'USER',N'CERTIFICATE USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_USER',N'USER',N'GROUP USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_USER',N'USER',N'SQL USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_USER',N'USER',N'WINDOWS USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_FUNCTION',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'UserDefinedFunction'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_FUNCTION',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'UserDefinedFunction'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'UserDefinedFunction'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'UserDefinedFunction'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'UserDefinedFunction'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_VIEW',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IViewOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_VIEW',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IViewOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IViewOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IViewOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IViewOptions'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_WORKLOAD_GROUP',N'WORKLOADGROUP',N'WORKLOAD GROUP'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'WorkloadGroup'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_WORKLOAD_GROUP',N'WORKLOADGROUP',N'WORKLOAD GROUP'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'WorkloadGroup'

        
GO
---------------------------------------------------------------
-- Condition object
---------------------------------------------------------------

IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_conditions_internal')
BEGIN
PRINT 'Creating table [dbo].[syspolicy_conditions_internal]...'
CREATE TABLE [dbo].[syspolicy_conditions_internal] (
    condition_id int IDENTITY(1,1),
    name sysname NOT NULL,
    date_created datetime default GETDATE(),
    description nvarchar(max) NOT NULL default (''),
    created_by sysname NOT NULL default SUSER_SNAME(),
    modified_by sysname NULL,
    date_modified datetime NULL,
    facet_id int,
    expression nvarchar(max),
    is_name_condition smallint default (0),
    obj_name sysname NULL,
    CONSTRAINT [PK_syspolicy_conditions] PRIMARY KEY CLUSTERED (condition_id ASC),
    CONSTRAINT [UQ_syspolicy_conditions_name] UNIQUE(name)
    );
ALTER TABLE [dbo].[syspolicy_conditions_internal] 
    ADD CONSTRAINT [FK_syspolicy_conditions_internal_facet] FOREIGN KEY(facet_id)
    REFERENCES [dbo].[syspolicy_management_facets] (management_facet_id);
END
GO

PRINT 'Creating view [dbo].[syspolicy_conditions]...'
GO
CREATE VIEW [dbo].[syspolicy_conditions]
AS
    SELECT
        c.condition_id, c.name, c.date_created, c.description, c.created_by, 
        c.modified_by, c.date_modified, c.is_name_condition, mf.name AS facet, c.expression, c.obj_name 
    FROM [dbo].[syspolicy_conditions_internal] c 
    LEFT OUTER JOIN [dbo].[syspolicy_management_facets] mf ON c.facet_id = mf.management_facet_id
GO

CREATE PROCEDURE [dbo].[sp_syspolicy_check_membership]
@role sysname,
@raiserror bit = 1
AS
BEGIN
	-- make sure that the caller is dbo or @role
	IF ( IS_MEMBER(@role) != 1 AND USER_ID() != 1)
	BEGIN
		IF (@raiserror = 1)
		BEGIN
			RAISERROR(15003, -1, -1, @role);
		END
		RETURN 15003;
	END
	
	RETURN 0;
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_add_condition]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_add_condition] 
@name sysname,
@description nvarchar(max) = N'',
@facet nvarchar(max),
@expression nvarchar(max),
@is_name_condition smallint = 0,
@obj_name sysname = NULL,
@condition_id int = NULL OUTPUT 
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT
	DECLARE @facet_id            INT
	DECLARE @null_column	sysname
	
	SET @null_column = NULL

    IF (@name IS NULL OR @name = N'')
        SET @null_column = '@name'
    ELSE IF( @description IS NULL)
        SET @null_column = '@description'
    ELSE IF( @facet IS NULL)
        SET @null_column = '@facet'
    ELSE IF( @expression IS NULL)
        SET @null_column = '@expression'

    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_syspolicy_add_condition')
        RETURN(1)
    END

    IF EXISTS (SELECT * FROM msdb.dbo.syspolicy_conditions WHERE name = @name)
    BEGIN
        RAISERROR(34010, -1, -1, 'Condition', @name)
        RETURN(1)
    END

    SET @facet_id = (SELECT management_facet_id FROM [dbo].[syspolicy_management_facets] WHERE name = @facet)
    IF (@facet_id IS NULL)
    BEGIN
        RAISERROR (34014, -1, -1)
        RETURN(1)
    END

    INSERT INTO msdb.dbo.syspolicy_conditions_internal(name, description,facet_id,expression,is_name_condition,obj_name) 
        VALUES(@name, @description,@facet_id,@expression,@is_name_condition,@obj_name)
    SELECT @retval = @@error
    SET @condition_id = SCOPE_IDENTITY()
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_verify_condition_identifiers]...'
GO
-----------------------------------------------------------
-- This procedure verifies if a condition exists
-- The caller can pass either the condition name or the id
-----------------------------------------------------------
CREATE PROCEDURE [dbo].[sp_syspolicy_verify_condition_identifiers]
@condition_name sysname = NULL OUTPUT, 
@condition_id int = NULL OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

  IF ((@condition_name IS NULL)     AND (@condition_id IS NULL)) OR
     ((@condition_name IS NOT NULL) AND (@condition_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, '@condition_name', '@condition_id')
    RETURN(1) -- Failure
  END

  -- Check id
  IF (@condition_id IS NOT NULL)
  BEGIN
    SELECT @condition_name = name
    FROM msdb.dbo.syspolicy_conditions
    WHERE (condition_id = @condition_id)
    
    -- the view would take care of all the permissions issues.
    IF (@condition_name IS NULL) 
    BEGIN
      DECLARE @condition_id_as_char VARCHAR(36)
      SELECT @condition_id_as_char = CONVERT(VARCHAR(36), @condition_id)
      RAISERROR(14262, -1, -1, '@condition_id', @condition_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check name
  IF (@condition_name IS NOT NULL)
  BEGIN
    -- get the corresponding condition_id (if the condition exists)
    SELECT @condition_id = condition_id
    FROM msdb.dbo.syspolicy_conditions
    WHERE (name = @condition_name)
    
    -- the view would take care of all the permissions issues.
    IF (@condition_id IS NULL) 
    BEGIN
      RAISERROR(14262, -1, -1, '@condition_name', @condition_name)
      RETURN(1) -- Failure
    END
  END

  RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_update_condition]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_update_condition] 
@name sysname = NULL,
@condition_id int = NULL,
@facet nvarchar(max) = NULL,
@expression nvarchar(max) = NULL,
@description nvarchar(max) = NULL,
@is_name_condition smallint = NULL,
@obj_name sysname = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT
	DECLARE @facet_id            INT

    EXEC @retval = msdb.dbo.sp_syspolicy_verify_condition_identifiers @name, @condition_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    IF (@facet IS NOT NULL)
    BEGIN
        SET @facet_id = (SELECT management_facet_id FROM [dbo].[syspolicy_management_facets] WHERE name = @facet)
        IF (@facet_id IS NULL)
        BEGIN
            RAISERROR (34014, -1, -1)
            RETURN(1)
        END
    END

    UPDATE msdb.[dbo].[syspolicy_conditions_internal] 
    SET
        description = ISNULL(@description, description),
        facet_id = ISNULL(@facet_id, facet_id),
        expression = ISNULL(@expression, expression),
        is_name_condition = ISNULL(@is_name_condition, is_name_condition),
        obj_name = ISNULL(@obj_name, obj_name)
    WHERE condition_id = @condition_id
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_delete_condition]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_delete_condition] 
@name sysname = NULL,
@condition_id int = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_condition_identifiers @name, @condition_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    IF EXISTS (SELECT * FROM msdb.dbo.syspolicy_policies WHERE condition_id = @condition_id)
    BEGIN
        RAISERROR(34012,-1,-1,'Condition','Policy')
        RETURN (1)
    END

    DELETE msdb.dbo.syspolicy_conditions_internal
    WHERE condition_id = @condition_id
    
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_rename_condition]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_rename_condition] 
@name sysname = NULL,
@condition_id int = NULL,
@new_name sysname = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    IF (@new_name IS NULL or LEN(@new_name) = 0)
    BEGIN
      RAISERROR(21263, -1, -1, '@new_name')
      RETURN(1) -- Failure
    END

    DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_condition_identifiers @name, @condition_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    UPDATE msdb.[dbo].[syspolicy_conditions_internal] 
    SET name = @new_name
    WHERE condition_id = @condition_id

    SELECT @retval = @@error
    RETURN(@retval)
END
GO

---------------------------------------------------------------
-- table for Policy category
---------------------------------------------------------------
IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_policy_categories_internal')
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_policy_categories_internal]...';
    CREATE TABLE [dbo].[syspolicy_policy_categories_internal] (
        policy_category_id int IDENTITY(1,1),
        name sysname,
        mandate_database_subscriptions bit default 1 NOT NULL,
        CONSTRAINT [PK_syspolicy_policy_categories] PRIMARY KEY CLUSTERED (policy_category_id ASC),
        CONSTRAINT [UQ_syspolicy_policy_categories_name] UNIQUE(name)
        );
END
GO

PRINT 'Creating view [dbo].[syspolicy_policy_categories]...'
GO
CREATE VIEW [dbo].[syspolicy_policy_categories]
AS
    SELECT     
        policy_category_id,
        name,
        mandate_database_subscriptions
    FROM [dbo].[syspolicy_policy_categories_internal]
GO

---------------------------------------------------------------
-- ObjectSet object
---------------------------------------------------------------
IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_object_sets_internal')
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_object_sets_internal]...'
CREATE TABLE [dbo].[syspolicy_object_sets_internal] (
    object_set_id int NOT NULL IDENTITY(1,1),
    object_set_name sysname NOT NULL,
    facet_id int,
    CONSTRAINT [PK_syspolicy_object_sets] PRIMARY KEY CLUSTERED (object_set_id),
    CONSTRAINT [UQ_syspolicy_object_sets_name] UNIQUE(object_set_name)
)

ALTER TABLE [dbo].[syspolicy_object_sets_internal]
    ADD CONSTRAINT [FK_syspolicy_object_sets_syspolicy_management_facets] FOREIGN KEY(facet_id)
    REFERENCES [dbo].[syspolicy_management_facets] (management_facet_id)
    ON DELETE CASCADE
END
GO

PRINT 'Creating view [dbo].[syspolicy_object_sets]...'
GO
CREATE VIEW [dbo].[syspolicy_object_sets]
AS
    SELECT     
        os.object_set_id,
        os.object_set_name,
        os.facet_id,
        facet.name as facet_name
    FROM [dbo].[syspolicy_object_sets_internal] AS os INNER JOIN [dbo].[syspolicy_management_facets] AS facet
    ON os.facet_id = facet.management_facet_id
GO

-----------------------------------------------------------
-- This procedure verifies if a object set definition exists
-- The caller can pass either the name or the id
-----------------------------------------------------------
PRINT 'Creating procedure [dbo].[sp_syspolicy_verify_object_set_identifiers]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_verify_object_set_identifiers]
@name sysname = NULL OUTPUT, 
@object_set_id int = NULL OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

  IF ((@name IS NULL)     AND (@object_set_id IS NULL)) OR
     ((@name IS NOT NULL) AND (@object_set_id IS NOT NULL))
  BEGIN
    -- TODO: Verify the error message is appropriate for object sets as well and not specific to policies
    RAISERROR(14524, -1, -1, '@name', '@object_set_id')
    RETURN(1) -- Failure
  END

  -- Check id
  IF (@object_set_id IS NOT NULL)
  BEGIN
    SELECT @name = object_set_name
    FROM msdb.dbo.syspolicy_object_sets
    WHERE (object_set_id = @object_set_id)
    
    -- the view would take care of all the permissions issues.
    IF (@name IS NULL) 
    BEGIN
        -- TODO: Where did 36 come from? Is this the total lenght of characters for an int?
      DECLARE @object_set_id_as_char VARCHAR(36)
      SELECT @object_set_id_as_char = CONVERT(VARCHAR(36), @object_set_id)
      RAISERROR(14262, -1, -1, '@object_set_id', @object_set_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check name
  IF (@name IS NOT NULL)
  BEGIN
    -- get the corresponding object_set_id (if the object_set exists)
    SELECT @object_set_id = object_set_id
    FROM msdb.dbo.syspolicy_object_sets
    WHERE (object_set_name = @name)
    
    -- the view would take care of all the permissions issues.
    IF (@object_set_id IS NULL) 
    BEGIN
    -- TODO: Verify the error message is appropriate for object sets as well and not specific to policies
      RAISERROR(14262, -1, -1, '@name', @name)
      RETURN(1) -- Failure
    END
  END

  RETURN (0)
END
GO

-----------------------------------------------------------
-- This procedure verifies if an object set is refernced (cannot be deleted)
-----------------------------------------------------------
PRINT 'Creating procedure [dbo].[sp_syspolicy_verify_object_set_references]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_verify_object_set_references]
@object_set_id int,
@is_referenced int OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	SELECT @is_referenced = count(*) FROM dbo.syspolicy_policies WHERE object_set_id = @object_set_id
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_add_object_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_add_object_set]
@object_set_name sysname,
@facet nvarchar (max),
@object_set_id int OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval             INT
	
	DECLARE @facet_id           INT
	DECLARE @null_column		sysname
	
	IF( @facet IS NULL)
		SET @null_column = '@facet'
		
	IF @null_column IS NOT NULL
	BEGIN
		RAISERROR(14043, -1, -1, @null_column, 'sp_syspolicy_add_object_set')
		RETURN(1)
	END

    SET @facet_id = (SELECT management_facet_id FROM [dbo].[syspolicy_management_facets] WHERE name = @facet)
        
    IF (@facet_id IS NULL)
    BEGIN
        RAISERROR (34014, -1, -1)
        RETURN(1)
    END

    INSERT INTO msdb.[dbo].[syspolicy_object_sets_internal]
                                        (object_set_name,
                                        facet_id)
    VALUES                            
                                        (@object_set_name,
                                        @facet_id)

    SELECT @retval = @@error
    SET @object_set_id = SCOPE_IDENTITY()
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_delete_object_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_delete_object_set]
@object_set_name sysname = NULL,
@object_set_id int = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_object_set_identifiers @object_set_name, @object_set_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    DELETE msdb.[dbo].[syspolicy_object_sets_internal] 
        WHERE object_set_id = @object_set_id

    RETURN (0)
END
GO


---------------------------------------------------------------
-- PolicyDefinition object
---------------------------------------------------------------

IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_policies_internal')
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_policies_internal]...';
CREATE TABLE [dbo].[syspolicy_policies_internal] (
	policy_id int IDENTITY(1,1),
	name sysname NOT NULL,
	condition_id int NOT NULL,
	root_condition_id int NULL,
	date_created datetime  NOT NULL default GETDATE(),
	execution_mode int NOT NULL default (0),
	policy_category_id int NULL,
	schedule_uid uniqueidentifier NULL,
	description nvarchar(max) NOT NULL default (''),
	help_text nvarchar(4000) NOT NULL default (''),
	help_link nvarchar(2083) NOT NULL default (''),
	object_set_id INT NULL,
	is_enabled bit default 0 NOT NULL,
	job_id uniqueidentifier NULL,
	created_by sysname NOT NULL default SUSER_SNAME(),
	modified_by sysname NULL,
	date_modified datetime NULL,
	CONSTRAINT [PK_syspolicy_policies] PRIMARY KEY CLUSTERED (policy_id ASC),
	CONSTRAINT [UQ_syspolicy_policies_name] UNIQUE(name)
	);
ALTER TABLE [dbo].[syspolicy_policies_internal] 
    ADD CONSTRAINT [FK_syspolicy_policies_syspolicy_conditions] FOREIGN KEY(condition_id)
    REFERENCES [dbo].[syspolicy_conditions_internal] (condition_id);

    ALTER TABLE [dbo].[syspolicy_policies_internal] 
        ADD CONSTRAINT [FK_syspolicy_policies_syspolicy_policy_categories] FOREIGN KEY(policy_category_id)
        REFERENCES [dbo].[syspolicy_policy_categories_internal] (policy_category_id);


ALTER TABLE [dbo].[syspolicy_policies_internal] 
    ADD CONSTRAINT [FK_syspolicy_policies_syspolicy_root_conditions] FOREIGN KEY(root_condition_id)
    REFERENCES [dbo].[syspolicy_conditions_internal] (condition_id);


    ALTER TABLE [dbo].[syspolicy_policies_internal] 
        ADD CONSTRAINT [FK_syspolicy_policies_sysjobs] FOREIGN KEY(job_id)
        REFERENCES [dbo].[sysjobs] (job_id);

ALTER TABLE [dbo].[syspolicy_policies_internal]
    ADD CONSTRAINT [FK_syspolicy_policies_syspolicy_object_sets] FOREIGN KEY(object_set_id)
    REFERENCES [dbo].[syspolicy_object_sets_internal] (object_set_id);
    
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_create_job]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_create_job] 
@schedule_uid uniqueidentifier,
@is_enabled bit = 0,
@jobID uniqueidentifier OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @job_name sysname

	-- create unique job name
	SET @job_name = N'syspolicy_check_schedule_' + LEFT(CONVERT(nvarchar(100), @schedule_uid), 100) 
	WHILE (EXISTS (SELECT * FROM msdb..sysjobs WHERE name = @job_name))
	BEGIN
		SET @job_name = N'syspolicy_check_schedule_' + LEFT(CONVERT(nvarchar(91), @schedule_uid), 91) + '_' + RIGHT(STR(FLOOR(RAND() * 100000000)),8) 
	END

	EXEC  msdb.dbo.sp_add_job @job_name=@job_name, 
			@enabled=@is_enabled, 
			@notify_level_eventlog=0, 
			@notify_level_email=2, 
			@notify_level_netsend=2, 
			@notify_level_page=2, 
			@delete_level=0, 
			@category_id=0, -- [Uncategorized (Local)]
			@job_id = @jobID OUTPUT

	EXEC msdb.dbo.sp_add_jobserver @job_name=@job_name, @server_name = @@servername

    EXEC msdb.dbo.sp_add_jobstep 
            @job_id=@jobID, 
			@step_name=N'Verify that automation is enabled.', 
		    @step_id=1, 
		    @cmdexec_success_code=0, 
		    @on_fail_action=1, 
		    @on_fail_step_id=0, 
		    @retry_attempts=0, 
		    @retry_interval=0, 
		    @os_run_priority=0, 
		    @subsystem=N'TSQL', 
		    @command=N'IF (msdb.dbo.fn_syspolicy_is_automation_enabled() != 1)
        BEGIN
            RAISERROR(34022, 16, 1)
        END', 
		    @database_name=N'master', 
		    @flags=0

	DECLARE @command nvarchar(max)
	SET @command = [dbo].[fn_syspolicy_get_ps_command] (@schedule_uid)

	EXEC msdb.dbo.sp_add_jobstep 
            @job_id=@jobID, 
			@step_name=N'Evaluate policies.', 
			@step_id=2, 
			@cmdexec_success_code=0, 
			@on_success_action=1, 
			@on_fail_action=2, 
			@retry_attempts=0, 
			@retry_interval=0, 
			@os_run_priority=0, 
			@subsystem=N'PowerShell', 
			@command=@command, 
			@flags=0

    EXEC msdb.dbo.sp_update_jobstep 
            @job_id = @jobID, 
            @step_id = 1, 
            @on_success_action=4, 
            @on_success_step_id=2 

	DECLARE @schedule_id int
	SELECT @schedule_id = schedule_id from msdb.dbo.sysschedules where schedule_uid = @schedule_uid

	EXEC msdb.dbo.sp_attach_schedule @job_name = @job_name, @schedule_id = @schedule_id
END
GO

PRINT 'Creating function [dbo].[fn_syspolicy_get_ps_command] ...'
GO

CREATE FUNCTION [dbo].[fn_syspolicy_get_ps_command] (@schedule_uid uniqueidentifier)
RETURNS nvarchar(max)
AS
BEGIN
	
	DECLARE @schedule_uid_string nvarchar(max);
	SET @schedule_uid_string = CONVERT(nvarchar(36), @schedule_uid);
	
	-- translate to PSPath root name, for default instances 
	-- we need to add \default as instance name
	DECLARE @root_name nvarchar(100);
	SET @root_name = @@SERVERNAME
	IF( 0 = CHARINDEX('\', @@SERVERNAME))
		SET @root_name = @root_name + N'\default';
	
	DECLARE @command nvarchar(max);
	SET @command = N'dir SQLSERVER:\SQLPolicy\' + @root_name + 
				N'\Policies | where { $_.ScheduleUid -eq "' + @schedule_uid_string + 
				N'" } |  where { $_.Enabled -eq 1} | where {$_.AutomatedPolicyEvaluationMode -eq 4} | Invoke-PolicyEvaluation -AdHocPolicyEvaluationMode 2 -TargetServerName ' + @@SERVERNAME
				
	RETURN @command
END
GO

PRINT 'Creating trigger [dbo].[syspolicy_insert_job_create_trigger] on [dbo].[syspolicy_policies_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_insert_job_create_trigger] on [dbo].[syspolicy_policies_internal]
FOR INSERT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

	DECLARE @jobID uniqueidentifier
	DECLARE @schedule_uid uniqueidentifier
	DECLARE @is_enabled bit

	-- verify that values in inserted.schedule_uid are valid
	IF EXISTS (
		SELECT * FROM inserted i 
		WHERE i.schedule_uid NOT IN 
				(SELECT schedule_uid FROM msdb.dbo.sysschedules ) AND 
		((i.execution_mode & 4) = 4))
	BEGIN
		ROLLBACK -- Failure
		RAISERROR (14365, -1, -1)
		RETURN
	END

	-- find all schedules referenced by the inserted policies for which 
	-- there is no agent job that executes the policies
	DECLARE schedule_cursor CURSOR LOCAL FOR
	    SELECT DISTINCT i.schedule_uid
	    FROM inserted i
	    WHERE 
		    ((i.execution_mode & 4) = 4) AND
	        NOT EXISTS (SELECT * 
	                        FROM msdb.dbo.syspolicy_policies p 
	                        WHERE 
	                            p.policy_id NOT IN (SELECT policy_id FROM inserted) AND
	                            p.schedule_uid = i.schedule_uid AND 
	                            ((p.execution_mode & 4) = 4) )
	
	-- iterate through the cursor and create a job for every schedule		
	OPEN schedule_cursor
	FETCH schedule_cursor INTO @schedule_uid
	WHILE @@FETCH_STATUS = 0
	BEGIN
		-- figure out if the job is enabled or not
		SELECT @is_enabled = COUNT(*) 
		FROM inserted i 
		WHERE i.schedule_uid = @schedule_uid AND i.is_enabled = 1
		
		-- explicitly nullify jobID, 
		-- (if we need to create more than 1 job, it will not be null and sp_add_job will think we're getting job from MSX)
		SET @jobID = NULL
		
		-- create the job that is going to execute the schedule
		EXEC [msdb].[dbo].[sp_syspolicy_create_job] @schedule_uid, @is_enabled, @jobID OUTPUT

		-- update the job_id back into the policies table
		UPDATE p SET p.job_id = @jobID 
			FROM msdb.dbo.syspolicy_policies_internal p
			INNER JOIN inserted i ON p.policy_id = i.policy_id
		WHERE 
			i.schedule_uid = @schedule_uid AND
			(i.execution_mode & 4) = 4

		FETCH schedule_cursor INTO @schedule_uid
	END
	
	CLOSE schedule_cursor
	DEALLOCATE schedule_cursor
	
	-- in case we haven't created the job we still need to update 
	-- the policies with their jobID
	UPDATE p
		SET p.job_id = ( SELECT TOP 1 p2.job_id 
						FROM msdb.dbo.syspolicy_policies p2 
						WHERE 
							p2.schedule_uid = p.schedule_uid AND 
							p2.job_id IS NOT NULL)
		FROM msdb.dbo.syspolicy_policies_internal p
		INNER JOIN inserted i ON p.policy_id = i.policy_id
		WHERE 
			((p.execution_mode & 4) = 4) AND
			p.job_id IS NULL
	
	-- See what jobs we need to enable.
	-- This can happen because we might create a new policy that 
	-- is enabled and there is already a job for it, but the existing
	-- job is disabled
	DECLARE jobs_to_enable CURSOR LOCAL FOR
		SELECT DISTINCT j.job_id
		FROM dbo.sysjobs_view j 
		JOIN msdb.dbo.syspolicy_policies p ON p.job_id = j.job_id
		JOIN inserted i ON p.policy_id = i.policy_id
		WHERE 
			((i.execution_mode & 4) = 4) AND
			j.enabled = 0 AND
			EXISTS ( SELECT * from msdb.dbo.syspolicy_policies p2 
						WHERE p2.job_id = j.job_id AND p2.is_enabled = 1)
	
	OPEN jobs_to_enable
	FETCH jobs_to_enable INTO @jobID
	WHILE @@FETCH_STATUS = 0
	BEGIN
		EXEC msdb.dbo.sp_update_job @job_id = @jobID, @enabled = 1
		
		FETCH jobs_to_enable INTO @jobID
	END
	CLOSE jobs_to_enable
	DEALLOCATE jobs_to_enable
	
	-- enable events infrastructure
	IF EXISTS ( SELECT * FROM inserted i WHERE ((i.execution_mode & 1) = 1))
	BEGIN
		EXEC sys.sp_syspolicy_update_ddl_trigger 
	END

	IF EXISTS (SELECT * FROM inserted i WHERE ((i.execution_mode & 2) = 2))
	BEGIN
		EXEC sys.sp_syspolicy_update_event_notification 
	END

	-- update owner information
	UPDATE msdb.dbo.syspolicy_policies_internal
	SET created_by = original_login(),
		date_created = getdate (),
		date_modified = NULL,
		modified_by = NULL
	FROM inserted i,
	   msdb.dbo.syspolicy_policies_internal policies
	WHERE i.policy_id = policies.policy_id

	-- protect against non-scheduled automation jobs
	-- that have expressions that execute script
	DECLARE @row_count int

    SELECT @row_count = count(*) 
        FROM syspolicy_conditions c  
        INNER JOIN inserted i ON (i.condition_id = c.condition_id OR i.root_condition_id = c.condition_id)
        WHERE    i.is_enabled != 0 AND
                i.execution_mode != 4 AND
                (1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteSql"]' ) OR
                1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteWql"]' ) ) 
        OPTION (FORCE ORDER);

    SELECT @row_count = @row_count + count(*)
        FROM dbo.syspolicy_target_set_levels l 
        INNER JOIN dbo.syspolicy_target_sets s ON s.target_set_id = l.target_set_id
        INNER JOIN syspolicy_conditions c on c.condition_id = l.condition_id
        INNER JOIN syspolicy_object_sets_internal os ON os.object_set_id = s.object_set_id
        INNER JOIN inserted i ON os.object_set_id = i.object_set_id
        WHERE    i.is_enabled != 0 AND
                i.execution_mode != 4 AND
                (1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteSql"]' ) OR
                1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteWql"]' ) )
        OPTION (FORCE ORDER);

    IF (@row_count > 0)
    BEGIN
        RAISERROR(34017, -1, -1);
        ROLLBACK TRANSACTION;
    END

END -- create trigger
GO

PRINT 'Creating trigger [dbo].[syspolicy_update_job_update_trigger] on [dbo].[syspolicy_policies_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_update_job_update_trigger] on [dbo].[syspolicy_policies_internal]
FOR UPDATE
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

	-- This is to prevent indirect entrance of the trigger
	IF (TRIGGER_NESTLEVEL() > 1) RETURN

	-- verify that values in inserted.schedule_uid are valid
	IF EXISTS (
		SELECT * FROM inserted i 
		WHERE i.schedule_uid NOT IN 
				(SELECT schedule_uid FROM msdb.dbo.sysschedules ) AND 
		((i.execution_mode & 4) = 4))
	BEGIN
		ROLLBACK -- Failure
		RAISERROR (14365, -1, -1)
		RETURN
	END

	-- update eventing infrastructure
	IF(UPDATE(execution_mode) OR UPDATE(is_enabled))
	BEGIN
		IF  EXISTS (SELECT * 
					FROM inserted i
					INNER JOIN deleted d ON i.policy_id = d.policy_id
					WHERE 
						(((i.execution_mode & 1) = 1) OR ((d.execution_mode & 1) = 1)) AND 
						(i.is_enabled != d.is_enabled OR i.execution_mode != d.execution_mode))
		BEGIN
			EXEC sys.sp_syspolicy_update_ddl_trigger 
		END

		IF  EXISTS (SELECT * 
					FROM inserted i
					INNER JOIN deleted d ON i.policy_id = d.policy_id
					WHERE 
						(((i.execution_mode & 2) = 2) OR ((d.execution_mode & 2) = 2)) AND 
						(i.is_enabled != d.is_enabled OR i.execution_mode != d.execution_mode))
		BEGIN
			EXEC sys.sp_syspolicy_update_event_notification 
		END
	END

	DECLARE @jobID uniqueidentifier
	DECLARE @is_enabled bit
	DECLARE @schedule_uid uniqueidentifier

    -- set the job_id to NULL for all policies whose schedule_uid has changed
    -- so that we can create a job if needed
    UPDATE p
        SET p.job_id = NULL
        FROM msdb.dbo.syspolicy_policies p
        INNER JOIN inserted i ON p.policy_id = i.policy_id
        INNER JOIN deleted d ON d.policy_id = p.policy_id
        WHERE i.schedule_uid != d.schedule_uid

	-- find all schedules referenced by the inserted policies for which 
	-- there is no agent job that executes the policies
	DECLARE schedule_cursor CURSOR LOCAL FOR
	    SELECT DISTINCT i.schedule_uid
	    FROM inserted i
	    WHERE 
		    ((i.execution_mode & 4) = 4) AND
	        NOT EXISTS (SELECT * 
	                        FROM msdb.dbo.syspolicy_policies p 
	                        WHERE 
	                            p.schedule_uid = i.schedule_uid AND 
	                            ((p.execution_mode & 4) = 4) AND
	                            p.job_id IS NOT NULL)
	                    
	-- iterate through the cursor and create a job for every schedule		
	OPEN schedule_cursor
	FETCH schedule_cursor INTO @schedule_uid
	WHILE @@FETCH_STATUS = 0
	BEGIN
		-- figure out if the job is enabled or not
		SELECT @is_enabled = COUNT(*) 
		FROM inserted i 
		WHERE i.schedule_uid = @schedule_uid AND i.is_enabled = 1
		
		-- create the job that is going to execute the schedule
		EXEC [msdb].[dbo].[sp_syspolicy_create_job] @schedule_uid, @is_enabled, @jobID OUTPUT

		-- update the job_id back into the policies table
		UPDATE p SET p.job_id = @jobID 
			FROM msdb.dbo.syspolicy_policies_internal p
			INNER JOIN inserted i ON p.policy_id = i.policy_id
		WHERE 
			i.schedule_uid = @schedule_uid AND
			(i.execution_mode & 4) = 4
	
		FETCH schedule_cursor INTO @schedule_uid
	END
	
	CLOSE schedule_cursor
	DEALLOCATE schedule_cursor
	
	-- in case we haven't created the job we still need to update 
	-- the policies with their jobID
	UPDATE p 
		SET p.job_id = ( SELECT TOP 1 p2.job_id 
						FROM msdb.dbo.syspolicy_policies p2 
						WHERE 
							p2.schedule_uid = p.schedule_uid AND
							p2.job_id IS NOT NULL)
		FROM msdb.dbo.syspolicy_policies p
		INNER JOIN inserted i ON p.policy_id = i.policy_id
		WHERE 
			((p.execution_mode & 4) = 4) AND
			p.job_id IS NULL
	
	-- if the execution_mode has changed then we need to clear the job references
	UPDATE p
		SET p.job_id = NULL
		FROM msdb.dbo.syspolicy_policies_internal p
		INNER JOIN inserted i ON p.policy_id = i.policy_id 
		INNER JOIN deleted d ON p.policy_id = d.policy_id
		WHERE 
			((i.execution_mode & 4) != 4) AND
			((d.execution_mode & 4) = 4) AND
			p.job_id IS NOT NULL
	
	-- See what jobs we need to enable.
	-- This can happen because we might create a new policy that 
	-- is enabled and there is already a job for it, but the existing
	-- job is disabled
	DECLARE jobs_to_enable CURSOR LOCAL FOR
		SELECT DISTINCT j.job_id
		FROM dbo.sysjobs_view j 
		JOIN msdb.dbo.syspolicy_policies p ON p.job_id = j.job_id
		JOIN inserted i ON p.policy_id = i.policy_id
		WHERE 
			((i.execution_mode & 4) = 4) AND
			j.enabled = 0 AND
			EXISTS ( SELECT * from msdb.dbo.syspolicy_policies p2 
						WHERE p2.job_id = j.job_id AND p2.is_enabled = 1)
	
	OPEN jobs_to_enable
	FETCH jobs_to_enable INTO @jobID
	WHILE @@FETCH_STATUS = 0
	BEGIN
		EXEC msdb.dbo.sp_update_job @job_id = @jobID, @enabled = 1
		
		FETCH jobs_to_enable INTO @jobID
	END
	CLOSE jobs_to_enable
	DEALLOCATE jobs_to_enable

	-- Find out what jobs have to be deleted because the policy's schedule 
	-- has changed
	IF (UPDATE(schedule_uid))
	BEGIN
		DECLARE deleted_cursor CURSOR LOCAL FOR 
			SELECT DISTINCT d.job_id
			FROM deleted d
			WHERE 
				((d.execution_mode & 4) = 4) AND
				d.job_id NOT IN (SELECT job_id FROM msdb.dbo.syspolicy_policies p 
								WHERE 
								((p.execution_mode & 4) = 4))

		OPEN deleted_cursor
		FETCH deleted_cursor INTO @jobID
		
		WHILE (@@FETCH_STATUS=0)
		BEGIN
			-- delete the job(s), but do not delete the shared schedule
			IF (@jobID IS NOT NULL)
				EXEC msdb.dbo.sp_delete_job @job_id = @jobID, @delete_unused_schedule = 0

			FETCH deleted_cursor INTO @jobID
		END -- while (@@FETCH_STATUS=0)

		CLOSE deleted_cursor
		DEALLOCATE deleted_cursor
	END	-- UPDATE(schedule_uid)


	-- See what jobs we need to disable.
	-- This can happen because we do not need to delete the job, but
	-- all policies that reference it are disabled.
	DECLARE jobs_to_disable CURSOR LOCAL FOR
		SELECT DISTINCT j.job_id
		FROM dbo.sysjobs_view j 
		JOIN msdb.dbo.syspolicy_policies p ON p.job_id = j.job_id
		WHERE 
			j.enabled = 1 AND
			NOT EXISTS ( SELECT * from msdb.dbo.syspolicy_policies p2 
						WHERE p2.job_id = j.job_id AND p2.is_enabled = 1 AND ((p2.execution_mode & 4) = 4))
	
	OPEN jobs_to_disable
	FETCH jobs_to_disable INTO @jobID
	WHILE @@FETCH_STATUS = 0
	BEGIN
		EXEC msdb.dbo.sp_update_job @job_id = @jobID, @enabled = 0
		
		FETCH jobs_to_disable INTO @jobID
	END
	CLOSE jobs_to_disable
	DEALLOCATE jobs_to_disable

    UPDATE msdb.dbo.syspolicy_policies_internal
    SET modified_by = original_login(),
        date_modified = GETDATE()
    FROM inserted i,
       msdb.dbo.syspolicy_policies_internal policies
    WHERE i.policy_id = policies.policy_id
END -- update trigger
GO

PRINT 'Creating trigger [dbo].[syspolicy_insert_policy_trigger] on [dbo].[syspolicy_policies_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_insert_policy_trigger] on [dbo].[syspolicy_policies_internal]
FOR INSERT
AS
BEGIN
    DECLARE @object_set_id int, @name sysname

	SELECT TOP 1 @object_set_id = i.object_set_id, @name = i.name 
	FROM inserted i 
	WHERE 1 < (SELECT count(*) FROM syspolicy_policies p WHERE p.object_set_id = i.object_set_id)

	IF @@ROWCOUNT > 0
	BEGIN
				DECLARE @os_name sysname, @policy_name sysname
				SELECT TOP 1 @os_name = os.object_set_name, @policy_name = p.name
				 FROM syspolicy_object_sets os 
					INNER JOIN syspolicy_policies p ON (os.object_set_id = p.object_set_id)
				WHERE os.object_set_id = @object_set_id AND p.name <> @name
				
				RAISERROR(34013, -1, -1, 'ObjectSet', @os_name, @policy_name) 
				ROLLBACK TRANSACTION
	END
END
GO

PRINT 'Creating trigger [dbo].[syspolicy_update_policy_trigger] on [dbo].[syspolicy_policies_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_update_policy_trigger] on [dbo].[syspolicy_policies_internal]
FOR UPDATE
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END
	
	-- This is to prevent indirect execution of the trigger
	IF (TRIGGER_NESTLEVEL() > 1) RETURN

    IF( UPDATE(condition_id) )
    BEGIN
        -- delete all health state records for active policies whose 
        -- condition has changed
        DELETE FROM [dbo].[syspolicy_system_health_state_internal] 
            FROM [dbo].[syspolicy_system_health_state_internal] phs
            INNER JOIN inserted i ON phs.policy_id = i.policy_id
            INNER JOIN deleted d ON phs.policy_id = d.policy_id
            WHERE d.condition_id != i.condition_id AND i.is_enabled = 1
    END

    IF( UPDATE(object_set_id) )
    BEGIN
        DECLARE @object_set_id int, @numref int, @new_object_set_id int, @name sysname
        
        DECLARE os_cursor CURSOR LOCAL FOR 
        SELECT i.object_set_id, d.object_set_id, i.name
        FROM inserted i INNER JOIN deleted d ON (i.policy_id = d.policy_id) 
        WHERE (d.object_set_id IS NOT NULL AND i.object_set_id IS NULL)
			OR (i.object_set_id IS NOT NULL AND d.object_set_id IS NULL)
			OR (d.object_set_id != i.object_set_id)

        OPEN os_cursor
        FETCH os_cursor INTO @new_object_set_id, @object_set_id, @name
    
        WHILE @@FETCH_STATUS = 0 
        BEGIN
			IF (@object_set_id IS NOT NULL)
			BEGIN
				EXEC sp_syspolicy_verify_object_set_references @object_set_id, @numref OUTPUT
				IF (@numref = 0)
					EXEC sp_syspolicy_delete_object_set @object_set_id=@object_set_id
            END
            
			IF (@new_object_set_id IS NOT NULL)
			BEGIN
				EXEC sp_syspolicy_verify_object_set_references @new_object_set_id, @numref OUTPUT
				IF (@numref > 1)
				BEGIN
					DECLARE @os_name sysname, @policy_name sysname
					SELECT TOP 1 @os_name = os.object_set_name, @policy_name = p.name
					 FROM syspolicy_object_sets os 
						INNER JOIN syspolicy_policies p ON (os.object_set_id = p.object_set_id)
					WHERE os.object_set_id = @object_set_id AND p.name <> @name
					
					RAISERROR(34013, -1, -1, 'ObjectSet', @os_name, @policy_name) 
					ROLLBACK TRANSACTION
				END
            END
                
            FETCH os_cursor INTO @new_object_set_id, @object_set_id, @name
        END
            
        CLOSE os_cursor
        DEALLOCATE os_cursor    

    END

    IF( UPDATE(is_enabled) )
    BEGIN
        -- delete all health state records for policies that 
        -- have been disabled
        DELETE FROM [dbo].[syspolicy_system_health_state_internal] 
            FROM [dbo].[syspolicy_system_health_state_internal] phs
            INNER JOIN inserted i ON phs.policy_id = i.policy_id
            INNER JOIN deleted d ON phs.policy_id = d.policy_id
            WHERE d.is_enabled = 1 AND i.is_enabled = 0
    END

    IF( UPDATE(is_enabled) )
    BEGIN
        DECLARE @row_count int

        SELECT @row_count = count(*) 
            FROM syspolicy_conditions c  
            INNER JOIN inserted i ON (i.condition_id = c.condition_id OR i.root_condition_id = c.condition_id)
            WHERE    i.is_enabled != 0 AND
                    i.execution_mode != 4 AND
                    (1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteSql"]' ) OR
                    1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteWql"]' ) ) 
            OPTION (FORCE ORDER)

        SELECT @row_count = @row_count + count(*)
            FROM dbo.syspolicy_target_set_levels l 
            INNER JOIN dbo.syspolicy_target_sets s ON s.target_set_id = l.target_set_id
            INNER JOIN syspolicy_conditions c on c.condition_id = l.condition_id
            INNER JOIN syspolicy_object_sets_internal os on os.object_set_id = s.object_set_id
            INNER JOIN inserted i ON os.object_set_id = i.object_set_id
            WHERE    i.is_enabled != 0 AND
                    i.execution_mode != 4 AND
                    (1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteSql"]' ) OR
                    1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteWql"]' ) )
            OPTION (FORCE ORDER)

        IF (@row_count > 0)
        BEGIN
            RAISERROR(34017, -1, -1) 
            ROLLBACK TRANSACTION
        END
    END
END
GO

PRINT 'Creating trigger [dbo].[syspolicy_delete_job_delete_trigger] on [dbo].[syspolicy_policies_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_delete_job_delete_trigger] on [dbo].[syspolicy_policies_internal]
FOR DELETE
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

	DECLARE @jobID uniqueidentifier 

	-- Declare the cursor to iterate over the jobs that are only referenced 
	-- by deleted policies. The jobs that are still referenced by active policies 
	-- should not be deleted.
	DECLARE deleted_cursor CURSOR LOCAL FOR 
		SELECT DISTINCT d.job_id
		FROM deleted d
		WHERE 
			((d.execution_mode & 4) = 4) AND
			NOT EXISTS (SELECT * FROM msdb.dbo.syspolicy_policies p 
							WHERE 
							p.job_id = d.job_id AND 
							((p.execution_mode & 4) = 4) AND
							p.policy_id NOT IN (SELECT d2.policy_id FROM deleted d2))

	OPEN deleted_cursor
	FETCH deleted_cursor INTO @jobID
	
	WHILE (@@FETCH_STATUS=0)
	BEGIN
		-- delete the job(s), but do not delete the shared schedule
		IF (@jobID IS NOT NULL)
			EXEC msdb.dbo.sp_delete_job @job_id = @jobID, @delete_unused_schedule = 0

		FETCH deleted_cursor INTO @jobID
	END -- while (@@FETCH_STATUS=0)

    CLOSE deleted_cursor
    DEALLOCATE deleted_cursor


	-- See what jobs we need to disable.
	-- This can happen because we do not need to delete the job, but
	-- all policies that reference it are disabled.
	DECLARE jobs_to_disable CURSOR LOCAL FOR
		SELECT DISTINCT j.job_id
		FROM dbo.sysjobs_view j 
		JOIN deleted d ON d.job_id = j.job_id
		WHERE 
			j.enabled = 1 AND
			NOT EXISTS ( SELECT * from msdb.dbo.syspolicy_policies p2 
						WHERE p2.job_id = j.job_id AND p2.is_enabled = 1 AND ((p2.execution_mode & 4) = 4))
	
	OPEN jobs_to_disable
	FETCH jobs_to_disable INTO @jobID
	WHILE @@FETCH_STATUS = 0
	BEGIN
		EXEC msdb.dbo.sp_update_job @job_id = @jobID, @enabled = 0
		
		FETCH jobs_to_disable INTO @jobID
	END
	CLOSE jobs_to_disable
	DEALLOCATE jobs_to_disable

	-- update eventing infrastructure
	IF EXISTS ( SELECT * FROM deleted d WHERE ((d.execution_mode & 1) = 1))
	BEGIN
		EXEC sys.sp_syspolicy_update_ddl_trigger 
	END

	IF EXISTS (SELECT * FROM deleted d WHERE ((d.execution_mode & 2) = 2))
	BEGIN
		EXEC sys.sp_syspolicy_update_event_notification 
	END

END
GO

PRINT 'Creating trigger [dbo].[syspolicy_instead_delete_policy_trigger] on [dbo].[syspolicy_policies_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_instead_delete_policy_trigger] on [dbo].[syspolicy_policies_internal]
INSTEAD OF DELETE 
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

	-- This trigger deletes references in given order to protect from deadlocks
	DELETE msdb.dbo.syspolicy_policy_execution_history_internal	WHERE policy_id in (SELECT policy_id FROM deleted)
	DELETE msdb.dbo.syspolicy_system_health_state_internal		WHERE policy_id in (SELECT policy_id FROM deleted)
	DELETE msdb.dbo.syspolicy_policies_internal		WHERE policy_id in (SELECT policy_id FROM deleted)
END
GO

PRINT 'Creating view [dbo].[syspolicy_policies]...'
GO
CREATE VIEW [dbo].[syspolicy_policies]
AS
    SELECT     
        policy_id,
        name,
        condition_id,
        root_condition_id,
        date_created,
        execution_mode,
        policy_category_id,
        schedule_uid,
        description,
        help_text,
        help_link,
        object_set_id,
        is_enabled,
        job_id,
        created_by,
        modified_by,
        date_modified
    FROM [dbo].[syspolicy_policies_internal]
GO

PRINT ''
PRINT 'Creating trigger syspolicy_insert_condition_trigger...'
GO

CREATE TRIGGER dbo.syspolicy_insert_condition_trigger
ON msdb.dbo.syspolicy_conditions_internal
AFTER INSERT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

  UPDATE msdb.dbo.syspolicy_conditions_internal
  SET created_by = original_login()
  FROM inserted i INNER JOIN
       msdb.dbo.syspolicy_conditions_internal conditions
  ON i.condition_id = conditions.condition_id

END
GO


PRINT ''
PRINT 'Creating trigger dbo.syspolicy_for_update_condition_trigger...'
GO

CREATE TRIGGER dbo.syspolicy_for_update_condition_trigger
ON msdb.dbo.syspolicy_conditions_internal
FOR UPDATE
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

	-- This is to prevent indirect entrance of the trigger
	IF (TRIGGER_NESTLEVEL() > 1) 
		RETURN

    -- do not allow expression to be changed to a script 
    -- if the policy is enabled
    IF UPDATE(expression)
    BEGIN
        DECLARE @row_count int

        SELECT @row_count = count(*) 
            FROM inserted i 
            INNER JOIN syspolicy_policies p ON (i.condition_id = p.condition_id OR p.root_condition_id = i.condition_id)
            WHERE    p.is_enabled != 0 AND
                    p.execution_mode != 4 AND
                    (1 = CONVERT(xml, i.expression).exist('//FunctionType/text()[.="ExecuteSql"]' ) OR
                    1 = CONVERT(xml, i.expression).exist('//FunctionType/text()[.="ExecuteWql"]' ) ) 
            OPTION (FORCE ORDER)

        SELECT @row_count = @row_count + count(*)
            FROM dbo.syspolicy_target_set_levels l 
            INNER JOIN dbo.syspolicy_target_sets s ON s.target_set_id = l.target_set_id
            INNER JOIN inserted i on i.condition_id = l.condition_id
            INNER JOIN syspolicy_object_sets_internal os ON s.object_set_id = os.object_set_id
            INNER JOIN syspolicy_policies p ON os.object_set_id = p.object_set_id
            WHERE    p.is_enabled != 0 AND
                    p.execution_mode != 4 AND
                    (1 = CONVERT(xml, i.expression).exist('//FunctionType/text()[.="ExecuteSql"]' ) OR
                    1 = CONVERT(xml, i.expression).exist('//FunctionType/text()[.="ExecuteWql"]' ) )
            OPTION (FORCE ORDER)

        IF (@row_count > 0)
        BEGIN
            RAISERROR(34017, -1, -1) 
            ROLLBACK TRANSACTION
        END

    END
END
GO

PRINT ''
PRINT 'Creating trigger syspolicy_after_update_condition_trigger...'
GO

CREATE TRIGGER dbo.syspolicy_after_update_condition_trigger
ON msdb.dbo.syspolicy_conditions_internal
AFTER UPDATE
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

	-- This is to prevent indirect entrance of the trigger
	IF (TRIGGER_NESTLEVEL() > 1) 
		RETURN

    UPDATE msdb.dbo.syspolicy_conditions_internal
        SET modified_by = original_login(), date_modified = GETDATE()
        FROM inserted i 
        INNER JOIN msdb.dbo.syspolicy_conditions_internal c ON i.condition_id = c.condition_id

    -- update health state table by deleting all the records for 
    -- policies whose expression has been modified
    IF UPDATE(expression)
    BEGIN
        DELETE FROM dbo.syspolicy_system_health_state_internal 
            FROM dbo.syspolicy_system_health_state_internal phs
            INNER JOIN dbo.syspolicy_policies p ON phs.policy_id = p.policy_id
            INNER JOIN inserted i ON p.condition_id = i.condition_id
            INNER JOIN deleted d ON p.condition_id = d.condition_id
            WHERE d.expression != i.expression 
    END
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_verify_policy_category_identifiers]...'
GO
-----------------------------------------------------------
-- This procedure verifies if a policy category exists
-- The caller can pass either the policy category name or the id
-----------------------------------------------------------
CREATE PROCEDURE [dbo].[sp_syspolicy_verify_policy_category_identifiers]
@policy_category_name sysname = NULL OUTPUT, 
@policy_category_id int = NULL OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

  IF ((@policy_category_name IS NULL)     AND (@policy_category_id IS NULL)) OR
     ((@policy_category_name IS NOT NULL) AND (@policy_category_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, '@policy_category_name', '@policy_category_id')
    RETURN(1) -- Failure
  END

  -- Check id
  IF (@policy_category_id IS NOT NULL)
  BEGIN
    SELECT @policy_category_name = name
    FROM msdb.dbo.syspolicy_policy_categories
    WHERE (policy_category_id = @policy_category_id)
    
    -- the view would take care of all the permissions issues.
    IF (@policy_category_name IS NULL) 
    BEGIN
      DECLARE @policy_category_id_as_char VARCHAR(36)
      SELECT @policy_category_id_as_char = CONVERT(VARCHAR(36), @policy_category_id)
      RAISERROR(14262, -1, -1, '@policy_category_id', @policy_category_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check name
  IF (@policy_category_name IS NOT NULL)
  BEGIN
    -- get the corresponding policy_category_id (if the condition exists)
    SELECT @policy_category_id = policy_category_id
    FROM msdb.dbo.syspolicy_policy_categories
    WHERE (name = @policy_category_name)
    
    -- the view would take care of all the permissions issues.
    IF (@policy_category_id IS NULL) 
    BEGIN
      RAISERROR(14262, -1, -1, '@policy_category_name', @policy_category_name)
      RETURN(1) -- Failure
    END
  END

  RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_add_policy_category]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_add_policy_category]
@name sysname,
@mandate_database_subscriptions bit = 1,
@policy_category_id int OUTPUT 
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval			INT
	DECLARE @null_column	sysname
	
	SET @null_column = NULL
	
	IF(@name IS NULL OR @name = N'')
		SET @null_column = '@name'

    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_syspolicy_add_policy_category')
        RETURN(1)
    END

    IF EXISTS (SELECT * FROM msdb.dbo.syspolicy_policy_categories_internal WHERE name = @name)
    BEGIN
        RAISERROR(34010, -1, -1, 'Policy Category', @name)
        RETURN(1)
    END

    INSERT INTO msdb.dbo.syspolicy_policy_categories_internal(name, mandate_database_subscriptions) VALUES (@name, @mandate_database_subscriptions)
    SELECT @retval = @@error
    SET @policy_category_id = SCOPE_IDENTITY()
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_delete_policy_category]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_delete_policy_category]
@name sysname = NULL,
@policy_category_id int = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_policy_category_identifiers @name, @policy_category_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    IF EXISTS (SELECT * FROM msdb.dbo.syspolicy_policy_category_subscriptions WHERE policy_category_id = @policy_category_id)
    BEGIN
        RAISERROR(34012,-1,-1,'Policy Category','Policy Subscription')
        RETURN (1)
    END


    IF EXISTS (SELECT * FROM msdb.dbo.syspolicy_policies WHERE policy_category_id = @policy_category_id)
    BEGIN
        RAISERROR(34012,-1,-1,'Policy Category','Policy')
        RETURN (1)
    END

    DELETE msdb.dbo.syspolicy_policy_categories_internal
    WHERE policy_category_id = @policy_category_id
    
    SET @retval = @@error
    RETURN @retval
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_rename_policy_category]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_rename_policy_category] 
@name sysname = NULL,
@policy_category_id int = NULL,
@new_name sysname = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    IF (@new_name IS NULL or LEN(@new_name) = 0)
    BEGIN
      RAISERROR(21263, -1, -1, '@new_name')
      RETURN(1) -- Failure
    END

    DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_policy_category_identifiers @name, @policy_category_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    UPDATE msdb.[dbo].[syspolicy_policy_categories_internal ]
    SET name = @new_name
    WHERE policy_category_id = @policy_category_id

    SELECT @retval = @@error
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_update_policy_category]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_update_policy_category] 
@name sysname = NULL,
@policy_category_id int = NULL,
@mandate_database_subscriptions bit = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_policy_category_identifiers @name, @policy_category_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    UPDATE msdb.[dbo].[syspolicy_policy_categories_internal ]
    SET mandate_database_subscriptions = ISNULL(@mandate_database_subscriptions, mandate_database_subscriptions)
    WHERE policy_category_id = @policy_category_id

    SELECT @retval = @@error
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_add_policy]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_add_policy] 
@name sysname,
@condition_id int = NULL,
@condition_name sysname = NULL,
@schedule_uid uniqueidentifier = NULL,
@policy_category sysname = NULL,
@description nvarchar(max) = N'',
@help_text nvarchar(4000) = N'',
@help_link nvarchar(2083) = N'',
@execution_mode int,
@is_enabled bit = 0,
@root_condition_id int = NULL,
@root_condition_name sysname = NULL,
@object_set sysname = NULL,
@policy_id int = NULL OUTPUT
WITH EXECUTE AS OWNER
AS
BEGIN
    DECLARE @retval_check int;
    EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
    IF ( 0!= @retval_check)
    BEGIN
        RETURN @retval_check
    END

    DECLARE @retval         INT
    DECLARE @null_column    sysname
    
    SET @null_column = NULL

    IF (@name IS NULL OR @name = N'')
        SET @null_column = '@name'
    ELSE IF (@execution_mode IS NULL )
        SET @null_column = '@execution_mode'
    ELSE IF( @is_enabled IS NULL)
        SET @null_column = '@is_enabled'
    ELSE IF( @description IS NULL)
        SET @null_column = '@description'
    ELSE IF( @help_text IS NULL)
        SET @null_column = '@help_text'
    ELSE IF( @help_link IS NULL)
        SET @null_column = '@help_link'
    

    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_syspolicy_add_policy')
        RETURN(1)
    END

    IF EXISTS (SELECT * FROM msdb.dbo.syspolicy_policies WHERE name = @name)
    BEGIN
        RAISERROR(34010, -1, -1, 'Policy', @name)
        RETURN(1)
    END

    SET @schedule_uid = ISNULL (@schedule_uid, '{00000000-0000-0000-0000-000000000000}')

    --Check for the execution mode value
    IF (@execution_mode NOT IN (0,1,2,4,5,6))
    BEGIN 
        RAISERROR(34004, -1, -1, @execution_mode)
        RETURN (1)
    END

    IF (@schedule_uid = '{00000000-0000-0000-0000-000000000000}' AND (@execution_mode & 4) = 4)
    BEGIN
        RAISERROR (34011, -1, -1, 'schedule_uid', 4)
        RETURN(1)
    END

    IF (@is_enabled = 1 AND @execution_mode = 0)
    BEGIN
        RAISERROR (34011, -1, -1, 'is_enabled', @execution_mode)
        RETURN(1)
    END

    -- Turn [nullable] empty string parameters into NULLs
    IF @condition_name = '' SELECT @condition_name = NULL
    IF @policy_category = ''   SELECT @policy_category = NULL
    IF @root_condition_name = '' SELECT @root_condition_name = NULL

    -- verify that the condition exists
    EXEC @retval = msdb.dbo.sp_syspolicy_verify_condition_identifiers @condition_name = @condition_name OUTPUT, @condition_id = @condition_id OUTPUT
    IF (@retval <> 0)
        RETURN(1)
        
    -- convert @object_set into id if needed
    DECLARE @object_set_id INT
    DECLARE @object_set_facet_id INT
    IF (@object_set IS NOT NULL)
    BEGIN
        SELECT @object_set_id = object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name = @object_set
        IF @object_set_id IS NULL
        BEGIN
            -- TODO: RAISERROR that specified object set doesn't exist
            RAISERROR(N'specified object set does not exists', -1, -1)
            RETURN(1) -- Failure
        END
        ELSE
        BEGIN
            SELECT @object_set_facet_id = facet_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name = @object_set
            -- Ensure the object set has been created from the same facet that the policy condition has been created
            IF (@object_set_facet_id <> (SELECT facet_id FROM msdb.dbo.syspolicy_conditions_internal WHERE condition_id = @condition_id))
            BEGIN
                -- TODO: RAISEERROR that specified object_set isn't created from the facet that the policy condition has been created from
                RAISERROR(N'specified object set does not match facet the policy condition was created off', -1, -1)
                RETURN(1) -- Failure
            END
        END
    END

    IF (@root_condition_name IS NOT NULL) OR (@root_condition_id IS NOT NULL)
    BEGIN
        -- verify that the root condition exists
        EXEC @retval = msdb.dbo.sp_syspolicy_verify_condition_identifiers @condition_name = @root_condition_name OUTPUT, @condition_id = @root_condition_id OUTPUT
        IF (@retval <> 0)
            RETURN(1)
            
        -- Check execution mode for compatibility with root_condition
        IF (@execution_mode = 1) OR (@execution_mode = 2) -- Enforce or Check on Change
        BEGIN
            RAISERROR (34011, -1, -1, 'root_condition', @execution_mode)
            RETURN(1)
        END

    END

    -- verify schedule
    IF (@schedule_uid != '{00000000-0000-0000-0000-000000000000}')
    BEGIN
        IF NOT EXISTS (SELECT * FROM msdb.dbo.sysschedules WHERE schedule_uid = @schedule_uid)
        BEGIN
            RAISERROR(14365, -1, -1)
            RETURN(1) -- Failure
        END
    END

    -- convert group_name into id if needed
    DECLARE @policy_category_id INT
    IF ( (@policy_category IS NOT NULL) )
    BEGIN 
        IF NOT EXISTS (SELECT * from msdb.dbo.syspolicy_policy_categories WHERE name = @policy_category)
        BEGIN
            RAISERROR(34015, -1, -1,@policy_category)
            RETURN(1) -- Failure
        END
        ELSE
            SELECT @policy_category_id = policy_category_id FROM msdb.dbo.syspolicy_policy_categories WHERE name = @policy_category
    END
    
    INSERT INTO msdb.dbo.syspolicy_policies_internal
                                        (name, 
                                        execution_mode, 
                                        schedule_uid,
                                        policy_category_id,
                                        description,
                                        help_text,
                                        help_link,
                                        condition_id,
                                        root_condition_id,
                                        object_set_id,
                                        is_enabled)
    VALUES                            
                                        (@name, 
                                        @execution_mode, 
                                        @schedule_uid,
                                        @policy_category_id,
                                        @description,
                                        @help_text,
                                        @help_link,
                                        @condition_id,
                                        @root_condition_id,
                                        @object_set_id,
                                        @is_enabled)

    SELECT @retval = @@error
    SET @policy_id = SCOPE_IDENTITY()
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_verify_policy_identifiers]...'
GO
-----------------------------------------------------------
-- This procedure verifies if a policy definition exists
-- The caller can pass either the name or the id
-----------------------------------------------------------
CREATE PROCEDURE [dbo].[sp_syspolicy_verify_policy_identifiers]
@name sysname = NULL OUTPUT, 
@policy_id int = NULL OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

  IF ((@name IS NULL)     AND (@policy_id IS NULL)) OR
     ((@name IS NOT NULL) AND (@policy_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, '@name', '@policy_id')
    RETURN(1) -- Failure
  END

  -- Check id
  IF (@policy_id IS NOT NULL)
  BEGIN
    SELECT @name = name
    FROM msdb.dbo.syspolicy_policies
    WHERE (policy_id = @policy_id)
    
    -- the view would take care of all the permissions issues.
    IF (@name IS NULL) 
    BEGIN
      DECLARE @policy_id_as_char VARCHAR(36)
      SELECT @policy_id_as_char = CONVERT(VARCHAR(36), @policy_id)
      RAISERROR(14262, -1, -1, '@policy_id', @policy_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check name
  IF (@name IS NOT NULL)
  BEGIN
    -- get the corresponding policy_id (if the policy exists)
    SELECT @policy_id = policy_id
    FROM msdb.dbo.syspolicy_policies
    WHERE (name = @name)
    
    -- the view would take care of all the permissions issues.
    IF (@policy_id IS NULL) 
    BEGIN
      RAISERROR(14262, -1, -1, '@name', @name)
      RETURN(1) -- Failure
    END
  END

  RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_rename_policy]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_rename_policy] 
@name sysname = NULL,
@policy_id int = NULL,
@new_name sysname = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    IF (@new_name IS NULL or LEN(@new_name) = 0)
    BEGIN
      RAISERROR(21263, -1, -1, '@new_name')
      RETURN(1) -- Failure
    END

    DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_policy_identifiers @name, @policy_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    UPDATE msdb.[dbo].[syspolicy_policies_internal] 
    SET name = @new_name
    WHERE policy_id = @policy_id

    SELECT @retval = @@error
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_update_policy]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_update_policy] 
@name sysname = NULL,
@policy_id int = NULL,
@condition_id int=NULL,
@condition_name sysname = NULL,
@execution_mode int=NULL,
@policy_category sysname = NULL,
@schedule_uid uniqueidentifier = NULL,
@description nvarchar(max) = NULL,
@help_text nvarchar(4000) = NULL,
@help_link nvarchar(2083) = NULL,
@root_condition_id int = -1,
@root_condition_name sysname = NULL,
@object_set_id int = -1,
@object_set sysname = NULL,
@is_enabled bit = NULL
WITH EXECUTE AS OWNER
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	--Check for the execution mode value
	IF (((@execution_mode IS NOT NULL)) AND (@execution_mode NOT IN (0,1,2,4,5,6)))
	BEGIN 
		RAISERROR(34004, -1, -1, @execution_mode)
		RETURN (1)
	END

    -- Turn [nullable] empty string parameters into NULLs
    IF @name = ''           SELECT @name = NULL
    IF @condition_name = '' SELECT @condition_name = NULL
    IF @root_condition_name = '' 
        BEGIN
        SELECT @root_condition_name = NULL
        IF @root_condition_id = -1
            -- root_condition is being reset
            SELECT @root_condition_id = NULL
        END
    IF @object_set = '' 
        BEGIN
        SELECT @object_set = NULL
        IF @object_set_id = -1
            -- object_set is being reset
            SELECT @object_set_id = NULL
        END

    DECLARE @retval              INT

    EXEC @retval = msdb.dbo.sp_syspolicy_verify_policy_identifiers @name, @policy_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    SELECT  @execution_mode = ISNULL(@execution_mode, execution_mode),
            @is_enabled = ISNULL(@is_enabled, is_enabled) 
        FROM msdb.dbo.syspolicy_policies WHERE policy_id = @policy_id

    IF(@condition_id IS NOT NULL or @condition_name IS NOT NULL)
    BEGIN
        EXEC @retval = msdb.dbo.sp_syspolicy_verify_condition_identifiers @condition_name = @condition_name OUTPUT, @condition_id = @condition_id OUTPUT
        IF (@retval <> 0)
            RETURN (1)
    END

    IF((@root_condition_id IS NOT NULL and @root_condition_id != -1) or @root_condition_name IS NOT NULL)
    BEGIN
        IF (@root_condition_id = -1 and @root_condition_name IS NOT NULL)
            SET @root_condition_id = NULL
        EXEC @retval = msdb.dbo.sp_syspolicy_verify_condition_identifiers @condition_name = @root_condition_name OUTPUT, @condition_id = @root_condition_id OUTPUT
        IF (@retval <> 0)
            RETURN (1)
    END

    SET @schedule_uid = ISNULL (@schedule_uid, '{00000000-0000-0000-0000-000000000000}')

    IF (@schedule_uid = '{00000000-0000-0000-0000-000000000000}' AND (@execution_mode & 4) = 4)
    BEGIN
        RAISERROR (34011, -1, -1, 'schedule_uid', 4)
        RETURN(1)
    END

    IF (@is_enabled = 1 AND @execution_mode = 0)
    BEGIN
        RAISERROR (34011, -1, -1, 'is_enabled', @execution_mode)
        RETURN(1)
    END

    IF (@schedule_uid != '{00000000-0000-0000-0000-000000000000}')
    BEGIN
        -- verify the schedule exists
        IF NOT EXISTS (SELECT schedule_id FROM msdb.dbo.sysschedules WHERE schedule_uid = @schedule_uid)
        BEGIN
            RAISERROR (14365, -1, -1)
            RETURN(1)
        END
    END

    DECLARE @object_set_facet_id INT
    IF ((@object_set_id IS NOT NULL and @object_set_id != -1) or @object_set IS NOT NULL)
    BEGIN
        IF (@object_set_id = -1 and @object_set IS NOT NULL)
            SET @object_set_id = NULL
        EXEC @retval = msdb.dbo.sp_syspolicy_verify_object_set_identifiers @name = @object_set OUTPUT, @object_set_id = @object_set_id OUTPUT
        IF (@retval <> 0)
            RETURN (1)

        SELECT @object_set_facet_id = facet_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name = @object_set
        -- Ensure the object set has been created from the same facet that the policy condition has been created
        IF (@object_set_facet_id <> (SELECT facet_id FROM msdb.dbo.syspolicy_conditions_internal WHERE condition_id = @condition_id))
        BEGIN
            -- TODO: RAISEERROR that specified object_set isn't created from the facet that the policy condition has been created from
            RAISERROR(N'specified object set does not match facet the policy condition was created off', -1, -1)
            RETURN(1) -- Failure
        END
    END

    DECLARE @policy_category_id INT
    SET @policy_category_id = NULL
    BEGIN TRANSACTION 

    DECLARE @old_policy_category_id INT
    SELECT @old_policy_category_id = policy_category_id 
        FROM syspolicy_policies 
        WHERE policy_id = @policy_id 

    IF ( (@policy_category IS NOT NULL and @policy_category != '') )
    BEGIN
        IF NOT EXISTS (SELECT * from syspolicy_policy_categories WHERE name = @policy_category)
        BEGIN
            RAISERROR(34015, -1, -1,@policy_category)
            RETURN(1) -- Failure
        END
        ELSE
            SELECT @policy_category_id = policy_category_id FROM msdb.dbo.syspolicy_policy_categories WHERE name = @policy_category
    END

        -- If the caller gave us an empty string for the
        -- @policy_category, then that means to remove the group.
    DECLARE @new_policy_category_id INT
        SELECT  @new_policy_category_id = @old_policy_category_id
    IF ( (@policy_category = '') )
                SELECT @new_policy_category_id = NULL
    ELSE IF (@policy_category_id IS NOT NULL)
                SELECT @new_policy_category_id = @policy_category_id

    UPDATE msdb.dbo.syspolicy_policies_internal
    SET 
        condition_id = ISNULL(@condition_id, condition_id),
        root_condition_id = CASE @root_condition_id WHEN -1 THEN root_condition_id ELSE @root_condition_id END,
        execution_mode = ISNULL(@execution_mode, execution_mode ),
        schedule_uid = @schedule_uid,
        policy_category_id = @new_policy_category_id, 
        description = ISNULL(@description, description),
        help_text = ISNULL(@help_text, help_text),
        help_link = ISNULL(@help_link, help_link),
        is_enabled = ISNULL(@is_enabled, is_enabled),
        object_set_id = CASE @object_set_id WHEN -1 THEN object_set_id ELSE @object_set_id END
    WHERE policy_id = @policy_id

    COMMIT TRANSACTION
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_delete_policy]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_delete_policy] 
@name sysname = NULL,
@policy_id int = NULL
WITH EXECUTE AS OWNER
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_policy_identifiers @name, @policy_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    DELETE msdb.dbo.syspolicy_policies_internal 
        WHERE policy_id = @policy_id

    RETURN (0)
END
GO


IF NOT EXISTS (SELECT * FROM sys.types where name = 'syspolicy_target_filters_type')
BEGIN
    PRINT 'Creating type [dbo].[syspolicy_target_filters_type]...'
    CREATE TYPE [dbo].[syspolicy_target_filters_type]
    AS
    TABLE (
        target_filter_id int,
        policy_id int,
        type sysname NOT NULL,
        filter nvarchar(max) NOT NULL,
        type_skeleton sysname NOT NULL
        )
END
GO

PRINT 'Creating function [dbo].[syspolicy_fn_get_bad_filters]...'
GO

-- This function returns filters that are not supported
-- It is used to prevent unsupported filters from being
-- created. It will only reject well formed filters, in 
-- other words it will not perform a full syntax check.
CREATE FUNCTION [dbo].[syspolicy_fn_get_bad_filters] (
    @inserted [dbo].[syspolicy_target_filters_type] READONLY
)
RETURNS TABLE
AS
    RETURN 
    (
        SELECT filter FROM @inserted 
        WHERE    
            -- do not accept filters for the next level 
            filter LIKE N'Server/%/%\[@%=%\]%' ESCAPE '\' AND 
            -- take out cases when the property contains the pattern
            filter NOT LIKE 'Server/%\[%\[%\]%\]%' ESCAPE '\'
    )
GO

---------------------------------------------------------------
-- Target Set object
---------------------------------------------------------------

IF OBJECT_ID ('[dbo].[syspolicy_target_sets_internal]') IS NULL
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_target_sets_internal]...'
    CREATE TABLE [dbo].[syspolicy_target_sets_internal] (
        target_set_id int NOT NULL IDENTITY(1,1),
        object_set_id int NOT NULL,
        type_skeleton nvarchar(440) NOT NULL,
        type sysname NOT NULL,
        enabled bit NOT NULL,
        -- TODO: Verify if the primary access method of this table is based on policy_id then perhaps the clustered intdex should be on the policy id?
        CONSTRAINT [PK_syspolicy_target_sets] PRIMARY KEY CLUSTERED (target_set_id),
        )
    ALTER TABLE [dbo].[syspolicy_target_sets_internal] 
        ADD CONSTRAINT [FK_syspolicy_target_sets_syspolicy_object_sets] FOREIGN KEY(object_set_id)
        REFERENCES [dbo].[syspolicy_object_sets_internal] (object_set_id)
        ON DELETE CASCADE
    
    CREATE UNIQUE INDEX [UX_syspolicy_target_sets] ON [dbo].[syspolicy_target_sets_internal](object_set_id, type_skeleton)
END
GO

PRINT 'Creating view [dbo].[syspolicy_target_sets]...'
GO
CREATE VIEW [dbo].[syspolicy_target_sets]
AS
    SELECT     
        target_set_id,
        object_set_id,
        type_skeleton,
        type,
        enabled
    FROM [dbo].[syspolicy_target_sets_internal]
GO

IF OBJECT_ID ('[dbo].[syspolicy_target_set_levels_internal]') IS NULL
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_target_set_levels_internal]...'
    CREATE TABLE [dbo].[syspolicy_target_set_levels_internal] (
        target_set_level_id int NOT NULL IDENTITY(1,1),
        target_set_id int NOT NULL,
        type_skeleton nvarchar(440) NOT NULL,
        condition_id int NULL,
        level_name sysname NOT NULL,
        CONSTRAINT [PK_syspolicy_target_set_levels_internal] PRIMARY KEY CLUSTERED (target_set_level_id),
        )
    ALTER TABLE [dbo].[syspolicy_target_set_levels_internal] 
        ADD CONSTRAINT [FK_syspolicy_levels_target_sets] FOREIGN KEY(target_set_id)
        REFERENCES [dbo].[syspolicy_target_sets_internal] (target_set_id)
        ON DELETE CASCADE
    ALTER TABLE [dbo].[syspolicy_target_set_levels_internal] 
        ADD CONSTRAINT [FK_syspolicy_levels_conditions] FOREIGN KEY(condition_id)
        REFERENCES [dbo].[syspolicy_conditions_internal] (condition_id)
    
    CREATE UNIQUE INDEX [UX_syspolicy_levels] ON [dbo].[syspolicy_target_sets_internal](target_set_id, type_skeleton)
END
GO

PRINT 'Creating view [dbo].[syspolicy_target_set_levels]...'
GO
CREATE VIEW [dbo].[syspolicy_target_set_levels]
AS
    SELECT     
        target_set_level_id,
        target_set_id,
        type_skeleton,
        condition_id,
        level_name
    FROM [dbo].[syspolicy_target_set_levels_internal]
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_add_target_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_add_target_set] 
@object_set_id int = NULL,
@object_set_name sysname = NULL,
@type_skeleton nvarchar(max),
@type sysname,
@enabled bit,
@target_set_id int OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT

    EXEC @retval = dbo.sp_syspolicy_verify_object_set_identifiers @name = @object_set_name OUTPUT, @object_set_id = @object_set_id OUTPUT
    if( @retval <> 0)
        RETURN(1)
    
    INSERT INTO msdb.[dbo].[syspolicy_target_sets_internal]
                                        (object_set_id,
                                        type_skeleton,
                                        type,
                                        enabled)
    VALUES                            
                                        (@object_set_id, 
                                        @type_skeleton,
                                        @type,
                                        @enabled)

    SELECT @retval = @@error
    SET @target_set_id = SCOPE_IDENTITY()
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_update_target_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_update_target_set]
@target_set_id int,
@enabled bit
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	UPDATE [msdb].[dbo].[syspolicy_target_sets_internal]
	SET
		enabled = @enabled
	WHERE
		target_set_id = @target_set_id
	
	IF (@@ROWCOUNT = 0)
	BEGIN
		DECLARE @target_set_id_as_char VARCHAR(36)
		SELECT @target_set_id_as_char = CONVERT(VARCHAR(36), @target_set_id)
		RAISERROR(14262, -1, -1, '@target_set_id', @target_set_id_as_char)
		RETURN (1)
	END

    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_delete_target_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_delete_target_set] 
@target_set_id int
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DELETE msdb.[dbo].[syspolicy_target_sets_internal] 
		WHERE target_set_id = @target_set_id
	
	IF (@@ROWCOUNT = 0)
	BEGIN
		DECLARE @target_set_id_as_char VARCHAR(36)
		SELECT @target_set_id_as_char = CONVERT(VARCHAR(36), @target_set_id)
		RAISERROR(14262, -1, -1, '@target_set_id', @target_set_id_as_char)
		RETURN (1)
	END

    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_add_target_set_level]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_add_target_set_level] 
@target_set_id int,
@type_skeleton nvarchar(max),
@condition_id int = NULL,
@condition_name sysname = NULL,
@level_name sysname,
@target_set_level_id int OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT

    IF NOT EXISTS (SELECT * FROM syspolicy_target_sets WHERE target_set_id = @target_set_id)
            RETURN (1)

    IF (@condition_name = '')
        SET @condition_name = NULL

    IF(@condition_id IS NOT NULL or @condition_name IS NOT NULL)
    BEGIN
        EXEC @retval = msdb.dbo.sp_syspolicy_verify_condition_identifiers @condition_name = @condition_name OUTPUT, @condition_id = @condition_id OUTPUT
        IF (@retval <> 0)
            RETURN (1)
    END

    
    INSERT INTO msdb.[dbo].[syspolicy_target_set_levels_internal]
                                        (target_set_id,
                                        type_skeleton,
                                        condition_id,
                                        level_name)
    VALUES                            
                                        (@target_set_id,
                                        @type_skeleton,
                                        @condition_id,
                                        @level_name)

    SELECT @retval = @@error
    SET @target_set_level_id = SCOPE_IDENTITY()
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_update_target_set_level]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_update_target_set_level] 
@target_set_id int,
@type_skeleton nvarchar(max),
@condition_id int = NULL,
@condition_name sysname = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval int

    IF @condition_name = '' SET @condition_name = NULL

    IF(@condition_id IS NOT NULL or @condition_name IS NOT NULL)
    BEGIN
        EXEC @retval = msdb.dbo.sp_syspolicy_verify_condition_identifiers @condition_name = @condition_name OUTPUT, @condition_id = @condition_id OUTPUT
        IF (@retval <> 0)
            RETURN (1)
    END

    UPDATE msdb.[dbo].[syspolicy_target_set_levels_internal] 
        SET condition_id = @condition_id
        WHERE target_set_id = @target_set_id AND type_skeleton = @type_skeleton
    
    IF (@@ROWCOUNT = 0)
    BEGIN
        DECLARE @id nvarchar(max)
        SET @id = '@target_set_id='+LTRIM(STR(@target_set_id))+' @type_skeleton='''+@type_skeleton+''''
        RAISERROR(14262, -1, -1, 'Target Set Level', @id)
        RETURN (1)
    END

    RETURN (0)
END
GO

PRINT 'Creating function [dbo].[syspolicy_fn_eventing_filter]'
GO

CREATE FUNCTION [dbo].[syspolicy_fn_eventing_filter] (@target_set_id INT)
RETURNS INT
AS
BEGIN
    DECLARE @cnt int, @level sysname, @condition_id int, @ret int

    SELECT @cnt = count(*) FROM msdb.dbo.syspolicy_target_set_levels 
        WHERE target_set_id = @target_set_id AND condition_id IS NOT NULL
    IF @cnt = 0 
        RETURN 1
    ELSE IF @cnt > 1
        RETURN 0
    ELSE
        BEGIN
        SELECT @level = level_name, @condition_id = condition_id FROM msdb.dbo.syspolicy_target_set_levels 
            WHERE target_set_id = @target_set_id AND condition_id IS NOT NULL
        IF @level != 'Database'
            RETURN 0

		IF @condition_id IS NOT NULL
			BEGIN
			IF EXISTS (SELECT * FROM msdb.dbo.syspolicy_conditions  
				WHERE condition_id = @condition_id AND   
				(1 = CONVERT(xml, expression).exist('//FunctionType/text()[.="ExecuteSql"]') OR
				1 = CONVERT(xml, expression).exist('//FunctionType/text()[.="ExecuteWql"]') ) )
				RETURN 0
			END

        SELECT @ret = is_name_condition 
        FROM msdb.dbo.syspolicy_conditions    
        WHERE condition_id = @condition_id
        END

    RETURN @ret
END
GO

PRINT 'Creating function [dbo].[syspolicy_fn_filter_complete]'
GO

CREATE FUNCTION [dbo].[syspolicy_fn_filter_complete] (@target_set_id INT)
RETURNS INT
AS
BEGIN
    DECLARE @target_set_skeleton nvarchar(max), @skeleton nvarchar(max), @level sysname, @dummy nvarchar(max), @ret int, 
            @i int, @p int
    
    SELECT @target_set_skeleton = type_skeleton, @i=0, @p=CHARINDEX('/',type_skeleton)
        FROM msdb.dbo.syspolicy_target_sets 
        WHERE target_set_id = @target_set_id

    IF @@ROWCOUNT != 1 RETURN 0
    
    IF @target_set_skeleton = 'Server' RETURN 1    

    -- Count the number of levels in the skeleton past the root
    WHILE (@p <> 0)
        BEGIN
            SET @i = @i + 1
        SET @p = CHARINDEX('/', @target_set_skeleton, @p + 1)
        END

    -- Compare the number of levels in the skeleton with those in TSL
    IF (@i = (SELECT COUNT(*) FROM msdb.dbo.syspolicy_target_set_levels 
             WHERE target_set_id = @target_set_id))
        RETURN 1

    RETURN 0
END
GO

PRINT 'Creating trigger [dbo].[syspolicy_insert_target_set_level_trigger] on [dbo].[syspolicy_target_set_levels_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_insert_target_set_level_trigger] 
    ON [dbo].[syspolicy_target_set_levels_internal]
FOR INSERT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

    DECLARE @update_notifications INT
    DECLARE @update_ddl_trigger   INT

    SET @update_notifications = 0
    SET @update_ddl_trigger = 0

	DECLARE @level sysname
	SET @level = NULL
	
	-- Don't allow setting non-db levels for runtime policies
    SELECT TOP 1 @level = i.level_name
		FROM inserted i
		JOIN dbo.syspolicy_target_sets s ON (i.target_set_id = s.target_set_id)
		JOIN msdb.dbo.syspolicy_object_sets os ON s.object_set_id = os.object_set_id
		JOIN msdb.dbo.syspolicy_policies p ON (os.object_set_id = p.object_set_id)    
		WHERE 1 = dbo.syspolicy_fn_filter_complete (i.target_set_id) AND
			((p.execution_mode & 3) > 0 AND 0 = dbo.syspolicy_fn_eventing_filter (i.target_set_id))
	IF @level IS NOT NULL
    BEGIN
        RAISERROR(34016, -1, -1, @level) 
        ROLLBACK TRANSACTION
    END


    SELECT @update_notifications = SUM (p.execution_mode & 2), @update_ddl_trigger = SUM (p.execution_mode & 1)
        FROM inserted i
        JOIN dbo.syspolicy_target_sets s ON (i.target_set_id = s.target_set_id)
        JOIN msdb.dbo.syspolicy_object_sets_internal os ON (s.object_set_id = os.object_set_id)
        JOIN msdb.dbo.syspolicy_policies p ON (os.object_set_id = p.object_set_id)    
        WHERE 1 = dbo.syspolicy_fn_filter_complete (i.target_set_id) AND
            ((p.execution_mode & 3) > 0 AND p.is_enabled = 1 AND 1 = dbo.syspolicy_fn_eventing_filter (i.target_set_id))

    IF (@update_ddl_trigger > 0)
        EXEC sys.sp_syspolicy_update_ddl_trigger 

    IF    (@update_notifications > 0)    
        EXEC sys.sp_syspolicy_update_event_notification 
END
GO

PRINT 'Creating trigger [dbo].[syspolicy_update_target_set_level_trigger] on [dbo].[syspolicy_target_set_levels_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_update_target_set_level_trigger] ON [dbo].[syspolicy_target_set_levels_internal]
FOR UPDATE
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

    DECLARE @update_notifications INT
    DECLARE @update_ddl_trigger   INT

    SET @update_notifications = 0
    SET @update_ddl_trigger = 0

    IF UPDATE(condition_id)
    BEGIN
		DECLARE @level sysname
		SET @level = NULL
		
		-- Don't allow setting non-db levels for runtime policies
        SELECT TOP 1 @level = i.level_name
			FROM inserted i
			JOIN dbo.syspolicy_target_sets s ON (i.target_set_id = s.target_set_id)
			JOIN msdb.dbo.syspolicy_object_sets os ON s.object_set_id = os.object_set_id
			JOIN msdb.dbo.syspolicy_policies p ON (os.object_set_id = p.object_set_id)    
			WHERE 1 = dbo.syspolicy_fn_filter_complete (i.target_set_id) AND
				((p.execution_mode & 3) > 0 AND 0 = dbo.syspolicy_fn_eventing_filter (i.target_set_id))
		IF @level IS NOT NULL
        BEGIN
            RAISERROR(34016, -1, -1, @level) 
            ROLLBACK TRANSACTION
        END
    END

    SELECT @update_notifications = SUM (p.execution_mode & 2), @update_ddl_trigger = SUM (p.execution_mode & 1)
        FROM inserted i
        JOIN dbo.syspolicy_target_sets s ON (i.target_set_id = s.target_set_id)
        JOIN msdb.dbo.syspolicy_object_sets os ON s.object_set_id = os.object_set_id
        JOIN msdb.dbo.syspolicy_policies p ON (os.object_set_id = p.object_set_id)    
        WHERE 1 = dbo.syspolicy_fn_filter_complete (i.target_set_id) AND
            ((p.execution_mode & 3) > 0 AND p.is_enabled = 1 AND 1 = dbo.syspolicy_fn_eventing_filter (i.target_set_id))

    IF (@update_ddl_trigger > 0)
        EXEC sys.sp_syspolicy_update_ddl_trigger 

    IF    (@update_notifications > 0)    
        EXEC sys.sp_syspolicy_update_event_notification 

END
GO


PRINT 'Creating trigger [dbo].[syspolicy_insert_target_set_trigger] on [dbo].[syspolicy_target_sets_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_insert_target_set_trigger] 
    ON [dbo].[syspolicy_target_sets_internal]
FOR INSERT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

    DECLARE @update_notifications INT
    DECLARE @update_ddl_trigger   INT

    SET @update_notifications = 0
    SET @update_ddl_trigger = 0

    -- Only need to check Server TargetSets, as they don't have levels
    SELECT @update_notifications = SUM (p.execution_mode & 2), @update_ddl_trigger = SUM (p.execution_mode & 1)
        FROM inserted i
        JOIN msdb.dbo.syspolicy_object_sets_internal os ON i.object_set_id = os.object_set_id
        JOIN msdb.dbo.syspolicy_policies p ON (os.object_set_id = p.object_set_id)    
        WHERE i.type = 'SERVER' AND ((p.execution_mode & 3) > 0 AND p.is_enabled = 1)

    IF (@update_ddl_trigger > 0)
        EXEC sys.sp_syspolicy_update_ddl_trigger 

    IF    (@update_notifications > 0)    
        EXEC sys.sp_syspolicy_update_event_notification 
END
GO

PRINT 'Creating trigger [dbo].[syspolicy_delete_target_set_trigger] on [dbo].[syspolicy_target_sets_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_delete_target_set_trigger] ON [dbo].[syspolicy_target_sets_internal]
FOR DELETE
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

    DECLARE @update_notifications INT
    DECLARE @update_ddl_trigger   INT

    SET @update_notifications = 0
    SET @update_ddl_trigger = 0

    -- If this is cascade delete, there will be no policies to join
    SELECT @update_notifications = SUM (p.execution_mode & 2), @update_ddl_trigger = SUM (p.execution_mode & 1)
        FROM deleted d
        JOIN msdb.dbo.syspolicy_object_sets_internal os ON d.object_set_id = os.object_set_id
        JOIN msdb.dbo.syspolicy_policies p ON (os.object_set_id = p.object_set_id)    
        WHERE ((p.execution_mode & 3) > 0 AND p.is_enabled = 1)

    IF (@update_ddl_trigger > 0)
        EXEC sys.sp_syspolicy_update_ddl_trigger 

    IF    (@update_notifications > 0)    
        EXEC sys.sp_syspolicy_update_event_notification 
END
GO


---------------------------------------------------------------
-- Policy category subscription object
---------------------------------------------------------------

IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_policy_category_subscriptions_internal')
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_policy_category_subscriptions_internal]...';
    CREATE TABLE [dbo].[syspolicy_policy_category_subscriptions_internal] (
        policy_category_subscription_id int IDENTITY(1,1),
        target_type sysname NOT NULL,
        target_object sysname NOT NULL, 
        policy_category_id int NOT NULL,
        CONSTRAINT [PK_syspolicy_policy_category_subscriptions] PRIMARY KEY CLUSTERED (policy_category_subscription_id ASC)
        );

    CREATE UNIQUE INDEX [UX_syspolicy_policy_category_subscriptions] ON [dbo].[syspolicy_policy_category_subscriptions_internal](policy_category_id, target_object, target_type)

    ALTER TABLE [dbo].[syspolicy_policy_category_subscriptions_internal] 
        ADD CONSTRAINT [FK_syspolicy_policy_category_subscriptions_syspolicy_policy_categories] FOREIGN KEY(policy_category_id)
        REFERENCES [dbo].[syspolicy_policy_categories_internal] (policy_category_id)
        ON DELETE CASCADE;
END
GO

PRINT 'Creating view [dbo].[syspolicy_policy_category_subscriptions]...'
GO
CREATE VIEW [dbo].[syspolicy_policy_category_subscriptions]
AS
    SELECT     
        policy_category_subscription_id,
        target_type,
        target_object,
        policy_category_id
    FROM [dbo].[syspolicy_policy_category_subscriptions_internal]
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_add_policy_category_subscription]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_add_policy_category_subscription] 
    @target_type sysname,
    @target_object sysname,
    @policy_category sysname,
    @policy_category_subscription_id int = NULL OUTPUT
WITH EXECUTE AS OWNER
AS
BEGIN
    DECLARE @retval int    

 IF(@target_type IS NOT NULL)
	BEGIN
		IF(@target_type <> 'DATABASE')
		BEGIN
			RAISERROR(34018,-1,-1,@target_type);
			RETURN(1)
		END
	END


 IF(NOT EXISTS(SELECT * FROM sys.databases WHERE name=@target_object))
	BEGIN
		RAISERROR(34019,-1,-1,@target_object);
		RETURN(1)
	END

    -- convert category_name into id if needed
    DECLARE @policy_category_id INT
    BEGIN TRANSACTION 
    IF ( (@policy_category IS NOT NULL AND @policy_category != '') )
    BEGIN 
        IF NOT EXISTS (SELECT * from syspolicy_policy_categories WHERE name = @policy_category)
        BEGIN
            INSERT INTO syspolicy_policy_categories_internal(name) VALUES (@policy_category)
            SELECT @policy_category_id = SCOPE_IDENTITY()
        END
        ELSE
            SELECT @policy_category_id = policy_category_id FROM syspolicy_policy_categories WHERE name = @policy_category
    END
    
    INSERT INTO msdb.[dbo].[syspolicy_policy_category_subscriptions_internal]
                                            (target_type, 
                                            target_object,
                                            policy_category_id)
        VALUES                            
                                            (@target_type, 
                                            @target_object, 
                                            @policy_category_id)

    SELECT @retval = @@error
    SET @policy_category_subscription_id = SCOPE_IDENTITY()

    COMMIT TRANSACTION
    RETURN(@retval)

END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_update_policy_category_subscription]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_update_policy_category_subscription] 
@policy_category_subscription_id int,
@target_type sysname = NULL,
@target_object sysname = NULL,
@policy_category sysname = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	-- Turn [nullable] empty string parameters into NULLs
	IF @target_type = ''      SELECT @target_type = NULL
	IF @target_object = ''    SELECT @target_object = NULL
	IF @policy_category = ''  SELECT @policy_category = NULL

    IF(@target_type IS NOT NULL)
	BEGIN
		IF(LOWER(@target_type) <> 'database')
		BEGIN
			RAISERROR(34018,-1,-1,@target_type);
			RETURN(1)
		END
	END
	
	IF(@target_object IS NOT NULL AND NOT EXISTS(SELECT * FROM sys.databases WHERE name=@target_object))
	BEGIN
		RAISERROR(34019,-1,-1,@target_object);
		RETURN(1)
	END

    DECLARE @policy_category_id INT
    SET @policy_category_id = NULL
    BEGIN TRANSACTION 

    DECLARE @old_policy_category_id INT
    SET @old_policy_category_id = NULL

    IF ( (@policy_category IS NOT NULL) )
    BEGIN 
        IF NOT EXISTS (SELECT * from syspolicy_policy_categories WHERE name = @policy_category)
        BEGIN
            -- add a new policy category
            INSERT INTO syspolicy_policy_categories_internal(name) VALUES (@policy_category)
            SELECT @policy_category_id = SCOPE_IDENTITY()

            SELECT @old_policy_category_id = policy_category_id 
                FROM syspolicy_policy_category_subscriptions 
                WHERE policy_category_subscription_id = @policy_category_subscription_id 
        END
        ELSE
            SELECT @policy_category_id = policy_category_id FROM syspolicy_policy_categories WHERE name = @policy_category
    END
    
    DECLARE @group_usage_count INT
    SELECT @group_usage_count = COUNT(*) 
        FROM syspolicy_policy_category_subscriptions  
        WHERE policy_category_id = @old_policy_category_id

    SELECT @group_usage_count = @group_usage_count + COUNT(*) 
        FROM syspolicy_policies  
        WHERE policy_category_id = @old_policy_category_id

    UPDATE msdb.[dbo].[syspolicy_policy_category_subscriptions_internal] 
        SET
            target_type            = ISNULL(@target_type, target_type),
            target_object       = ISNULL(@target_object, target_object),
            policy_category_id        = ISNULL(@policy_category_id, policy_category_id)
        WHERE policy_category_subscription_id = @policy_category_subscription_id

    IF (@@ROWCOUNT = 0)
    BEGIN
        DECLARE @policy_category_subscription_id_as_char VARCHAR(36)
        SELECT @policy_category_subscription_id_as_char = CONVERT(VARCHAR(36), @policy_category_subscription_id)
        RAISERROR(14262, -1, -1, '@policy_category_subscription_id', @policy_category_subscription_id_as_char)
        ROLLBACK TRANSACTION
        RETURN(1) -- Failure
    END

    -- delete the old entry if it was used only by this policy
    DELETE syspolicy_policy_categories_internal WHERE 
        policy_category_id = @old_policy_category_id
        AND 1 = @group_usage_count

    COMMIT TRANSACTION
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_delete_policy_category_subscription]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_delete_policy_category_subscription] 
@policy_category_subscription_id int
WITH EXECUTE AS OWNER
AS
BEGIN
    DECLARE @old_policy_category_id INT
    SELECT @old_policy_category_id = policy_category_id 
        FROM dbo.syspolicy_policy_category_subscriptions 
        WHERE policy_category_subscription_id = @policy_category_subscription_id

    DECLARE @group_usage_count INT
    SELECT @group_usage_count = COUNT(name) 
        FROM syspolicy_policies pd 
        WHERE pd.policy_category_id = @old_policy_category_id

    DECLARE @subscription_group_usage_count INT
    SELECT @subscription_group_usage_count = COUNT(*) 
        FROM syspolicy_policy_category_subscriptions  
        WHERE policy_category_id = @old_policy_category_id

    SELECT @group_usage_count = @group_usage_count + @subscription_group_usage_count

    DELETE msdb.dbo.syspolicy_policy_category_subscriptions_internal 
        WHERE policy_category_subscription_id = @policy_category_subscription_id

    IF (@@ROWCOUNT = 0)
    BEGIN
        DECLARE @policy_category_subscription_id_as_char VARCHAR(36)
        SELECT @policy_category_subscription_id_as_char = CONVERT(VARCHAR(36), @policy_category_subscription_id)
        RAISERROR(14262, -1, -1, '@policy_category_subscription_id', @policy_category_subscription_id_as_char)
        RETURN(1) -- Failure
    END

    RETURN (0)
END
GO

GO
IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_system_health_state_internal')
BEGIN
	PRINT 'Creating table [dbo].[syspolicy_system_health_state_internal]...'
	CREATE TABLE [dbo].[syspolicy_system_health_state_internal](
			health_state_id bigint IDENTITY PRIMARY KEY CLUSTERED,
			policy_id int NOT NULL REFERENCES [dbo].[syspolicy_policies_internal],
			last_run_date datetime NOT NULL, 
			target_query_expression_with_id nvarchar(400) NOT NULL, 
			target_query_expression nvarchar(max) NOT NULL, 
			result bit NOT NULL);
	CREATE INDEX IX_syspolicy_system_health_state_internal_policy_id ON 
		[dbo].[syspolicy_system_health_state_internal](policy_id);
	CREATE INDEX IX_syspolicy_system_health_state_internal_target_query_expression_with_id ON
		[dbo].[syspolicy_system_health_state_internal](target_query_expression_with_id);
END
GO

CREATE VIEW [dbo].[syspolicy_system_health_state]
AS
    SELECT 
        health_state_id,
        policy_id,
        last_run_date,
        target_query_expression_with_id,
        target_query_expression,
        result
    FROM [dbo].[syspolicy_system_health_state_internal]
GO

IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_policy_execution_history_internal')
BEGIN
	PRINT 'Creating table [dbo].[syspolicy_policy_execution_history_internal]...';
	CREATE TABLE syspolicy_policy_execution_history_internal (
		history_id bigint IDENTITY PRIMARY KEY CLUSTERED,
		policy_id int NOT NULL REFERENCES syspolicy_policies_internal,
		start_date datetime NOT NULL DEFAULT (GETDATE()),
		end_date datetime NULL,
		result bit NOT NULL DEFAULT (0),
		is_full_run bit NOT NULL DEFAULT (1),
		exception_message nvarchar(max) NULL,
		exception nvarchar(max) NULL
	);
	CREATE INDEX IX_syspolicy_policy_execution_history_internal_end_date_policy_id 
		ON [dbo].[syspolicy_policy_execution_history_internal](policy_id, end_date);
	CREATE INDEX IX_syspolicy_policy_execution_history_internal_policy_id 
		ON [dbo].[syspolicy_policy_execution_history_internal](policy_id);
END

PRINT 'Creating trigger [syspolicy_update_system_health_state]...'
GO
CREATE TRIGGER [syspolicy_update_system_health_state] ON [dbo].[syspolicy_policy_execution_history_internal]
AFTER UPDATE
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

    -- if the entire policy has been checked delete all entries 
    -- regarding that policy
    DELETE FROM [dbo].[syspolicy_system_health_state_internal] 
        WHERE policy_id in (SELECT policy_id FROM inserted WHERE is_full_run = 1)

    -- Note: in the queries below new records are added only for
    -- policies that are enabled for automation

    -- if the policy is evaluated against a single target
    -- delete the old entry
    DELETE FROM [dbo].[syspolicy_system_health_state_internal] 
        WHERE policy_id in 
            (SELECT i.policy_id FROM inserted i WHERE i.is_full_run = 0) AND
                target_query_expression_with_id in (SELECT target_query_expression_with_id 
                                                    FROM [dbo].[syspolicy_policy_execution_history_details_internal] d 
                                                    INNER JOIN inserted i2 ON i2.history_id = d.history_id 
                                                    WHERE i2.is_full_run = 0)


    -- insert the detail rows, but only for failures
    -- this is done both for the full runs and for the partial runs
    -- we will not insert anything if this is a ghost record, i.e. 
    -- target_query_expression_with_id is null 
    -- this will happen when we log prevent policies
    INSERT INTO [dbo].[syspolicy_system_health_state_internal] 
        (policy_id, last_run_date, target_query_expression_with_id, target_query_expression, result)
    SELECT i.policy_id, d.execution_date, d.target_query_expression_with_id, d.target_query_expression, d.result
        FROM inserted i 
        INNER JOIN [dbo].[syspolicy_policy_execution_history_details_internal] d on i.history_id = d.history_id
        INNER JOIN [dbo].[syspolicy_policies] p on i.policy_id = p.policy_id
        WHERE d.result = 0 AND p.is_enabled = 1 AND d.target_query_expression_with_id != N''
        
    -- delete all the success detail rows with no expression
    -- these are rows inserted so that we can update the system health state
    -- make an exception if the global switch says we should keep those records
    IF( 0 = (SELECT ISNULL(convert(bit, current_value), 0) FROM msdb.dbo.syspolicy_configuration WHERE name = 'LogOnSuccess'))
    BEGIN
        DELETE FROM d
        FROM [dbo].[syspolicy_policy_execution_history_details_internal] d
        INNER JOIN inserted i ON i.history_id = d.history_id
            WHERE d.result_detail = N''
    END
END
GO

CREATE VIEW [dbo].[syspolicy_policy_execution_history]
AS
    SELECT 
        history_id,
        policy_id,
        start_date,
        end_date,
        result,
        exception_message,
        exception
    FROM [dbo].[syspolicy_policy_execution_history_internal]
GO

IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_policy_execution_history_details_internal')
BEGIN
	PRINT 'Creating table [dbo].[syspolicy_policy_execution_history_details_internal]...';
	CREATE TABLE syspolicy_policy_execution_history_details_internal (
	detail_id bigint IDENTITY PRIMARY KEY CLUSTERED,
	history_id bigint NOT NULL REFERENCES syspolicy_policy_execution_history_internal ON DELETE CASCADE,
	target_query_expression nvarchar(4000) NOT NULL,
	target_query_expression_with_id nvarchar(4000) NOT NULL,
	execution_date datetime NOT NULL DEFAULT (GETDATE()),
	result bit NOT NULL,
	result_detail nvarchar(max) NULL, 
	exception_message nvarchar(max) NULL,
	exception nvarchar(max) NULL
	);
	CREATE INDEX IX_syspolicy_policy_execution_history_details_internal_result ON
		[dbo].[syspolicy_policy_execution_history_details_internal](result);
END
GO

CREATE VIEW [dbo].[syspolicy_policy_execution_history_details]
AS
    SELECT 
        detail_id,
        history_id,
        target_query_expression,
        execution_date,
        result,
        result_detail,
        exception_message,
        exception
    FROM [dbo].[syspolicy_policy_execution_history_details_internal]
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_log_policy_execution_start]...'
GO
CREATE PROC [dbo].[sp_syspolicy_log_policy_execution_start] 
    @policy_id int,
    @is_full_run bit,
    @history_id bigint OUTPUT 
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole', 0
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END
    DECLARE @ret int

    SET @history_id = 0

    EXEC @ret = dbo.sp_syspolicy_verify_policy_identifiers NULL, @policy_id
    IF @ret <> 0 RETURN -1

    INSERT syspolicy_policy_execution_history_internal (policy_id, is_full_run) VALUES (@policy_id, @is_full_run) 
    SET @history_id = SCOPE_IDENTITY ()
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_log_policy_execution_end]...'
GO
CREATE PROC [dbo].[sp_syspolicy_log_policy_execution_end] 
    @history_id bigint, 
    @result bit,
    @exception_message nvarchar(max) = NULL,
    @exception nvarchar(max) = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole', 0
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    UPDATE syspolicy_policy_execution_history_internal 
      SET result = @result,
          end_date = GETDATE(),
          exception_message = @exception_message,
          exception = @exception
      WHERE history_id = @history_id
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_log_policy_execution_detail]...'
GO
CREATE PROC [dbo].[sp_syspolicy_log_policy_execution_detail] 
 @history_id bigint, 
 @target_query_expression nvarchar(4000), 
 @target_query_expression_with_id nvarchar(4000), 
 @result bit, 
 @result_detail nvarchar(max),
 @exception_message nvarchar(max) = NULL,
 @exception nvarchar(max) = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole', 0
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END
    BEGIN TRANSACTION
    DECLARE @is_valid_entry INT
    -- take an update lock on this table first to prevent deadlock
    SELECT @is_valid_entry = count(*) FROM syspolicy_policy_execution_history_internal
        WITH (UPDLOCK) 
        WHERE history_id = @history_id

    INSERT syspolicy_policy_execution_history_details_internal (
                                history_id, 
                                target_query_expression, 
                                target_query_expression_with_id, 
                                result, 
                                result_detail,
                                exception_message,
                                exception) 
                        VALUES (
                                @history_id, 
                                @target_query_expression, 
                                @target_query_expression_with_id, 
                                @result, 
                                @result_detail,
                                @exception_message,
                                @exception) 
    IF( @@TRANCOUNT > 0)
        COMMIT
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_delete_policy_execution_history]...'
GO
CREATE PROC [dbo].[sp_syspolicy_delete_policy_execution_history] 
 @policy_id int,
 @oldest_date datetime
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    IF @oldest_date IS NULL
        BEGIN
        IF (@policy_id IS NULL)
            DELETE syspolicy_policy_execution_history_internal
        ELSE
            DELETE syspolicy_policy_execution_history_internal WHERE policy_id = @policy_id
        END
    ELSE
        BEGIN
        IF (@policy_id IS NULL)
            DELETE syspolicy_policy_execution_history_internal WHERE start_date < @oldest_date
        ELSE
            DELETE syspolicy_policy_execution_history_internal WHERE policy_id = @policy_id AND start_date < @oldest_date
        END
END
GO




-----------------------------------------------------------
-- event processing
-----------------------------------------------------------

PRINT 'Creating function [dbo].[syspolicy_fn_get_type_name]...'
GO
CREATE FUNCTION [dbo].[syspolicy_fn_get_type_name](@event_type_name sysname)
RETURNS sysname
AS
BEGIN
    RETURN 
    (CASE LOWER(@event_type_name)
        WHEN 'procedure' THEN 'StoredProcedure'
        WHEN 'function' THEN 'UserDefinedFunction'
        WHEN 'type' THEN 'UserDefinedType'
        WHEN 'sql user' THEN 'User'
        WHEN 'certificate user' THEN 'User'
        WHEN 'asymmetric key user' THEN 'User'
        WHEN 'windows user' THEN 'User'
        WHEN 'group user' THEN 'User'
        WHEN 'application role' THEN 'ApplicationRole'
        ELSE UPPER(SUBSTRING(@event_type_name, 1,1)) + LOWER(SUBSTRING(@event_type_name, 2,LEN(@event_type_name)))
    END)
END
GO

IF OBJECT_ID ('[dbo].[syspolicy_execution_internal]') IS NULL
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_execution_internal]...'
    CREATE TABLE [dbo].[syspolicy_execution_internal] (
        policy_id int,
        synchronous bit,
        event_data xml)
END
GO

IF OBJECT_ID ('[dbo].[syspolicy_execution_trigger]') IS NOT NULL
BEGIN
    DROP TRIGGER [dbo].[syspolicy_execution_trigger] 
END
GO
CREATE TRIGGER [dbo].[syspolicy_execution_trigger] ON [dbo].[syspolicy_execution_internal]
INSTEAD OF INSERT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

	IF NOT EXISTS (SELECT * FROM inserted)
		RETURN

    DECLARE @policy_id int
    DECLARE @synchronous bit
    DECLARE @event_data xml
    DECLARE affected_policies CURSOR LOCAL FOR 
        SELECT policy_id, synchronous, event_data FROM inserted
    
    OPEN affected_policies
    FETCH NEXT FROM affected_policies INTO @policy_id, @synchronous, @event_data
    DECLARE @err int
    SET @err = 0
    WHILE (@@FETCH_STATUS = 0 AND (@synchronous = 0 OR @err = 0)) 
    BEGIN
        DECLARE @pol_name sysname
        SELECT @pol_name = p.name
            FROM dbo.syspolicy_policies p 
            WHERE p.policy_id = @policy_id

        IF (@synchronous = 0)
        BEGIN
            -- trace what policy is processing this event
            DECLARE @msg nvarchar(1000)
            SET @msg = N'Policy ''' + @pol_name + ''' was activated by an event.'
            RAISERROR(@msg, 1, 1) with log
        END

        -- execute the policy
        EXEC @err = msdb.sys.sp_syspolicy_execute_policy @policy_name =@pol_name, @event_data = @event_data, @synchronous = @synchronous
    
        -- move to the next policy if we're checking the policy
        -- or if we are in enforce mode and we haven't failed
        IF( @synchronous = 0 OR @err = 0)
            FETCH NEXT FROM affected_policies INTO @policy_id, @synchronous, @event_data
    END

    CLOSE affected_policies
    DEALLOCATE affected_policies

END
GO
    

PRINT 'Creating procedure [dbo].[sp_syspolicy_dispatch_event]...'
GO
-- These settings are necessary to read XML.
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
SET ARITHABORT ON
SET CONCAT_NULL_YIELDS_NULL ON
SET NUMERIC_ROUNDABORT OFF
SET QUOTED_IDENTIFIER ON
GO

-- procedure that processes an event and decides 
-- what binding should handle it
CREATE PROCEDURE [dbo].[sp_syspolicy_dispatch_event]  @event_data xml, @synchronous bit
AS
BEGIN
	-- disable these as the caller may not have SHOWPLAN permission
	SET STATISTICS XML OFF
	SET STATISTICS PROFILE OFF

	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	IF ( @synchronous = 0)
		PRINT CONVERT(nvarchar(max), @event_data)
	DECLARE @event_type sysname
	DECLARE @object_type sysname
	DECLARE @database sysname
	DECLARE @mode int
	DECLARE @filter_expression nvarchar(4000)
	DECLARE @filter_expression_skeleton nvarchar(4000)

    SET @mode = (case @synchronous when 1 then 1 else 2 end)

    -- These settings are necessary to read XML.
    SET ANSI_NULLS ON
    SET ANSI_PADDING ON
    SET ANSI_WARNINGS ON
    SET ARITHABORT ON
    SET CONCAT_NULL_YIELDS_NULL ON
    SET NUMERIC_ROUNDABORT OFF
    SET QUOTED_IDENTIFIER ON

    SET NOCOUNT ON 

    SELECT 
        @event_type = T.c.value('(EventType/text())[1]', 'sysname')
        , @database = T.c.value('(DatabaseName/text())[1]', 'sysname')
        , @object_type = T.c.value('(ObjectType/text())[1]', 'sysname')
    FROM   @event_data.nodes('/EVENT_INSTANCE') T(c)
    
    -- we are going to ignore events that affect subobjects
    IF  (@event_type = N'ALTER_DATABASE' AND 
        1 = @event_data.exist('EVENT_INSTANCE/AlterDatabaseActionList')) OR
        (@event_type = N'ALTER_TABLE' AND 
        1 = @event_data.exist('EVENT_INSTANCE/AlterTableActionList'))
    BEGIN
        RETURN;
    END

    -- convert trace numerical objecttypes to string
    IF (ISNUMERIC(@object_type) = 1)
        select @object_type = name from master.dbo.spt_values where type = 'EOB' and number = @object_type

    -- these events do not have ObjectType and ObjectName
    IF ((@object_type IS NULL) AND @event_type IN ('CREATE_DATABASE', 'DROP_DATABASE', 'ALTER_DATABASE'))
    BEGIN
        SET @object_type = 'DATABASE'
    END

    INSERT msdb.dbo.syspolicy_execution_internal
        SELECT p.policy_id , @synchronous, @event_data
        FROM dbo.syspolicy_policies p
        INNER JOIN dbo.syspolicy_conditions_internal c ON c.condition_id = p.condition_id
        INNER JOIN dbo.syspolicy_facet_events fe ON c.facet_id = fe.management_facet_id
        INNER JOIN dbo.syspolicy_target_sets ts ON ts.object_set_id = p.object_set_id AND ts.type = fe.target_type
        LEFT JOIN dbo.syspolicy_policy_category_subscriptions pgs ON pgs.policy_category_id = p.policy_category_id
        LEFT JOIN dbo.syspolicy_target_set_levels tsl on tsl.target_set_id = ts.target_set_id AND tsl.level_name = 'Database'
        LEFT JOIN dbo.syspolicy_conditions_internal lc ON lc.condition_id = tsl.condition_id
        LEFT JOIN dbo.syspolicy_policy_categories cat on p.policy_category_id = cat.policy_category_id
        WHERE fe.event_name=@event_type AND 
            p.is_enabled = 1 AND
            fe.target_type_alias = @object_type AND
            -- 1 means Enforce, 2 means CheckOnChange
            (p.execution_mode & @mode) = @mode AND
            ((p.policy_category_id IS NULL) OR (cat.mandate_database_subscriptions = 1) OR ( ts.type_skeleton NOT LIKE 'Server/Database%') OR (@database IS NOT NULL AND pgs.target_object = @database)) AND
            ((@database IS NULL) OR 
             (@database IS NOT NULL AND 
              (tsl.condition_id IS NULL OR 
               (tsl.condition_id IS NOT NULL AND 
                ((lc.is_name_condition=1 AND @database = lc.obj_name) OR
                 (lc.is_name_condition=2 AND @database LIKE lc.obj_name) OR
                 (lc.is_name_condition=3 AND @database != lc.obj_name) OR
                 (lc.is_name_condition=4 AND @database NOT LIKE lc.obj_name))
               )
              )
             )
            ) 

    -- NOTE: if we haven't subscribed via an Endpoint facet on those events 
    -- we know for sure they will not be processed by the ServerAreaFacet policies 
    -- because syspolicy_facet_events expects @target_type to be SERVER
    -- so the filter will leave them out, and we are going to generate a fake 
    -- event to make those policies run
    IF( @synchronous = 0 AND 
        (@event_type IN ('ALTER_ENDPOINT', 'CREATE_ENDPOINT', 'DROP_ENDPOINT')))
    BEGIN
        DECLARE @fake_event_data xml
        SET @fake_event_data = CONVERT(xml, '<EVENT_INSTANCE><EventType>SAC_ENDPOINT_CHANGE</EventType><ObjectType>21075</ObjectType><ObjectName/><DatabaseName>master</DatabaseName></EVENT_INSTANCE>')
        
        EXEC [dbo].[sp_syspolicy_dispatch_event]  @event_data = @fake_event_data, @synchronous = 0
    END
            
END              
GO 

/* 
 * Asynchronous events collection via event notifications
 */

PRINT N'Dropping service [syspolicy_event_listener]...'
GO
IF EXISTS (SELECT * FROM sys.services WHERE name = N'syspolicy_event_listener')
    DROP SERVICE [syspolicy_event_listener]
GO
PRINT N'Dropping queue [syspolicy_event_queue]...'
GO
IF EXISTS (SELECT * FROM sys.service_queues WHERE name = N'syspolicy_event_queue')
    DROP QUEUE [syspolicy_event_queue]
GO
PRINT N'Creating queue [syspolicy_event_queue]...'
GO
CREATE QUEUE [syspolicy_event_queue]
GO

PRINT N'Creating service [syspolicy_event_listener]...'
GO
CREATE SERVICE [syspolicy_event_listener]
 ON QUEUE  [syspolicy_event_queue]
    ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]);
GO

IF EXISTS (SELECT * FROM sys.procedures WHERE name = N'sp_syspolicy_events_reader')
    DROP PROCEDURE [sp_syspolicy_events_reader] 
GO
PRINT N'Creating procedure [sp_syspolicy_events_reader]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_events_reader] 
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @dh uniqueidentifier;
	DECLARE @mt sysname;
	DECLARE @body varbinary(max);
	DECLARE @msg nvarchar(max)

    BEGIN TRANSACTION;
    WAITFOR (RECEIVE TOP (1)
        @dh = conversation_handle,
        @mt = message_type_name,
        @body = message_body
        FROM [syspolicy_event_queue]), timeout 5000;
    WHILE (@dh is not null)
    BEGIN
        IF (@mt = N'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
        BEGIN
            -- @body contains the error
            DECLARE @bodyStr nvarchar(max)
            SET @bodyStr = convert(nvarchar(max), @body)
            RAISERROR (34001, 1,1, @bodyStr) with log;
            END CONVERSATION @dh;
        END
        IF (@mt = N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
        BEGIN
            RAISERROR (34002, 1,1) with log;
            END CONVERSATION @dh;
        END
        IF (@mt = N'http://schemas.microsoft.com/SQL/Notifications/EventNotification')
        BEGIN
            -- process the event
            BEGIN TRY
                EXEC [dbo].[sp_syspolicy_dispatch_event]  @event_data = @body, @synchronous = 0
            END TRY
            BEGIN CATCH
                -- report the error

                DECLARE @errorNumber int
                DECLARE @errorMessage nvarchar(max)
                SET @errorNumber = ERROR_NUMBER()
                SET @errorMessage = ERROR_MESSAGE()

                RAISERROR (34003, 1,1, @errorNumber, @errorMessage ) with log;
            END CATCH
        END
        -- every message is handled in its own transaction
        COMMIT TRANSACTION;
        SELECT @dh = null;
        BEGIN TRANSACTION;
        WAITFOR (RECEIVE TOP (1)
            @dh = conversation_handle,
            @mt = message_type_name,
            @body = message_body
            FROM [syspolicy_event_queue]), TIMEOUT 5000;
    END
    COMMIT;
END
GO

IF OBJECT_ID('[dbo].[syspolicy_configuration_internal]') IS NULL
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_configuration_internal]...'
    CREATE TABLE [dbo].[syspolicy_configuration_internal] (
        name sysname PRIMARY KEY CLUSTERED NOT NULL,
        current_value sql_variant NOT NULL);
        
    INSERT INTO [dbo].[syspolicy_configuration_internal] (name, current_value)
    VALUES (N'Enabled', 1);
    
    INSERT INTO [dbo].[syspolicy_configuration_internal] (name, current_value)
    VALUES (N'HistoryRetentionInDays', 0);
    
    INSERT INTO [dbo].[syspolicy_configuration_internal] (name, current_value)
    VALUES (N'LogOnSuccess', 0);
END
GO


PRINT 'Creating view [dbo].[syspolicy_configuration] ...'
GO
CREATE VIEW [dbo].[syspolicy_configuration]
AS
    SELECT 
        name,
        CASE WHEN name = N'Enabled' and SERVERPROPERTY('EngineEdition') = 4 THEN 0 ELSE current_value END AS current_value
    FROM [dbo].[syspolicy_configuration_internal] 
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_set_config_history_retention] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_set_config_history_retention] 
	@value sql_variant
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    UPDATE [msdb].[dbo].[syspolicy_configuration_internal]
        SET current_value = @value
        WHERE name = N'HistoryRetentionInDays';
    
END
GO
PRINT 'Creating procedure [dbo].[sp_syspolicy_set_log_on_success] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_set_log_on_success] 
	@value sql_variant
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    UPDATE [msdb].[dbo].[syspolicy_configuration_internal]
        SET current_value = @value
        WHERE name = N'LogOnSuccess';
    
END
GO
PRINT 'Creating procedure [dbo].[sp_syspolicy_set_config_enabled] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_set_config_enabled] 
	@value sql_variant
AS
BEGIN
	DECLARE @retval_check int;
	
	SET NOCOUNT ON
	
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END
    
    DECLARE @val bit;
    SET @val = CONVERT(bit, @value);
    IF (@val = 1)
    BEGIN
	    DECLARE @engine_edition INT
		SELECT @engine_edition = CAST(SERVERPROPERTY('EngineEdition') AS INT)
		IF @engine_edition = 4 -- All SQL Express types + embedded
		BEGIN
			RAISERROR (34054, 16, 1)			
            RETURN 34054
		END
		
	    UPDATE [msdb].[dbo].[syspolicy_configuration_internal]
	    SET current_value = @value
	    WHERE name = N'Enabled';
    
        -- enable policy automation
        ALTER QUEUE [syspolicy_event_queue] WITH ACTIVATION (STATUS = ON)

        EXEC sys.sp_syspolicy_update_ddl_trigger;
        EXEC sys.sp_syspolicy_update_event_notification;
    END
    ELSE
    BEGIN
	    UPDATE [msdb].[dbo].[syspolicy_configuration_internal]
	    SET current_value = @value
	    WHERE name = N'Enabled';

        -- disable policy automation
        ALTER QUEUE [syspolicy_event_queue] WITH ACTIVATION (STATUS = OFF)

        IF EXISTS (SELECT * FROM sys.server_event_notifications WHERE name = N'syspolicy_event_notification')
	        DROP EVENT NOTIFICATION [syspolicy_event_notification] ON SERVER 

        IF EXISTS (SELECT * FROM sys.server_triggers WHERE name = N'syspolicy_server_trigger')
            DISABLE TRIGGER [syspolicy_server_trigger] ON ALL SERVER 
    END

END
GO
PRINT 'Creating procedure [dbo].[sp_syspolicy_configure] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_configure]
    @name sysname,
    @value sql_variant
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF (0 != @retval_check)
	BEGIN
		RETURN @retval_check
	END

    DECLARE @value_type sysname;
    
    IF (@name=N'Enabled')
    BEGIN
        SET @value_type = CONVERT(sysname, SQL_VARIANT_PROPERTY(@value, 'BaseType'));
        IF (@value_type != 'int')
        BEGIN
            RAISERROR (34021, -1, -1, @name, @value_type);
            RETURN 34021;
        END
       
        EXEC msdb.[dbo].[sp_syspolicy_set_config_enabled] @value;
    END
    ELSE 
    IF (@name = N'HistoryRetentionInDays')
    BEGIN
        SET @value_type = CONVERT(sysname, SQL_VARIANT_PROPERTY(@value, 'BaseType'));
        IF (@value_type != 'int')
        BEGIN
            RAISERROR (34021, -1, -1, @name, @value_type);
            RETURN 34021;
        END
        
        EXEC msdb.[dbo].[sp_syspolicy_set_config_history_retention] @value;
    END
    ELSE
    IF (@name=N'LogOnSuccess')
    BEGIN
        SET @value_type = CONVERT(sysname, SQL_VARIANT_PROPERTY(@value, 'BaseType'));
        IF (@value_type != 'int')
        BEGIN
            RAISERROR (34021, -1, -1, @name, @value_type);
            RETURN 34021;
        END
       
        EXEC msdb.[dbo].[sp_syspolicy_set_log_on_success] @value;
    END
    ELSE 
    BEGIN
        RAISERROR(34020, -1, -1, @name);
        RETURN 34020;
    END
    
    RETURN 0;
END
GO
PRINT 'Creating function [dbo].[fn_syspolicy_is_automation_enabled] ...'
GO
CREATE FUNCTION fn_syspolicy_is_automation_enabled()
RETURNS bit
AS
BEGIN
    DECLARE @ret bit;
    SELECT @ret = CONVERT(bit, current_value)
        FROM msdb.dbo.syspolicy_configuration 
        WHERE name = 'Enabled' 

    RETURN @ret;
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_repair_policy_automation] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_repair_policy_automation]
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole';
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check;
	END

    BEGIN TRANSACTION;

    BEGIN TRY
        SET NOCOUNT ON
        
        DECLARE @policies_copy TABLE (
                            policy_id int ,
                            name sysname NOT NULL,
                            condition_id int NOT NULL,
                            root_condition_id int NULL,
                            execution_mode int NOT NULL,
                            policy_category_id int NULL,
                            schedule_uid uniqueidentifier NULL,
                            description nvarchar(max) NOT NULL ,
                            help_text nvarchar(4000) NOT NULL ,
                            help_link nvarchar(2083) NOT NULL ,
                            object_set_id INT NULL,
                            is_enabled bit default 0 NOT NULL);

        INSERT INTO @policies_copy 
            SELECT          policy_id,
                            name, 
                            condition_id, 
                            root_condition_id, 
                            execution_mode, 
                            policy_category_id, 
                            schedule_uid, 
                            description,
                            help_text,
                            help_link,
                            object_set_id,
                            is_enabled
            FROM msdb.dbo.syspolicy_policies_internal;
        
        DELETE FROM syspolicy_policies_internal;
        
        SET IDENTITY_INSERT msdb.dbo.syspolicy_policies_internal ON
        INSERT INTO msdb.dbo.syspolicy_policies_internal (
                            policy_id,
                            name, 
                            condition_id, 
                            root_condition_id, 
                            execution_mode, 
                            policy_category_id, 
                            schedule_uid, 
                            description,
                            help_text,
                            help_link,
                            object_set_id,
                            is_enabled)
            SELECT 
                            policy_id,
                            name, 
                            condition_id, 
                            root_condition_id, 
                            execution_mode, 
                            policy_category_id, 
                            schedule_uid, 
                            description,
                            help_text,
                            help_link,
                            object_set_id,
                            is_enabled
            FROM @policies_copy;
            
        SET IDENTITY_INSERT msdb.dbo.syspolicy_policies_internal OFF;
        
        SET NOCOUNT OFF;
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION;
        RAISERROR (14351, -1, -1);
        RETURN 1;
    END CATCH

    -- commit the transaction we started
    COMMIT TRANSACTION;
    
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_purge_history] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_purge_history]
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole';
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check;
	END

    DECLARE @retention_interval_in_days_variant sql_variant
    SET @retention_interval_in_days_variant = (SELECT current_value 
                                        FROM msdb.dbo.syspolicy_configuration
                                        WHERE name = N'HistoryRetentionInDays');
                                        
    DECLARE @retention_interval_in_days int;
    SET @retention_interval_in_days = CONVERT(int, @retention_interval_in_days_variant);
    
    IF( @retention_interval_in_days <= 0)
        RETURN 0;
	
	DECLARE @cutoff_date datetime;
	SET @cutoff_date = DATEADD(day, -@retention_interval_in_days, GETDATE());

    -- delete old policy history records
    BEGIN TRANSACTION
    
    DELETE d 
    FROM msdb.dbo.syspolicy_policy_execution_history_details_internal d
    INNER JOIN msdb.dbo.syspolicy_policy_execution_history_internal h ON d.history_id = h.history_id
    WHERE h.end_date < @cutoff_date
    
    DELETE h
    FROM msdb.dbo.syspolicy_policy_execution_history_internal h
    WHERE h.end_date < @cutoff_date
    
    COMMIT TRANSACTION
    
    -- delete policy subscriptions that refer to the nonexistent databases
    DELETE s
    FROM msdb.dbo.syspolicy_policy_category_subscriptions_internal s
    LEFT OUTER JOIN master.sys.databases d ON s.target_object = d.name
    WHERE s.target_type = 'DATABASE' AND d.database_id IS NULL
    
    RETURN 0;
END
GO
PRINT 'Creating procedure [dbo].[sp_syspolicy_create_purge_job] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_create_purge_job]
AS
BEGIN
DECLARE @retval_check int;
EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole';
IF ( 0!= @retval_check)
BEGIN
	RETURN @retval_check;
END

-- create a policy history retention maintenance job
-- first check if this job already exists 
IF EXISTS (SELECT * 
            FROM msdb.dbo.syspolicy_configuration c
            WHERE c.name = 'PurgeHistoryJobGuid')
BEGIN
    RETURN;
END

BEGIN TRANSACTION;
DECLARE @ReturnCode INT;
SELECT @ReturnCode = 0;
DECLARE @job_name sysname;
-- create unique job name
SET @job_name = N'syspolicy_purge_history';
WHILE (EXISTS (SELECT * FROM msdb..sysjobs WHERE name = @job_name))
BEGIN
	SET @job_name = N'syspolicy_purge_history_' + RIGHT(STR(FLOOR(RAND() * 100000000)),8);
END

DECLARE @sa_account_name sysname
SET @sa_account_name = SUSER_Name(0x1)

DECLARE @jobId BINARY(16);
EXEC @ReturnCode =  msdb.dbo.sp_add_job 
        @job_name=@job_name, 
		@enabled=1, 
		@notify_level_eventlog=0, 
		@notify_level_email=0, 
		@notify_level_netsend=0, 
		@notify_level_page=0, 
		@delete_level=0, 
		@owner_login_name=@sa_account_name, 
		@job_id = @jobId OUTPUT;
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;

EXEC @ReturnCode = msdb.dbo.sp_add_jobstep 
        @job_id=@jobId, 
        @step_name=N'Verify that automation is enabled.', 
		@step_id=1, 
		@cmdexec_success_code=0, 
		@on_success_action=3, 
		@on_success_step_id=0, 
		@on_fail_action=1, 
		@on_fail_step_id=0, 
		@retry_attempts=0, 
		@retry_interval=0, 
		@os_run_priority=0, 
		@subsystem=N'TSQL', 
		@command=N'IF (msdb.dbo.fn_syspolicy_is_automation_enabled() != 1)
        BEGIN
            RAISERROR(34022, 16, 1)
        END', 
		@database_name=N'master', 
		@flags=0;
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_add_jobstep 
        @job_id=@jobId, 
        @step_name=N'Purge history.', 
		@step_id=2, 
		@cmdexec_success_code=0, 
		@on_success_action=3, 
		@on_success_step_id=0, 
		@on_fail_action=2, 
		@on_fail_step_id=0, 
		@retry_attempts=0, 
		@retry_interval=0, 
		@os_run_priority=0, 
		@subsystem=N'TSQL', 
		@command=N'EXEC msdb.dbo.sp_syspolicy_purge_history', 
		@database_name=N'master', 
		@flags=0;
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;

DECLARE @command nvarchar(1000);
DECLARE @serverName nvarchar(255)

set @serverName=Convert(nvarchar(255), SERVERPROPERTY(N'ServerName'))
IF ( (@serverName NOT LIKE '%\%') )
BEGIN
	-- Powershell requires that default instances are called DEFAULT
	SET @serverName = @serverName + '\DEFAULT'
END

SET @command = N'(Get-Item SQLSERVER:\SQLPolicy\' + @serverName + ').EraseSystemHealthPhantomRecords()';

EXEC @ReturnCode = msdb.dbo.sp_add_jobstep 
        @job_id=@jobId, 
        @step_name=N'Erase Phantom System Health Records.', 
		@step_id=3, 
		@cmdexec_success_code=0, 
		@on_success_action=1, 
		@on_success_step_id=0, 
		@on_fail_action=2, 
		@on_fail_step_id=0, 
		@retry_attempts=0, 
		@retry_interval=0, 
		@os_run_priority=0, 
		@subsystem=N'PowerShell', 
		@command=@command, 
		@database_name=N'master', 
		@flags=0;
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;

EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1;
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;

EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = @@SERVERNAME;
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;

-- run this job every day at 2AM
EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule 
        @job_id=@jobId, 
        @name=N'syspolicy_purge_history_schedule', 
		@enabled=1, 
		@freq_type=4, 
		@freq_interval=1, 
		@freq_subday_type=1, 
		@freq_subday_interval=0, 
		@freq_relative_interval=0, 
		@freq_recurrence_factor=0, 
		@active_start_date=20080101, 
		@active_end_date=99991231, 
		@active_start_time=20000, 
		@active_end_time=235959;
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;

INSERT INTO [msdb].[dbo].[syspolicy_configuration_internal] (name, current_value)
VALUES (N'PurgeHistoryJobGuid', @jobId);

COMMIT TRANSACTION;
RETURN;

QuitWithRollback:
    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION;
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_purge_health_state] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_purge_health_state]
    @target_tree_root_with_id nvarchar(400) = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole';
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check;
	END
	
	IF (@target_tree_root_with_id IS NULL)
	BEGIN
	    DELETE FROM msdb.dbo.syspolicy_system_health_state_internal;
	END
	ELSE
	BEGIN
	    DECLARE @target_mask nvarchar(801);
	    SET @target_mask = @target_tree_root_with_id;
	    -- we need to escape all the characters that can be part of the 
	    -- LIKE pattern
	    SET @target_mask = REPLACE(@target_mask, '[', '\[');
	    SET @target_mask = REPLACE(@target_mask, ']', '\]');
	    SET @target_mask = REPLACE(@target_mask, '_', '\_');
	    SET @target_mask = REPLACE(@target_mask, '%', '\%');
	    SET @target_mask = @target_mask + '%';
	    DELETE FROM msdb.dbo.syspolicy_system_health_state_internal
	        WHERE target_query_expression_with_id LIKE @target_mask ESCAPE '\';
	END
	
	RETURN 0;
END
GO

-----------------------------------------------------------
-- Security for policy objects
-----------------------------------------------------------
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'PolicyAdministratorRole' AND type = 'R'))
BEGIN
    CREATE ROLE [PolicyAdministratorRole]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'PolicyAdministratorRole' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [PolicyAdministratorRole]
        CREATE ROLE [PolicyAdministratorRole]
    END
END
GO
-- Policy administrator is also an agent operator
-- because we need to create jobs automatically
EXECUTE sp_addrolemember @rolename = 'SQLAgentOperatorRole' , 
                   @membername = 'PolicyAdministratorRole' 
GO
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'ServerGroupAdministratorRole' AND type = 'R'))
BEGIN
    CREATE ROLE [ServerGroupAdministratorRole]
END

GO
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'ServerGroupReaderRole' AND type = 'R'))
BEGIN
    CREATE ROLE [ServerGroupReaderRole]
END
GO
EXECUTE sp_addrolemember @rolename = 'ServerGroupReaderRole' , 
                   @membername = 'ServerGroupAdministratorRole' 

GO
CREATE PROCEDURE #provision_table @short_name sysname, @role_name sysname, @grant_public_select bit
AS
BEGIN
    DECLARE @stmt nvarchar(max)
    -- revoke table permissions
    SELECT @stmt = N'REVOKE DELETE, INSERT, REFERENCES, SELECT, UPDATE, ALTER, CONTROL, TAKE OWNERSHIP 
        ON [dbo].' + QUOTENAME(@short_name + N'_internal') + ' FROM [public] CASCADE'
    EXEC sp_executesql @stmt

    -- revoke view permissions
    SELECT @stmt = N'REVOKE ALTER, CONTROL, DELETE, INSERT, REFERENCES, SELECT, TAKE OWNERSHIP, UPDATE
        ON [dbo].' + QUOTENAME(@short_name ) + ' FROM [public] CASCADE'
    EXEC sp_executesql @stmt

    -- grant select on view 
    SELECT @stmt = N'GRANT SELECT ON [dbo].' + QUOTENAME(@short_name)+ ' TO ' + QUOTENAME(@role_name)
    EXEC sp_executesql @stmt

    if (@grant_public_select != 0)
    BEGIN
        SELECT @stmt = N'GRANT SELECT ON [dbo].' + QUOTENAME(@short_name)+ ' TO [public]'
        EXEC sp_executesql @stmt
    END
    
END
GO

-- public role can view all policy metadata
EXEC #provision_table N'syspolicy_conditions', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_policies', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_policy_categories', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_object_sets', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_target_sets', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_target_set_levels', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_policy_category_subscriptions', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_system_health_state', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_policy_execution_history', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_policy_execution_history_details', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_configuration', N'PolicyAdministratorRole', 1

-- Registered Server information is limited to the ServerGroupReaderRole, with no public access
EXEC #provision_table N'sysmanagement_shared_registered_servers', N'ServerGroupReaderRole', 0
EXEC #provision_table N'sysmanagement_shared_server_groups', N'ServerGroupReaderRole', 0
GO
DROP PROCEDURE #provision_table

GO
CREATE PROCEDURE #provision_sp @name sysname, @role_name sysname
AS
BEGIN
    DECLARE @stmt nvarchar(max)
    SELECT @stmt = N'REVOKE ALTER, CONTROL, EXECUTE, TAKE OWNERSHIP, VIEW DEFINITION
    ON [dbo].' + QUOTENAME(@name) + ' FROM [public] CASCADE'
    EXEC sp_executesql @stmt

    SELECT @stmt = N'GRANT EXECUTE ON [dbo].' + QUOTENAME(@name)+ ' TO ' + QUOTENAME(@role_name)
    EXEC sp_executesql @stmt
    
END
GO
EXEC #provision_sp N'sp_syspolicy_add_condition', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_update_condition', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_rename_condition', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_delete_condition', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_add_policy', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_update_policy', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_rename_policy', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_delete_policy', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_add_target_set', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_update_target_set', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_add_target_set_level', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_update_target_set_level', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_add_policy_category_subscription', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_update_policy_category_subscription', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_delete_policy_category_subscription', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_log_policy_execution_start', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_log_policy_execution_end', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_log_policy_execution_detail', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_add_policy_category', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_rename_policy_category', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_update_policy_category', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_delete_policy_category', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_add_object_set', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_delete_object_set', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_verify_object_set_identifiers', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_dispatch_event', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_configure', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_purge_history', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_repair_policy_automation', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_purge_health_state', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_create_purge_job', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_set_log_on_success', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_set_config_enabled', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_set_config_history_retention', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_sysmanagement_update_shared_registered_server', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_rename_shared_registered_server', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_update_shared_server_group', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_rename_shared_server_group', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_move_shared_registered_server', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_delete_shared_registered_server', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_move_shared_server_group', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_delete_shared_server_group', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_add_shared_registered_server', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_add_shared_server_group', N'ServerGroupAdministratorRole'

GO
DROP PROCEDURE #provision_sp
GO
GRANT EXECUTE ON [dbo].[fn_syspolicy_is_automation_enabled] TO PUBLIC
GO 

IF EXISTS (SELECT * from sys.database_principals where name = N'##MS_PolicyEventProcessingLogin##')
    DROP USER [##MS_PolicyEventProcessingLogin##]
GO
use master
GO
IF EXISTS (SELECT * from sys.server_principals WHERE name = '##MS_PolicyEventProcessingLogin##')
BEGIN
    IF EXISTS (SELECT * from sys.server_triggers WHERE name = N'syspolicy_server_trigger')
        DROP TRIGGER [syspolicy_server_trigger] ON ALL SERVER
    DROP LOGIN [##MS_PolicyEventProcessingLogin##]
END
go
-- create event processing login with random password
DECLARE @newid uniqueidentifier
SET @newid = NEWID()
DECLARE @password varchar(255)
SELECT @password = QUOTENAME(CONVERT(varchar(255), @newid), '''')
DBCC TRACEON(4606,-1)
EXECUTE( N'CREATE LOGIN [##MS_PolicyEventProcessingLogin##] WITH PASSWORD=N' + @password + '')
DBCC TRACEOFF(4606,-1)
go
-- create t-sql execution login with random password
IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = '##MS_PolicyTsqlExecutionLogin##')
BEGIN
    DECLARE @newid uniqueidentifier
    SET @newid = NEWID()
    DECLARE @password varchar(255)
    SELECT @password = QUOTENAME(CONVERT(varchar(255), @newid), '''')
    DBCC TRACEON(4606,-1)
    EXECUTE( N'CREATE LOGIN [##MS_PolicyTsqlExecutionLogin##] WITH PASSWORD=N' + @password + '')
    DBCC TRACEOFF(4606,-1)

    -- this login is used just for impersonation, no one should be able to connect
    ALTER LOGIN [##MS_PolicyTsqlExecutionLogin##] DISABLE

    GRANT VIEW ANY DEFINITION TO [##MS_PolicyTsqlExecutionLogin##]
    GRANT VIEW SERVER STATE TO [##MS_PolicyTsqlExecutionLogin##]
END
go

-- this login is used just for impersonation, no one should be able to connect
ALTER LOGIN [##MS_PolicyEventProcessingLogin##] DISABLE

go
USE [msdb]
go
CREATE USER [##MS_PolicyEventProcessingLogin##] FROM LOGIN [##MS_PolicyEventProcessingLogin##]
go
EXEC msdb.sys.sp_addrolemember @rolename = 'PolicyAdministratorRole', @membername = '##MS_PolicyEventProcessingLogin##'
go
GRANT EXECUTE ON [sp_syspolicy_events_reader] TO [##MS_PolicyEventProcessingLogin##]
go

IF NOT EXISTS ( SELECT * FROM sys.database_principals WHERE name = '##MS_PolicyTsqlExecutionLogin##')
BEGIN
    CREATE USER [##MS_PolicyTsqlExecutionLogin##] FROM LOGIN [##MS_PolicyTsqlExecutionLogin##]
    EXEC msdb.sys.sp_addrolemember @rolename = 'PolicyAdministratorRole', @membername = '##MS_PolicyTsqlExecutionLogin##'
END
GO

USE [master]
go
IF EXISTS (SELECT * from sys.database_principals where name = N'##MS_PolicyEventProcessingLogin##')
    DROP USER [##MS_PolicyEventProcessingLogin##]
go
CREATE USER [##MS_PolicyEventProcessingLogin##]
go
GRANT EXECUTE ON [sp_syspolicy_execute_policy] TO [##MS_PolicyEventProcessingLogin##]
go

USE [msdb]
go

PRINT N'Hook up the activation procedure to the queue...'
ALTER QUEUE [syspolicy_event_queue]
    WITH ACTIVATION (
        STATUS = ON,
        MAX_QUEUE_READERS = 1,
        PROCEDURE_NAME = [sp_syspolicy_events_reader],
        EXECUTE AS N'##MS_PolicyEventProcessingLogin##');
GO

-- if this script runs on an existing installation (e.g in an upgrade) we need to recreate 
-- the event notifications and the ddl triggers used for policy automation 
exec sys.sp_syspolicy_update_event_notification
exec sys.sp_syspolicy_update_ddl_trigger
GO


-------------------------------------------------------------------------------
-- End policy objects
-------------------------------------------------------------------------------


/**************************************************************/
/* Sign agent sps and add them to Off By Default component    */
/*                                                            */
/* Also sign SPs for other components located in MSDB         */
/**************************************************************/

-- List all of the stored procedures we need to sign with the Agent
-- signing certicate. Optionally if your SP belongs in the 'Agent XPs'
-- Off-by-default component, then specify 1 for the 'obdComponent'
-- column; If it belongs in 'DBMail XPs', specify 2.

create table #sp_table (name sysname, sign int, obdComponent int, bNewProc int)
go
insert into #sp_table values(N'sp_sqlagent_is_srvrolemember',               1, 0, 0)
insert into #sp_table values(N'sp_verify_category_identifiers',               1, 0, 0)
insert into #sp_table values(N'sp_verify_proxy_identifiers',               1, 0, 0)
insert into #sp_table values(N'sp_verify_credential_identifiers',          1, 0, 0)
insert into #sp_table values(N'sp_verify_subsystem_identifiers',           1, 0, 0)
insert into #sp_table values(N'sp_verify_login_identifiers',               1, 0, 0)
insert into #sp_table values(N'sp_verify_proxy',                  1, 0, 0)
insert into #sp_table values(N'sp_add_proxy',                     1, 0, 0)
insert into #sp_table values(N'sp_delete_proxy',                  1, 0, 0)
insert into #sp_table values(N'sp_update_proxy',                  1, 0, 0)
insert into #sp_table values(N'sp_sqlagent_is_member',                  1, 0, 0)
insert into #sp_table values(N'sp_verify_proxy_permissions',               1, 0, 0)
insert into #sp_table values(N'sp_help_proxy',                    1, 0, 0)
insert into #sp_table values(N'sp_grant_proxy_to_subsystem',               1, 0, 0)
insert into #sp_table values(N'sp_grant_login_to_proxy',             1, 0, 0)
insert into #sp_table values(N'sp_revoke_login_from_proxy',             1, 0, 0)
insert into #sp_table values(N'sp_revoke_proxy_from_subsystem',               1, 0, 0)
insert into #sp_table values(N'sp_enum_proxy_for_subsystem',               1, 0, 0)
insert into #sp_table values(N'sp_enum_login_for_proxy',             1, 0, 0)
insert into #sp_table values(N'sp_sqlagent_get_startup_info',              1, 1, 0)
insert into #sp_table values(N'sp_sqlagent_has_server_access',             1, 1, 0)
insert into #sp_table values(N'sp_sem_add_message',                  1, 0, 0)
insert into #sp_table values(N'sp_sem_drop_message',                 1, 0, 0)
insert into #sp_table values(N'sp_get_message_description',             1, 0, 0)
insert into #sp_table values(N'sp_sqlagent_get_perf_counters',             1, 0, 0)
insert into #sp_table values(N'sp_sqlagent_notify',                  1, 1, 0)
insert into #sp_table values(N'sp_is_sqlagent_starting',             1, 1, 0)
insert into #sp_table values(N'sp_verify_job_identifiers',              1, 0, 0)
insert into #sp_table values(N'sp_verify_schedule_identifiers',               1, 0, 0)
insert into #sp_table values(N'sp_verify_jobproc_caller',               1, 0, 0)
insert into #sp_table values(N'sp_downloaded_row_limiter',              1, 1, 0)
insert into #sp_table values(N'sp_post_msx_operation',                  1, 1, 0)
insert into #sp_table values(N'sp_verify_performance_condition',           1, 0, 0)
insert into #sp_table values(N'sp_verify_job_date',                  1, 0, 0)
insert into #sp_table values(N'sp_verify_job_time',                  1, 0, 0)
insert into #sp_table values(N'sp_verify_alert',                  1, 1, 0)
insert into #sp_table values(N'sp_update_alert',                  1, 0, 0)
insert into #sp_table values(N'sp_delete_job_references',               1, 0, 0)
insert into #sp_table values(N'sp_delete_all_msx_jobs',                 1, 0, 0)
insert into #sp_table values(N'sp_generate_target_server_job_assignment_sql',       1, 0, 0)
insert into #sp_table values(N'sp_generate_server_description',               1, 1, 0)
insert into #sp_table values(N'sp_msx_set_account',                  1, 1, 0)
insert into #sp_table values(N'sp_msx_get_account',                  1, 1, 0)
insert into #sp_table values(N'sp_delete_operator',                  1, 0, 0)
insert into #sp_table values(N'sp_msx_defect',                    1, 1, 0)
insert into #sp_table values(N'sp_msx_enlist',                    1, 1, 0)
insert into #sp_table values(N'sp_delete_targetserver',                 1, 0, 0)
insert into #sp_table values(N'sp_get_sqlagent_properties',             1, 1, 0)
insert into #sp_table values(N'sp_set_sqlagent_properties',             1, 1, 0)
insert into #sp_table values(N'sp_add_targetservergroup',               1, 0, 0)
insert into #sp_table values(N'sp_update_targetservergroup',               1, 0, 0)
insert into #sp_table values(N'sp_delete_targetservergroup',               1, 0, 0)
insert into #sp_table values(N'sp_help_targetservergroup',              1, 0, 0)
insert into #sp_table values(N'sp_add_targetsvrgrp_member',             1, 0, 0)
insert into #sp_table values(N'sp_delete_targetsvrgrp_member',             1, 0, 0)
insert into #sp_table values(N'sp_verify_category',                  1, 0, 0)
insert into #sp_table values(N'sp_add_category',                  1, 0, 0)
insert into #sp_table values(N'sp_update_category',                  1, 0, 0)
insert into #sp_table values(N'sp_delete_category',                  1, 0, 0)
insert into #sp_table values(N'sp_help_category',                 1, 0, 0)
insert into #sp_table values(N'sp_help_targetserver',                1, 0, 0)
insert into #sp_table values(N'sp_resync_targetserver',                 1, 0, 0)
insert into #sp_table values(N'sp_purge_jobhistory',                 1, 0, 0)
insert into #sp_table values(N'sp_help_jobhistory',                  1, 0, 0)
insert into #sp_table values(N'sp_help_jobhistory_full',                  1, 0, 0)
insert into #sp_table values(N'sp_help_jobhistory_summary',                  1, 0, 0)
insert into #sp_table values(N'sp_help_jobhistory_sem',                  1, 0, 0)
insert into #sp_table values(N'sp_add_jobserver',                 1, 0, 0)
insert into #sp_table values(N'sp_delete_jobserver',                 1, 0, 0)
insert into #sp_table values(N'sp_help_jobserver',                1, 0, 0)
insert into #sp_table values(N'sp_help_downloadlist',                1, 0, 0)
insert into #sp_table values(N'sp_enum_sqlagent_subsystems',               1, 0, 0)
insert into #sp_table values(N'sp_enum_sqlagent_subsystems_internal', 1, 0, 0)
insert into #sp_table values(N'sp_verify_subsystem',                 1, 1, 0)
insert into #sp_table values(N'sp_verify_subsystems',                 1, 0, 0)
insert into #sp_table values(N'sp_verify_schedule',                  1, 0, 0)
insert into #sp_table values(N'sp_add_schedule',                  1, 0, 0)
insert into #sp_table values(N'sp_attach_schedule',                  1, 0, 0)
insert into #sp_table values(N'sp_detach_schedule',                  1, 0, 0)
insert into #sp_table values(N'sp_update_schedule',                  1, 0, 0)
insert into #sp_table values(N'sp_delete_schedule',                  1, 0, 0)
insert into #sp_table values(N'sp_get_jobstep_db_username',             1, 0, 0)
insert into #sp_table values(N'sp_verify_jobstep',                1, 0, 0)
insert into #sp_table values(N'sp_add_jobstep_internal',             1, 0, 0)
insert into #sp_table values(N'sp_add_jobstep',                   1, 0, 0)
insert into #sp_table values(N'sp_update_jobstep',                1, 0, 0)
insert into #sp_table values(N'sp_delete_jobstep',                1, 0, 0)
insert into #sp_table values(N'sp_help_jobstep',                  1, 0, 0)
insert into #sp_table values(N'sp_write_sysjobstep_log',             1, 0, 0)
insert into #sp_table values(N'sp_help_jobsteplog',                  1, 0, 0)
insert into #sp_table values(N'sp_delete_jobsteplog',                1, 0, 0)
insert into #sp_table values(N'sp_get_schedule_description',               1, 1, 0)
insert into #sp_table values(N'sp_add_jobschedule',                  1, 0, 0)
insert into #sp_table values(N'sp_update_replication_job_parameter',          1, 0, 0)
insert into #sp_table values(N'sp_update_jobschedule',                  1, 0, 0)
insert into #sp_table values(N'sp_delete_jobschedule',                  1, 0, 0)
insert into #sp_table values(N'sp_help_schedule',                 1, 0, 0)
insert into #sp_table values(N'sp_help_jobschedule',                 1, 0, 0)
insert into #sp_table values(N'sp_verify_job',                    1, 1, 0)
insert into #sp_table values(N'sp_add_job',                    1, 0, 0)
insert into #sp_table values(N'sp_update_job',                    1, 0, 0)
insert into #sp_table values(N'sp_delete_job',                    1, 0, 0)
insert into #sp_table values(N'sp_get_composite_job_info',              1, 1, 0)
insert into #sp_table values(N'sp_help_job',                   1, 0, 0)
insert into #sp_table values(N'sp_help_jobcount ',                1, 0, 0)
insert into #sp_table values(N'sp_help_jobs_in_schedule',               1, 0, 0)
insert into #sp_table values(N'sp_manage_jobs_by_login',             1, 0, 0)
insert into #sp_table values(N'sp_apply_job_to_targets',             1, 0, 0)
insert into #sp_table values(N'sp_remove_job_from_targets',             1, 0, 0)
insert into #sp_table values(N'sp_get_job_alerts',                1, 0, 0)
insert into #sp_table values(N'sp_convert_jobid_to_char',               1, 0, 0)
insert into #sp_table values(N'sp_start_job',                     1, 0, 0)
insert into #sp_table values(N'sp_stop_job',                   1, 0, 0)
insert into #sp_table values(N'sp_cycle_agent_errorlog',             1, 0, 0)
insert into #sp_table values(N'sp_get_chunked_jobstep_params',             1, 0, 0)
insert into #sp_table values(N'sp_check_for_owned_jobs',             1, 0, 0)
insert into #sp_table values(N'sp_check_for_owned_jobsteps',               1, 0, 0)
insert into #sp_table values(N'sp_sqlagent_refresh_job',             1, 0, 0)
insert into #sp_table values(N'sp_jobhistory_row_limiter',              1, 1, 0)
insert into #sp_table values(N'sp_sqlagent_log_jobhistory',             1, 0, 0)
insert into #sp_table values(N'sp_sqlagent_check_msx_version',             1, 0, 0)
insert into #sp_table values(N'sp_sqlagent_probe_msx',                  1, 0, 0)
insert into #sp_table values(N'sp_set_local_time',                1, 1, 0)
insert into #sp_table values(N'sp_multi_server_job_summary',               1, 0, 0)
insert into #sp_table values(N'sp_target_server_summary',               1, 0, 0)
insert into #sp_table values(N'sp_uniquetaskname',                1, 0, 0)
insert into #sp_table values(N'sp_addtask',                    1, 0, 0)
insert into #sp_table values(N'sp_droptask',                   1, 0, 0)
insert into #sp_table values(N'sp_add_alert_internal',                  1, 0, 0)
insert into #sp_table values(N'sp_add_alert',                     1, 0, 0)
insert into #sp_table values(N'sp_delete_alert',                  1, 0, 0)
insert into #sp_table values(N'sp_help_alert',                    1, 0, 0)
insert into #sp_table values(N'sp_verify_operator',                  1, 0, 0)
insert into #sp_table values(N'sp_add_operator',                  1, 0, 0)
insert into #sp_table values(N'sp_update_operator',                  1, 1, 0)
insert into #sp_table values(N'sp_help_operator',                 1, 0, 0)
insert into #sp_table values(N'sp_help_operator_jobs',                  1, 0, 0)
insert into #sp_table values(N'sp_verify_operator_identifiers',               1, 0, 0)
insert into #sp_table values(N'sp_notify_operator',                  1, 0, 0)
insert into #sp_table values(N'sp_verify_notification',                 1, 0, 0)
insert into #sp_table values(N'sp_add_notification',                 1, 0, 0)
insert into #sp_table values(N'sp_update_notification',                 1, 0, 0)
insert into #sp_table values(N'sp_delete_notification',                 1, 0, 0)
insert into #sp_table values(N'sp_help_notification',                1, 0, 0)
insert into #sp_table values(N'sp_help_jobactivity',                 1, 0, 0)
insert into #sp_table values(N'sp_enlist_tsx',                    1, 1, 0)
insert into #sp_table values(N'trig_targetserver_insert',               1, 0, 0)

-- Database Mail configuration procs
insert into #sp_table values(N'sysmail_verify_accountparams_sp',           1, 0, 0)
insert into #sp_table values(N'sysmail_verify_principal_sp',               1, 0, 0)
insert into #sp_table values(N'sysmail_verify_profile_sp',              1, 0, 0)
insert into #sp_table values(N'sysmail_verify_account_sp',              1, 0, 0)
insert into #sp_table values(N'sysmail_add_profile_sp',                 1, 0, 0)
insert into #sp_table values(N'sysmail_update_profile_sp',              1, 0, 0)
insert into #sp_table values(N'sysmail_delete_profile_sp',              1, 0, 0)
insert into #sp_table values(N'sysmail_help_profile_sp',             1, 0, 0)
insert into #sp_table values(N'sysmail_create_user_credential_sp',            1, 0, 0)
insert into #sp_table values(N'sysmail_alter_user_credential_sp',          1, 0, 0)
insert into #sp_table values(N'sysmail_drop_user_credential_sp',           1, 0, 0)
insert into #sp_table values(N'sysmail_add_account_sp',                 1, 0, 0)
insert into #sp_table values(N'sysmail_update_account_sp',              1, 0, 0)
insert into #sp_table values(N'sysmail_delete_account_sp',              1, 0, 0)
insert into #sp_table values(N'sysmail_help_account_sp',             1, 0, 0)
insert into #sp_table values(N'sysmail_help_admin_account_sp',             1, 0, 0)
insert into #sp_table values(N'sysmail_add_profileaccount_sp',             1, 0, 0)
insert into #sp_table values(N'sysmail_update_profileaccount_sp',          1, 0, 0)
insert into #sp_table values(N'sysmail_delete_profileaccount_sp',          1, 0, 0)
insert into #sp_table values(N'sysmail_help_profileaccount_sp',               1, 0, 0)
insert into #sp_table values(N'sysmail_configure_sp',                1, 0, 0)
insert into #sp_table values(N'sysmail_help_configure_sp',              1, 0, 0)
insert into #sp_table values(N'sysmail_help_configure_value_sp',           1, 0, 0)
insert into #sp_table values(N'sysmail_add_principalprofile_sp',           1, 0, 0)
insert into #sp_table values(N'sysmail_update_principalprofile_sp',           1, 0, 0)
insert into #sp_table values(N'sysmail_delete_principalprofile_sp',           1, 0, 0)
insert into #sp_table values(N'sysmail_help_principalprofile_sp',          1, 0, 0)

-- Database Mail: mail host database specific procs
insert into #sp_table values(N'sysmail_start_sp',             1, 2, 0)
insert into #sp_table values(N'sysmail_stop_sp',              1, 2, 0)
insert into #sp_table values(N'sysmail_logmailevent_sp',      1, 0, 0)
insert into #sp_table values(N'sp_SendMailMessage',           1, 0, 0)
insert into #sp_table values(N'sp_isprohibited',              1, 0, 0)
insert into #sp_table values(N'sp_SendMailQueues',            1, 0, 0)
insert into #sp_table values(N'sp_ProcessResponse',           1, 0, 0)
insert into #sp_table values(N'sp_MailItemResultSets',        1, 0, 0)
insert into #sp_table values(N'sp_process_DialogTimer',       1, 0, 0)
insert into #sp_table values(N'sp_readrequest',               1, 0, 0)
insert into #sp_table values(N'sp_GetAttachmentData',         1, 0, 0)
insert into #sp_table values(N'sp_RunMailQuery',              1, 0, 0)
insert into #sp_table values(N'sysmail_help_queue_sp',        1, 0, 0)
insert into #sp_table values(N'sysmail_help_status_sp',       1, 2, 0)
insert into #sp_table values(N'sysmail_delete_mailitems_sp',  1, 0, 0)
insert into #sp_table values(N'sysmail_delete_log_sp',        1, 0, 0)
insert into #sp_table values(N'sp_validate_user',             1, 2, 0)
insert into #sp_table values(N'sp_send_dbmail',               1, 2, 0)
insert into #sp_table values(N'sp_ExternalMailQueueListener', 1, 0, 0)
insert into #sp_table values(N'sp_sysmail_activate',          1, 0, 0)

-- Maintenance Plans
insert into #sp_table values(N'sp_maintplan_delete_log',             1, 0, 0)
insert into #sp_table values(N'sp_maintplan_delete_subplan',               1, 0, 0)
insert into #sp_table values(N'sp_maintplan_open_logentry',             1, 0, 0)
insert into #sp_table values(N'sp_maintplan_close_logentry',               1, 0, 0)
insert into #sp_table values(N'sp_maintplan_update_log',             1, 0, 0)
insert into #sp_table values(N'sp_maintplan_update_subplan',               1, 0, 0)
insert into #sp_table values(N'sp_maintplan_delete_plan',               1, 0, 0)
insert into #sp_table values(N'sp_maintplan_start',                  1, 0, 0)
insert into #sp_table values(N'sp_clear_dbmaintplan_by_db',             1, 0, 0)
insert into #sp_table values(N'sp_add_maintenance_plan',             1, 0, 0)
insert into #sp_table values(N'sp_delete_maintenance_plan',             1, 0, 0)
insert into #sp_table values(N'sp_add_maintenance_plan_db',             1, 0, 0)
insert into #sp_table values(N'sp_delete_maintenance_plan_db',             1, 0, 0)
insert into #sp_table values(N'sp_add_maintenance_plan_job',               1, 1, 0)
insert into #sp_table values(N'sp_delete_maintenance_plan_job',               1, 0, 0)
insert into #sp_table values(N'sp_help_maintenance_plan',               1, 0, 0)
insert into #sp_table values(N'sp_maintplan_update_subplan_tsx',               1, 0, 0)
insert into #sp_table values(N'sp_maintplan_subplans_by_job',               1, 0, 0)

-- Log Shipping
insert into #sp_table values(N'sp_add_log_shipping_monitor_jobs',          1, 0, 0)
insert into #sp_table values(N'sp_add_log_shipping_primary',               1, 0, 0)
insert into #sp_table values(N'sp_add_log_shipping_secondary',             1, 0, 0)
insert into #sp_table values(N'sp_delete_log_shipping_monitor_jobs',          1, 0, 0)
insert into #sp_table values(N'sp_delete_log_shipping_primary',               1, 0, 0)
insert into #sp_table values(N'sp_delete_log_shipping_secondary ',            1, 0, 0)
insert into #sp_table values(N'sp_log_shipping_in_sync',             1, 0, 0)
insert into #sp_table values(N'sp_log_shipping_get_date_from_file ',          1, 0, 0)
insert into #sp_table values(N'sp_get_log_shipping_monitor_info',          1, 0, 0)
insert into #sp_table values(N'sp_update_log_shipping_monitor_info',          1, 0, 0)
insert into #sp_table values(N'sp_delete_log_shipping_monitor_info',          1, 0, 0)
insert into #sp_table values(N'sp_remove_log_shipping_monitor_account',          1, 0, 0)
insert into #sp_table values(N'sp_log_shipping_monitor_backup',               1, 0, 0)
insert into #sp_table values(N'sp_log_shipping_monitor_restore',           1, 0, 0)
insert into #sp_table values(N'sp_change_monitor_role',                 1, 0, 0)
insert into #sp_table values(N'sp_create_log_shipping_monitor_account',          1, 0, 0)

-- DTS
insert into #sp_table values(N'sp_get_dtsversion',                1, 0, 0)
insert into #sp_table values(N'sp_make_dtspackagename',                 1, 0, 0)
insert into #sp_table values(N'sp_add_dtspackage',                1, 0, 0)
insert into #sp_table values(N'sp_drop_dtspackage',                  1, 0, 0)
insert into #sp_table values(N'sp_reassign_dtspackageowner',               1, 0, 0)
insert into #sp_table values(N'sp_get_dtspackage',                1, 0, 0)
insert into #sp_table values(N'sp_reassign_dtspackagecategory',               1, 0, 0)
insert into #sp_table values(N'sp_enum_dtspackages',                 1, 0, 0)
insert into #sp_table values(N'sp_add_dtscategory',                  1, 0, 0)
insert into #sp_table values(N'sp_drop_dtscategory',                 1, 0, 0)
insert into #sp_table values(N'sp_modify_dtscategory',                  1, 0, 0)
insert into #sp_table values(N'sp_enum_dtscategories',                  1, 0, 0)
insert into #sp_table values(N'sp_log_dtspackage_begin',             1, 0, 0)
insert into #sp_table values(N'sp_log_dtspackage_end',                  1, 0, 0)
insert into #sp_table values(N'sp_log_dtsstep_begin',                1, 0, 0)
insert into #sp_table values(N'sp_log_dtsstep_end',                  1, 0, 0)
insert into #sp_table values(N'sp_log_dtstask',                   1, 0, 0)
insert into #sp_table values(N'sp_enum_dtspackagelog',                  1, 0, 0)
insert into #sp_table values(N'sp_enum_dtssteplog',                  1, 0, 0)
insert into #sp_table values(N'sp_enum_dtstasklog',                  1, 0, 0)
insert into #sp_table values(N'sp_dump_dtslog_all',                  1, 0, 0)
insert into #sp_table values(N'sp_dump_dtspackagelog',                  1, 0, 0)
insert into #sp_table values(N'sp_dump_dtssteplog',                  1, 0, 0)
insert into #sp_table values(N'sp_dump_dtstasklog',                  1, 0, 0)
insert into #sp_table values(N'sp_dts_secure',						1, 0, 0)
insert into #sp_table values(N'sp_ssis_addlogentry',                  1, 0, 0)
insert into #sp_table values(N'sp_ssis_listpackages',                 1, 0, 0)
insert into #sp_table values(N'sp_ssis_listfolders',                  1, 0, 0)
insert into #sp_table values(N'sp_ssis_deletepackage',                1, 0, 0)
insert into #sp_table values(N'sp_ssis_deletefolder',                 1, 0, 0)
insert into #sp_table values(N'sp_ssis_getpackage',                1, 0, 0)
insert into #sp_table values(N'sp_ssis_getfolder',                 1, 0, 0)
insert into #sp_table values(N'sp_ssis_putpackage',                1, 0, 0)
insert into #sp_table values(N'sp_ssis_addfolder',                 1, 0, 0)
insert into #sp_table values(N'sp_ssis_renamefolder',                 1, 0, 0)
insert into #sp_table values(N'sp_ssis_setpackageroles',                 1, 0, 0)
insert into #sp_table values(N'sp_ssis_getpackageroles',                 1, 0, 0)
go

/**************************************************************/
/* Mark system objects                                        */
/**************************************************************/
declare  @start datetime
      ,@name  sysname
select @start = start from #InstMsdb
-- As a temporary solution, exclude the syscollector_collection_sets table from being a system table
declare newsysobjs cursor for select name from sys.objects where schema_id = 1 and create_date >= @start
open newsysobjs
fetch next from newsysobjs into @name
while @@fetch_status = 0
begin
   Exec sp_MS_marksystemobject @name
   update #sp_table set bNewProc = 1 where name = @name
   fetch next from newsysobjs into @name
end
deallocate newsysobjs
drop table #InstMsdb
go

PRINT 'Signing sps ...'
-- Create certificate to sign Agent sps
--
declare @certError int

if exists (select * from sys.certificates where name = '##MS_AgentSigningCertificate##')
begin
   PRINT 'Dropping existing Agent certificate ...'
   drop certificate [##MS_AgentSigningCertificate##]
   select @certError = @@error
   IF (@certError <> 0)
   BEGIN
       select 'Cannot drop existing certificate. Objects still signed by ##MS_AgentSigningCertificate##' = object_name(crypts.major_id) 
       from sys.crypt_properties crypts, sys.certificates certs
       where crypts.thumbprint = certs.thumbprint
       and crypts.class = 1
       and certs.name = '##MS_AgentSigningCertificate##'
   
       RAISERROR('Cannot drop existing ##MS_AgentSigningCertificate## in msdb. INSTMSDB.SQL terminating.', 20, 127) WITH LOG
   END
end

-- If the temp table #SqlAgentSignatures exists, then this script is running as part
-- of an upgrade from a previous build of sql server. In this case we want to sign
-- the Agent procedures using the same signatures that exist in a clean install
-- of this same build. Those signatures are in #SqlAgentSignatures. To do this
-- we create the certificate from the .cer file.
--
-- In the non-upgrade case, meaning that this script is being run during the build
-- of the SQL Server product itself, we simply create the certificate from scratch.

CREATE TABLE #InstmsdbAgentCertPath (AgentCert NVARCHAR(1100))
DECLARE @certificate_name NVARCHAR(1100) -- space for path + MS_AgentSigningCertificate.cer
IF (NOT OBJECT_ID(N'tempdb.dbo.#SqlAgentSignatures', 'U') IS NULL)
BEGIN

    -- We need agent XP's to be on in order to lookup the path to our instance install folder.
    DECLARE @advopt_old_value int
    DECLARE @comp_old_value int
    EXEC #sp_enable_component 'Agent XPs', @advopt_old_value out, @comp_old_value out

    DECLARE @InstallRootPath nvarchar(1024)

    -- Note: This is an _instance_ registry lookup, so it will
    -- automatically insert the right instance name after the "Microsoft
    -- SQL Server" part of the path, thus looking in a path like
    -- 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL10.INST1\Setup'
    EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\Microsoft SQL Server\Setup', N'SQLPath', @InstallRootPath OUTPUT

    -- Restore Agent XPs to previous state
    EXECUTE #sp_restore_component_state 'Agent XPs', @advopt_old_value, @comp_old_value

    IF @InstallRootPath IS NULL
    BEGIN
       RAISERROR('Cannot find instance-specific registry path for SOFTWARE\Microsoft\Microsoft SQL Server\Setup\SqlPath. INSTMSDB.SQL terminating.', 20, 127) WITH LOG
    END

    -- Now find our certificate in the Install folder, placed there by setup.
    SELECT @InstallRootPath = @InstallRootPath + N'\Install'

    select @certificate_name = @InstallRootPath + N'\MS_AgentSigningCertificate.cer'

    -- Remember this file path so we can import it into master later
    insert #InstmsdbAgentCertPath(AgentCert) VALUES (@certificate_name)
    PRINT 'Updated #InstmsdbAgentCertPath with value ' + @certificate_name

    -- Handle single quotes in the directory name since this string is going to be passed to EXECUTE
    DECLARE @certificate_nameQuoted NVARCHAR(2200)
    SELECT  @certificate_nameQuoted = '''' + REPLACE(@certificate_name, '''', '''''') + ''''

    PRINT 'Creating ##MS_AgentSigningCertificate## from ' + @certificate_name
    EXECUTE(N'create certificate [##MS_AgentSigningCertificate##] from file = ' + @certificate_nameQuoted)
    select @certError = @@error

    IF (@certError <> 0)
    BEGIN
       RAISERROR('Cannot create ##MS_AgentSigningCertificate## from file. INSTMSDB.SQL terminating.', 20, 127) WITH LOG
    END
END
ELSE
BEGIN
   dbcc traceon(4606,-1) -- Ignore domain policy about weak password
   create certificate [##MS_AgentSigningCertificate##] 
      encryption by password = 'Yukon90_'
      with subject = 'MS_AgentSigningCertificate'
   select @certError = @@error
   dbcc traceoff(4606,-1)

   IF (@certError <> 0)
   BEGIN
      RAISERROR('Cannot create ##MS_AgentSigningCertificate## in msdb. INSTMSDB.SQL terminating.', 20, 127) WITH LOG
   END

   --create a temporary database in order to get the path to the 'Data' folder
   IF (EXISTS (SELECT name
                   FROM master.dbo.sysdatabases
                   WHERE (name = N'temp_MS_AgentSigningCertificate_database')))
   BEGIN
     DROP DATABASE temp_MS_AgentSigningCertificate_database
   END

   CREATE DATABASE temp_MS_AgentSigningCertificate_database
   
   -- Note: master.dbo.sysaltfiles's filename column is 260 nvarchars
   DECLARE @dataDirName NVARCHAR(520)
   SELECT @dataDirName = SUBSTRING(filename, 1, CHARINDEX(N'temp_MS_AgentSigningCertificate_database.mdf', filename) - 1)
     FROM master.dbo.sysaltfiles
     WHERE (name = N'temp_MS_AgentSigningCertificate_database')
   
   if (@dataDirName is null)
      RAISERROR('Cannot deduce path for temporary certificate ##MS_AgentSigningCertificate##. Was temp_MS_AgentSigningCertificate_database not created? INSTMSDB.SQL terminating.', 20, 127) WITH LOG
   
   SELECT @certificate_name = @dataDirName + 'MS_AgentSigningCertificate.cer'

   -- Remember this file path so we can import it into master later
   insert #InstmsdbAgentCertPath(AgentCert) VALUES (@certificate_name)
   PRINT 'Created #InstmsdbAgentCertPath'
   
   -- drop temporary database
   IF (EXISTS (SELECT name
                   FROM master.dbo.sysdatabases
                   WHERE (name = N'temp_MS_AgentSigningCertificate_database')))
   BEGIN
     DROP DATABASE temp_MS_AgentSigningCertificate_database
   END
END

go


BEGIN TRANSACTION
declare @sp sysname
declare @exec_str nvarchar(1024)
declare @sign int
declare @obdComponent int
declare @bNewProc int

declare @bUseExistingSignature int
set @bUseExistingSignature = 0
IF (NOT OBJECT_ID(N'tempdb.dbo.#SqlAgentSignatures', 'U') IS NULL)
BEGIN
    set @bUseExistingSignature = 1
END

declare @err_str nvarchar(1024)
declare ms_crs_sps cursor global for select name, sign, obdComponent, bNewProc from #sp_table 
open ms_crs_sps
fetch next from ms_crs_sps into @sp, @sign, @obdComponent, @bNewProc
while @@fetch_status = 0
begin
   if exists(select * from sys.objects where name = @sp)
   begin
      print 'processing sp: ' + @sp
      if (@sign = 1)
      begin
         if (@bUseExistingSignature = 1)
         begin
            declare @signature varbinary(max)
            select @signature = signature
               from #SqlAgentSignatures
               where object_name = @sp

            if (@signature is null)
            begin
               set @err_str = 'Cannot find existing signature for stored procedure ' + @sp + '. Terminating.'
               RAISERROR(@err_str, 20, 127) WITH LOG
               ROLLBACK TRANSACTION
               return
            end

            set @exec_str = N'add signature to ' + @sp + N' by certificate [##MS_AgentSigningCertificate##] with signature = ' + CONVERT(nvarchar(max), @signature, 1)
         end
         else
         begin
            set @exec_str = N'add signature to ' + @sp + N' by certificate [##MS_AgentSigningCertificate##] with password = ''Yukon90_'''
         end
         Execute(@exec_str)
         if (@@error <> 0)
         begin
            set @err_str = 'Cannot sign stored procedure ' + @sp + '. Terminating.'
            RAISERROR(@err_str, 20, 127) WITH LOG
            ROLLBACK TRANSACTION
            return
         end
      end

      -- If there is a new procedure that goes in a component, put it there
      if (@obdComponent > 0 and @bNewProc > 0)
      begin
         if (@obdComponent = 1) -- SQLAgent
             set @exec_str = N'exec sp_AddFunctionalUnitToComponent N''Agent XPs'', N''' + @sp + N''''

         else if (@obdComponent = 2) -- DbMail
             set @exec_str = N'exec sp_AddFunctionalUnitToComponent N''Database Mail XPs'', N''' + @sp + N''''
 
         Execute(@exec_str)
         if (@@error <> 0)
         begin
            RAISERROR('Cannot add stored procedure to component. INSTMSDB.SQL terminating.', 20, 127) WITH LOG
            ROLLBACK TRANSACTION
            return
         end
      end

   end
   fetch next from ms_crs_sps into @sp, @sign, @obdComponent, @bNewProc
end
close ms_crs_sps
deallocate ms_crs_sps
COMMIT TRANSACTION
go
drop table #sp_table
go

PRINT 'Succesfully signed sps'

DECLARE @certificate_name NVARCHAR(1100)
select @certificate_name = AgentCert
   from #InstmsdbAgentCertPath

-- Handle single quotes in the directory name since this string is going to be passed to EXECUTE
-- in both BACKUP CERT and CREATE CERT below.
DECLARE @certificate_nameQuoted NVARCHAR(2200)
SELECT  @certificate_nameQuoted = '''' + REPLACE(@certificate_name, '''', '''''') + ''''

-- If this is not an upgrade, then we made our certificate from scratch.
-- Export it to a new file so that it can be imported into the master database.
IF (OBJECT_ID(N'tempdb.dbo.#SqlAgentSignatures', 'U') IS NULL)
BEGIN
   -- Drop certificate private key. When we export to a file just below,
   -- the file will not include the private key.
   alter certificate [##MS_AgentSigningCertificate##] remove private key

   IF (@@error <> 0)
      RAISERROR('Cannot alter ##MS_AgentSigningCertificate## in msdb. INSTMSDB.SQL terminating.', 20, 127) WITH LOG

   -- Now we export the certificate to a file so that it can be imported into the master database.
   -- Use the data directory to persist the file.
   --
   if (@certificate_name is null)
      RAISERROR('Cannot deduce path for temporary certificate ##MS_AgentSigningCertificate##.', 20, 127) WITH LOG

   declare @certError int

   PRINT 'Exporting ##MS_AgentSigningCertificate## to ' + @certificate_name
   BEGIN TRY
      EXECUTE(N'backup certificate [##MS_AgentSigningCertificate##] to file = ' + @certificate_nameQuoted)
      select @certError = @@error
   END TRY
   BEGIN CATCH
      -- Error 15240 happens when instmsdb.sql is run repeatedly and directly on a sql server, so the
      -- certificate already exists on disk and so the file cannot be written.
      -- It should not happen during the mkmastr build stage since the .cer file was already deleted.
      if (ERROR_NUMBER() != 15240)
      BEGIN
         select @certError = ERROR_NUMBER()
         PRINT 'Error ' + @certError + ' backing up certificate.'
      END
      ELSE
      BEGIN
         PRINT 'Could not export certificate, trying again with random name. If this is a mkmastr build then this is an error.'
         SELECT @certificate_name = SUBSTRING(@certificate_name, 1, CHARINDEX(N'.cer', @certificate_name) - 1) + 
                                    CONVERT(NVARCHAR(520), NEWID()) + N'.cer' 
         SELECT @certificate_nameQuoted = '''' + REPLACE(@certificate_name, '''', '''''') + ''''

         update #InstmsdbAgentCertPath set AgentCert = @certificate_name

         PRINT 'Exporting ##MS_AgentSigningCertificate## to ' + @certificate_name 
         EXECUTE(N'backup certificate [##MS_AgentSigningCertificate##] to file = ' + @certificate_nameQuoted)
         select @certError = @@error
      END
   END CATCH
   
   IF (@certError <> 0)
      RAISERROR('Cannot backup ##MS_AgentSigningCertificate##. INSTMSDB.SQL terminating.', 20, 127) WITH LOG
END

drop table #InstmsdbAgentCertPath

-- Now we want to grant the signed stored procedures access to the
-- master database. To allow the cross-database call from msdb, we
-- will recreate the Agent certificate in the master database and
-- grant execute permission to it.

-- Switch to master so the certificate is recreated there
use master

if exists (select * from sys.database_principals where name = '##MS_AgentSigningCertificate##')
   drop user [##MS_AgentSigningCertificate##]

if exists (select * from sys.server_principals where name = '##MS_AgentSigningCertificate##')
   drop login [##MS_AgentSigningCertificate##]

if exists (select * from sys.certificates where name = '##MS_AgentSigningCertificate##')
   drop certificate [##MS_AgentSigningCertificate##]

-- Recreate the certificate (minus the private key, which we dropped earlier) from the file
-- into the master database.
PRINT 'Creating ##MS_AgentSigningCertificate## in master from ' + @certificate_name
execute(N'create certificate [##MS_AgentSigningCertificate##] from file = ' + @certificate_nameQuoted)
IF (@@error <> 0)
   RAISERROR('Cannot create ##MS_AgentSigningCertificate## certificate in master. INSTMSDB.SQL terminating.', 20, 127) WITH LOG

-- create a login in the master database based on this certicate.
--
create login [##MS_AgentSigningCertificate##] from certificate [##MS_AgentSigningCertificate##]
IF (@@error <> 0)
   RAISERROR('Cannot create ##MS_AgentSigningCertificate## login. INSTMSDB.SQL terminating.', 20, 127) WITH LOG

-- create certificate-based user for execution granting
--
create user [##MS_AgentSigningCertificate##] for certificate [##MS_AgentSigningCertificate##]
IF (@@error <> 0)
   RAISERROR('Cannot create ##MS_AgentSigningCertificate## user. INSTMSDB.SQL terminating.', 20, 127) WITH LOG

-- enable certificate for OBD
--
exec sys.sp_SetOBDCertificate N'##MS_AgentSigningCertificate##',N'ON'

grant execute to [##MS_AgentSigningCertificate##]
PRINT 'Successfully granted execute permission in master to ##MS_AgentSigningCertificate##.'

use msdb
go

--
-- End of signing sps
go

if not exists (select * from [dbo].[sysssispackagefolders] where [folderid] = '00000000-0000-0000-0000-000000000000')
BEGIN
-- Insert our root folder
DECLARE  @advopt_old_value    INT 
DECLARE  @comp_old_value   INT
EXECUTE #sp_enable_component 'Agent XPs', @advopt_old_value out, @comp_old_value out
EXEC sp_ssis_addfolder NULL, '', '00000000-0000-0000-0000-000000000000'
-- Insert the 'Maintenance Plans' node at the root.
-- Note this GUID must never change - 08aa12d5-8f98-4dab-a4fc-980b150a5dc8
EXEC sp_ssis_addfolder '00000000-0000-0000-0000-000000000000', 'Maintenance Plans', '08aa12d5-8f98-4dab-a4fc-980b150a5dc8'
EXECUTE #sp_restore_component_state 'Agent XPs', @advopt_old_value, @comp_old_value
END
GO

/**************************************************************/
/* Enable Logshipping (Yukon)                                 */
/**************************************************************/
declare @retcode int
exec @retcode = sys.sp_logshippinginstallmetadata
if (@retcode = 0)
begin
raiserror('Logshipping enabled successfully', 10, 1)
end
else
begin
raiserror('Logshipping not enabled for this edition', 10, 1)
end
go

/**************************************************************/
/* Drop auxilary procedure to enable OBD component          */
/**************************************************************/
DROP PROCEDURE #sp_enable_component 
go
DROP PROCEDURE #sp_restore_component_state 
go

-- enable policy checking 
IF EXISTS (SELECT * FROM sys.server_triggers WHERE name = N'syspolicy_server_trigger')
    ENABLE TRIGGER [syspolicy_server_trigger] ON ALL SERVER 

-- Restore old state of 'allow updates'
EXECUTE master.dbo.sp_configure N'allow updates', 0
go
RECONFIGURE WITH OVERRIDE
go

PRINT ''
PRINT '----------------------------------'
PRINT 'Execution of INSTMSDB.SQL complete'
PRINT '----------------------------------'
go

CHECKPOINT
go
use msdb
go

DECLARE @CMDExec        sysname

EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                        N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent\SubSystems',
                                        N'CmdExec',
                                        @CMDExec OUTPUT,
                                        N'no_output'
IF @CMDExec IS NOT NULL                                        
BEGIN                                        
  PRINT ''
  PRINT 'Remove subsystem definition from registry...'
  --remove subsystem definition from registry and populate subsystem table
  DECLARE @ret INT
  EXEC @ret = master..xp_instance_regdeletekey N'HKEY_LOCAL_MACHINE',
                          N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent\SubSystems'
  IF @ret <> 0
    RAISERROR('Failed to remove subsystems definition from registry', 10, 127) 
END

PRINT ''
PRINT 'Populate syssubsystem table...'

EXEC @ret = sp_enum_sqlagent_subsystems
IF @ret <> 0
  RAISERROR('Failed to add subsystems definition to syssubsystem table. Upgrade script terminating', 20, 127) WITH LOG
go
--restore Shiloh object permission
DECLARE @state_desc			  nvarchar(60)
DECLARE @permission_name	sysname
DECLARE @object_name		  sysname
DECLARE @grantee_name		  sysname 

DECLARE perms_set_cursor CURSOR LOCAL FOR 
	SELECT state_desc, permission_name, object_name, grantee_name from msdb.dbo.upgrade_perms

OPEN perms_set_cursor
   FETCH NEXT FROM perms_set_cursor INTO @state_desc, @permission_name, @object_name, @grantee_name
   WHILE (@@fetch_status = 0)
   BEGIN
      --PRINT @state_desc + N' ' + @permission_name + N' ON ' + @object_name + N' TO ' + @grantee_name
      BEGIN TRY
        EXEC (@state_desc + N' ' + @permission_name + N' ON ' + @object_name + N' TO ' + @grantee_name)
      END TRY
      BEGIN CATCH
      END CATCH
      FETCH NEXT FROM perms_set_cursor INTO @state_desc, @permission_name, @object_name, @grantee_name
   END
DEALLOCATE perms_set_cursor
--remove public from SQLAgent Shiloh procedures
PRINT ''
PRINT 'Revoke any permission to public role...'
BEGIN TRY
REVOKE ALL ON sp_add_alert																	FROM PUBLIC
REVOKE ALL ON sp_add_category																FROM PUBLIC
REVOKE ALL ON sp_add_job																		FROM PUBLIC
REVOKE ALL ON sp_add_jobschedule														FROM PUBLIC
REVOKE ALL ON sp_add_jobserver															FROM PUBLIC
REVOKE ALL ON sp_add_jobstep																FROM PUBLIC
REVOKE ALL ON sp_add_notification														FROM PUBLIC
REVOKE ALL ON sp_add_operator																FROM PUBLIC
REVOKE ALL ON sp_add_targetservergroup											FROM PUBLIC
REVOKE ALL ON sp_add_targetsvrgrp_member										FROM PUBLIC
REVOKE ALL ON sp_apply_job_to_targets												FROM PUBLIC
REVOKE ALL ON sp_convert_jobid_to_char											FROM PUBLIC
REVOKE ALL ON sp_delete_alert																FROM PUBLIC
REVOKE ALL ON sp_delete_all_msx_jobs												FROM PUBLIC
REVOKE ALL ON sp_delete_category														FROM PUBLIC
REVOKE ALL ON sp_delete_job																	FROM PUBLIC
REVOKE ALL ON sp_delete_job_references											FROM PUBLIC
REVOKE ALL ON sp_delete_jobschedule													FROM PUBLIC
REVOKE ALL ON sp_delete_jobserver														FROM PUBLIC
REVOKE ALL ON sp_delete_jobstep															FROM PUBLIC
REVOKE ALL ON sp_delete_notification												FROM PUBLIC
REVOKE ALL ON sp_delete_operator														FROM PUBLIC
REVOKE ALL ON sp_delete_targetserver												FROM PUBLIC
REVOKE ALL ON sp_delete_targetservergroup										FROM PUBLIC
REVOKE ALL ON sp_delete_targetsvrgrp_member									FROM PUBLIC
REVOKE ALL ON sp_downloaded_row_limiter											FROM PUBLIC
REVOKE ALL ON sp_generate_server_description								FROM PUBLIC
REVOKE ALL ON sp_generate_target_server_job_assignment_sql	FROM PUBLIC
REVOKE ALL ON sp_get_chunked_jobstep_params									FROM PUBLIC
REVOKE ALL ON sp_get_composite_job_info											FROM PUBLIC
REVOKE ALL ON sp_get_job_alerts															FROM PUBLIC
REVOKE ALL ON sp_get_jobstep_db_username										FROM PUBLIC
REVOKE ALL ON sp_get_message_description										FROM PUBLIC
REVOKE ALL ON sp_get_schedule_description										FROM PUBLIC
REVOKE ALL ON sp_get_sqlagent_properties										FROM PUBLIC
REVOKE ALL ON sp_help_alert																	FROM PUBLIC
REVOKE ALL ON sp_help_category															FROM PUBLIC
REVOKE ALL ON sp_help_downloadlist													FROM PUBLIC
REVOKE ALL ON sp_help_job																		FROM PUBLIC
REVOKE ALL ON sp_help_jobhistory														FROM PUBLIC
REVOKE ALL ON sp_help_jobschedule														FROM PUBLIC
REVOKE ALL ON sp_help_jobserver															FROM PUBLIC
REVOKE ALL ON sp_help_jobstep																FROM PUBLIC
REVOKE ALL ON sp_help_notification													FROM PUBLIC
REVOKE ALL ON sp_help_operator															FROM PUBLIC
REVOKE ALL ON sp_help_operator_jobs													FROM PUBLIC
REVOKE ALL ON sp_help_targetserver													FROM PUBLIC
REVOKE ALL ON sp_help_targetservergroup											FROM PUBLIC
REVOKE ALL ON sp_is_sqlagent_starting												FROM PUBLIC
REVOKE ALL ON sp_jobhistory_row_limiter											FROM PUBLIC
REVOKE ALL ON sp_manage_jobs_by_login												FROM PUBLIC
REVOKE ALL ON sp_msx_defect																	FROM PUBLIC
REVOKE ALL ON sp_msx_enlist																	FROM PUBLIC
REVOKE ALL ON sp_multi_server_job_summary										FROM PUBLIC
REVOKE ALL ON sp_post_msx_operation													FROM PUBLIC
REVOKE ALL ON sp_purge_jobhistory														FROM PUBLIC
REVOKE ALL ON sp_remove_job_from_targets										FROM PUBLIC
REVOKE ALL ON sp_resync_targetserver												FROM PUBLIC
REVOKE ALL ON sp_sem_add_message														FROM PUBLIC
REVOKE ALL ON sp_sem_drop_message														FROM PUBLIC
REVOKE ALL ON sp_set_local_time															FROM PUBLIC
REVOKE ALL ON sp_set_sqlagent_properties										FROM PUBLIC
REVOKE ALL ON sp_sqlagent_check_msx_version									FROM PUBLIC
REVOKE ALL ON sp_sqlagent_get_perf_counters									FROM PUBLIC
REVOKE ALL ON sp_sqlagent_get_startup_info									FROM PUBLIC
REVOKE ALL ON sp_sqlagent_has_server_access									FROM PUBLIC
REVOKE ALL ON sp_sqlagent_log_jobhistory										FROM PUBLIC
REVOKE ALL ON sp_sqlagent_notify														FROM PUBLIC
REVOKE ALL ON sp_sqlagent_probe_msx													FROM PUBLIC
REVOKE ALL ON sp_sqlagent_refresh_job												FROM PUBLIC
REVOKE ALL ON sp_start_job																	FROM PUBLIC
REVOKE ALL ON sp_stop_job																		FROM PUBLIC
REVOKE ALL ON sp_target_server_summary											FROM PUBLIC
REVOKE ALL ON sp_uniquetaskname															FROM PUBLIC
REVOKE ALL ON sp_update_alert																FROM PUBLIC
REVOKE ALL ON sp_update_category														FROM PUBLIC
REVOKE ALL ON sp_update_job																	FROM PUBLIC
REVOKE ALL ON sp_update_jobschedule													FROM PUBLIC
REVOKE ALL ON sp_update_jobstep															FROM PUBLIC
REVOKE ALL ON sp_update_notification												FROM PUBLIC
REVOKE ALL ON sp_update_operator														FROM PUBLIC
REVOKE ALL ON sp_update_targetservergroup										FROM PUBLIC
REVOKE ALL ON sp_verify_alert																FROM PUBLIC
REVOKE ALL ON sp_verify_category														FROM PUBLIC
REVOKE ALL ON sp_verify_job																	FROM PUBLIC
REVOKE ALL ON sp_verify_job_date														FROM PUBLIC
REVOKE ALL ON sp_verify_job_identifiers											FROM PUBLIC
REVOKE ALL ON sp_verify_job_time														FROM PUBLIC
REVOKE ALL ON sp_verify_jobproc_caller											FROM PUBLIC
REVOKE ALL ON sp_verify_jobstep															FROM PUBLIC
REVOKE ALL ON sp_verify_notification												FROM PUBLIC
REVOKE ALL ON sp_verify_operator														FROM PUBLIC
REVOKE ALL ON sp_verify_performance_condition								FROM PUBLIC
REVOKE ALL ON sp_verify_subsystem														FROM PUBLIC
END TRY
BEGIN CATCH
END CATCH
go
--remove public permission from Shiloh objects that have been secured in Shiloh and overwritten during instmsdb.sql
--create a temporary table with objects granted to public during execution of instmsdb.sql
CREATE TABLE #instmsdb_public_objects(object_name sysname)

INSERT INTO #instmsdb_public_objects VALUES (N'backupfile')
INSERT INTO #instmsdb_public_objects VALUES (N'backupmediafamily')
INSERT INTO #instmsdb_public_objects VALUES (N'backupmediaset')
INSERT INTO #instmsdb_public_objects VALUES (N'backupset')
INSERT INTO #instmsdb_public_objects VALUES (N'restorehistory')
INSERT INTO #instmsdb_public_objects VALUES (N'restorefile')
INSERT INTO #instmsdb_public_objects VALUES (N'restorefilegroup')
INSERT INTO #instmsdb_public_objects VALUES (N'logmarkhistory')
INSERT INTO #instmsdb_public_objects VALUES (N'suspect_pages')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_get_dtsversion')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_make_dtspackagename')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_add_dtspackage')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_drop_dtspackage')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_reassign_dtspackageowner')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_get_dtspackage')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_enum_dtspackages')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_log_dtspackage_begin')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_log_dtspackage_end')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_log_dtsstep_begin')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_log_dtsstep_end')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_log_dtstask')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_enum_dtspackagelog')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_enum_dtssteplog')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_enum_dtstasklog')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_dump_dtslog_all')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_dump_dtspackagelog')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_dump_dtssteplog')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_dump_dtstasklog')
go

DECLARE @object_name		  sysname
DECLARE @tsql             nvarchar(300)
DECLARE public_remove_cursor CURSOR LOCAL FOR 
	SELECT object_name FROM #instmsdb_public_objects

OPEN public_remove_cursor
   FETCH NEXT FROM public_remove_cursor INTO @object_name
   WHILE (@@fetch_status = 0)
   BEGIN
      --PRINT @state_desc + N' ' + @permission_name + N' ON ' + @object_name + N' TO ' + @grantee_name
      BEGIN TRY
      --if this object exists in 8.0 msdb and is granted to public no op otherwise remove permission to public on it
        IF (EXISTS (SELECT * FROM msdb.dbo.upgrade_perms 
              WHERE UPPER(object_name collate SQL_Latin1_General_CP1_CS_AS ) = UPPER(@object_name collate SQL_Latin1_General_CP1_CS_AS )))
            AND (NOT EXISTS (SELECT * FROM msdb.dbo.upgrade_perms WHERE object_name = @object_name 
                 AND UPPER(grantee_name collate SQL_Latin1_General_CP1_CS_AS ) = N'PUBLIC'))
          BEGIN
            SELECT @tsql = N'REVOKE ALL ON ' + QUOTENAME(@object_name) + N' FROM PUBLIC'
            PRINT @tsql
            EXEC (@tsql)
          END
      END TRY
      BEGIN CATCH
      END CATCH
      FETCH NEXT FROM public_remove_cursor INTO @object_name
   END
DEALLOCATE public_remove_cursor
go
DROP TABLE #instmsdb_public_objects
DROP TABLE msdb.dbo.upgrade_perms
go

--------------------------------------------------------------------------------
--update proxy account
--proxy update batch
--read sysadminonly flag
DECLARE @ret              INT
DECLARE @proxy_id         INT
DECLARE @job_id           UNIQUEIDENTIFIER
DECLARE @step_id          INT
DECLARE @subsystem        sysname
DECLARE @owner_name       NVARCHAR(256)
DECLARE @full_name        sysname
DECLARE @owner_sid        VARBINARY(85)
DECLARE @is_sysadmin      INT

--walk throu all job steps excluding TSQL jobsteps
DECLARE jobsteps_cursor CURSOR LOCAL FOR
SELECT js.job_id, js.step_id, js.subsystem, SUSER_SNAME(j.owner_sid)
FROM sysjobs j JOIN sysjobsteps js ON j.job_id = js.job_id
WHERE UPPER(js.subsystem collate SQL_Latin1_General_CP1_CS_AS) <> N'TSQL'
AND SUSER_SNAME(j.owner_sid) IS NOT NULL
FOR READ ONLY

--wals thru all non sysadmin job owners
DECLARE job_nonsysadmin_owners_cursor CURSOR LOCAL FOR
SELECT DISTINCT j.owner_sid FROM sysjobs j 
FOR READ ONLY

OPEN job_nonsysadmin_owners_cursor
FETCH NEXT FROM job_nonsysadmin_owners_cursor INTO @owner_sid
WHILE (@@fetch_status = 0)
BEGIN
	SELECT @owner_name = SUSER_SNAME(@owner_sid)
  IF @owner_name IS NOT NULL
  BEGIN
    --is job owner member of sysadmin role?
    BEGIN TRY
      EXECUTE AS LOGIN=@owner_name -- impersonate 
      SELECT @is_sysadmin = ISNULL(IS_SRVROLEMEMBER('sysadmin'),0) -- check role membership 
      REVERT -- revert back
    END TRY
    BEGIN CATCH
      SET @is_sysadmin = 0
    END CATCH
          
    IF @is_sysadmin = 0
    BEGIN
	    --add job_owner to the SQLAgentUserRole msdb role in order to permit the job owner to handle his jobs
	    --has this login a user in msdb?
	    IF NOT EXISTS(SELECT * FROM sys.database_principals WHERE (sid = @owner_sid) OR (LOWER(name collate SQL_Latin1_General_CP1_CS_AS) = LOWER(@owner_name collate SQL_Latin1_General_CP1_CS_AS)))
	    BEGIN
		    PRINT ''
		    PRINT 'Granting login access''' + @owner_name + ''' to msdb database...'
		    BEGIN TRY
		      EXEC sp_grantdbaccess @loginame = @owner_name
        END TRY
        BEGIN CATCH
          RAISERROR('A problem was encountered granting access to MSDB database for login ''%s''. Make sure this login is provisioned with SQLServer and rerun sqlagent_msdb_upgrade.sql ', 10, 127) WITH LOG
        END CATCH		      
	    END

	    PRINT ''
	    PRINT 'Adding user ''' + @owner_name + ''' to SQLAgentUserRole msdb role...'
		  BEGIN TRY
	      EXEC sp_addrolemember @rolename = 'SQLAgentUserRole', @membername = @owner_name
      END TRY
      BEGIN CATCH
        RAISERROR('A problem was encountered adding user ''%s'' to SQLAgentUserRole. Make sure this is a valid user in MSDB database and rerun sqlagent_msdb_upgrade.sql ', 10, 127) WITH LOG
      END CATCH		      
	  END
	END
	FETCH NEXT FROM job_nonsysadmin_owners_cursor INTO @owner_sid
END
DEALLOCATE job_nonsysadmin_owners_cursor
  

--credential created from LSA values in the file src\upgrade_scripts\sqlagent100_upgrade.sql
IF EXISTS (SELECT credential_id FROM master.sys.credentials  WHERE  name = N'UpgradedCredential')
BEGIN
  IF (NOT EXISTS(SELECT * FROM sysproxies WHERE name = N'UpgradedProxyAccount'))
  BEGIN  
    PRINT 'Update proxy account'
    SELECT @ret = 0

    --create the proxy first
    PRINT ''
    PRINT 'Adding Proxy...'
    BEGIN TRY --prevent sp_add_proxy raising severity 16 error that will terminate upgrade scritp when proxy account has bad info
      EXEC @ret = sp_add_proxy @proxy_name      = N'UpgradedProxyAccount',
                                @credential_name = N'UpgradedCredential',
                                @proxy_id        = @proxy_id OUTPUT
    END TRY
    BEGIN CATCH
      SET @ret = 1
    END CATCH      
                              
    IF @ret <> 0
    BEGIN
      RAISERROR('A problem was encountered updating proxy account. Verify that user name and secret of credential [UpgradedCredential] are correct and rerun sqlagent_msdb_upgrade.sql ', 10, 127) WITH LOG
    END
    ELSE
    BEGIN
      OPEN jobsteps_cursor
      FETCH NEXT FROM jobsteps_cursor INTO @job_id, @step_id, @subsystem, @owner_name

      WHILE (@@fetch_status = 0)
      BEGIN
        --is job owner member of sysadmin role?
        BEGIN TRY
          EXECUTE AS LOGIN = @owner_name -- impersonate 
          SELECT @is_sysadmin = ISNULL(IS_SRVROLEMEMBER('sysadmin'),0) -- check role membership 
          REVERT -- revert back
        END TRY
        BEGIN CATCH
          SET @is_sysadmin = 0
        END CATCH
        
        IF @is_sysadmin = 0
        BEGIN        
          IF NOT EXISTS(SELECT * FROM sysproxysubsystem ps JOIN syssubsystems s 
                        ON ps.subsystem_id = s.subsystem_id
                        WHERE s.subsystem = @subsystem
                        AND ps.proxy_id = @proxy_id)
          BEGIN
            PRINT 'Grant permission to proxy ''UpgradedProxyAccount'' for subsystem ''' + @subsystem + '''...'
            BEGIN TRY
              EXEC @ret = sp_grant_proxy_to_subsystem @subsystem_name = @subsystem,
											          @proxy_id       = @proxy_id
					  END TRY
					  BEGIN CATCH
					    SET @ret = 1
					  END CATCH
            IF @ret <> 0
            BEGIN
              RAISERROR('FAILED TO GRANT PERMISSION TO PROXY (%d) FOR SUBSYSTEM ''%s''.', 10, 127, @proxy_id, @subsystem ) WITH LOG
            END
	        END

          IF NOT EXISTS(SELECT * FROM sysproxylogin WHERE proxy_id = @proxy_id AND sid = SUSER_SID(@owner_name))
          BEGIN
            PRINT 'Grant login ''' + @owner_name + '''  permission to use proxy ''UpgradedProxyAccount'' for subsystem ''' + @subsystem + '''...'
            BEGIN TRY
              EXEC @ret = sp_grant_login_to_proxy @proxy_id       = @proxy_id,
                                                  @login_name     = @owner_name
            END TRY
					  BEGIN CATCH
					    SET @ret = 1
					  END CATCH
            IF @ret <> 0
            BEGIN
              RAISERROR('FAILED TO GRANT PERMISSION TO LOGIN ''%s'' FOR PROXY (%d).', 10, 127, @owner_name, @proxy_id) WITH LOG
            END
          END

          --PRINT 'Update sysjobsteps...'
          UPDATE sysjobsteps SET proxy_id = @proxy_id WHERE job_id = @job_id and step_id = @step_id
        END --is_sysadmin = 0
        FETCH NEXT FROM jobsteps_cursor INTO @job_id, @step_id, @subsystem, @owner_name
      END --WHILE (@@fetch_status = 0)
      CLOSE jobsteps_cursor
    END
  END -- NOT EXISTS(SELECT * FROM sysproxies WHERE name = N'UpgradedProxyAccount')
END  -- EXISTS (SELECT credential_id FROM master.sys.credentials  WHERE  name = N'AgentCred80') 
DEALLOCATE jobsteps_cursor                          
go

--msx proxy upgrade
DECLARE @credential_id          INT
--credential created from 80 lSA values in the file src\upgrade_scripts\sqlagent100_upgrade.sql
IF EXISTS (SELECT credential_id FROM master.sys.credentials  WHERE  name = N'UpgradedMSXCredential')
BEGIN

    SELECT @credential_id = credential_id FROM master.sys.credentials
    WHERE  name = N'UpgradedMSXCredential'

    --set credential_id to agent registry
      EXECUTE master.dbo.xp_instance_regwrite  'HKEY_LOCAL_MACHINE',
                                      'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                      'MSXCredentialID',
                                      'REG_DWORD', 
                                      @credential_id
END      
go

-- Complete replication job security meta-data upgrades
IF OBJECT_ID('sys.sp_vupgrade_replsecurity_metadata', 'P') IS NOT NULL
BEGIN
	PRINT 'Performing replication job security meta-data upgrades...'
	EXECUTE master.sys.sp_vupgrade_replsecurity_metadata
END
ELSE
BEGIN
	-- "The replication security meta-data could not be upgraded for all database(s). Please ensure that entire server is upgraded and re-execute sp_vupgrade_replsecurity_metadata."
	RAISERROR(21450, 10, -1, 'security meta-data', 'all',  'entire server', 'master.sys.sp_vupgrade_replsecurity_metadata') WITH LOG	
END
GO


-- Log Shipping Upgrade
declare @retcode int
PRINT ''
PRINT 'Upgrading SQL Server Log Shipping...'
exec @retcode = master.sys.sp_upgrade_log_shipping
if @retcode != 0 or @@error != 0
begin
  RAISERROR('Upgrade of Log Shipping for SQL Server did not complete successfully. After upgrade, re-execute sp_upgrade_log_shipping from master database.', 10, 1) WITH LOG
end
PRINT 'Upgraded SQL Server Log Shipping successfully.'
go

-----------------------------------------------------------------------------
--Drop legacy objects
CREATE TABLE #instmsdb_legacy_objects(object_name sysname, type_name sysname, type_id nvarchar(2))
--tables
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.mswebtasks', N'TABLE', N'T' )
--procedures
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_helphistory', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_helptask', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_insmswebtask', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_purgehistory', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_sem_get_perf_counter_help', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_updatetask', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_updmswebtask', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_verify_jobschedule', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_verifytaskid', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_check_localserver_name', N'PROCEDURE', N'P' )

--views
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sysalternates', N'VIEW', N'V' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.systasks', N'VIEW', N'V' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.systasks_view', N'VIEW', N'V' )
go

DECLARE @object_name		  sysname
DECLARE @type_name		    sysname
DECLARE @type_id 		      nvarchar(2)

DECLARE @tsql             nvarchar(300)
DECLARE legacy_remove_cursor CURSOR LOCAL FOR 
	SELECT object_name, type_name, type_id FROM #instmsdb_legacy_objects

OPEN legacy_remove_cursor
   FETCH NEXT FROM legacy_remove_cursor INTO @object_name, @type_name, @type_id
   WHILE (@@fetch_status = 0)
   BEGIN
      BEGIN TRY
        IF (OBJECT_ID(@object_name, @type_id) IS NOT NULL)
          BEGIN
            SELECT @tsql = N'DROP ' + @type_name + N' ' +  @object_name
            PRINT @tsql
            EXEC (@tsql)
          END
      END TRY
      BEGIN CATCH
      END CATCH
      FETCH NEXT FROM legacy_remove_cursor INTO @object_name, @type_name, @type_id
   END
CLOSE legacy_remove_cursor   
DEALLOCATE legacy_remove_cursor
go
DROP TABLE #instmsdb_legacy_objects
go
--other legacy objects CTP15 to RTM only
BEGIN TRY
if exists (select * from sys.database_principals where name = 'MS_AgentSigningCertificateUser')
   drop user MS_AgentSigningCertificateUser

if exists (select * from sys.server_principals where name = 'MS_AgentSigningCertificateLogin')
   drop login MS_AgentSigningCertificateLogin
   
-- remove the MaintenanceUserRole role CTP15 to RTM only
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysusers
            WHERE (name = N'MaintenanceUserRole')
              AND (issqlrole = 1)))
BEGIN
  BEGIN
    EXECUTE msdb.dbo.sp_droprole @rolename = N'MaintenanceUserRole'
  END
END
END TRY
BEGIN CATCH
END CATCH
go
-------------------------------------------------------------

PRINT '------------------------------------'
PRINT 'Moving 2005 SSIS Data to 2008 tables'
PRINT '------------------------------------'

--
-- Add the old roles to the new SSIS role membership
--

PRINT 'Mapping SSIS yukon roles to katmai roles...'
GO

if exists (select * from dbo.sysusers where [name] = N'db_dtsadmin' and [issqlrole] = 1)
BEGIN
EXEC sp_addrolemember N'db_ssisadmin', N'db_dtsadmin'
END
GO

if exists (select * from dbo.sysusers where [name] = N'db_dtsltduser' and [issqlrole] = 1)
BEGIN
EXEC sp_addrolemember N'db_ssisltduser', N'db_dtsltduser'
END
GO

if exists (select * from dbo.sysusers where [name] = N'db_dtsoperator' and [issqlrole] = 1)
BEGIN
EXEC sp_addrolemember N'db_ssisoperator', N'db_dtsoperator'
END
GO

--
-- Move folders from sysdtspackagefolders90 to sysssispackagefolders
--

PRINT 'Moving package folders...'
GO

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sysdtspackagefolders90]') AND type = N'U') 
INSERT INTO [msdb].[dbo].[sysssispackagefolders]
           ([folderid]
           ,[parentfolderid]
           ,[foldername])
    SELECT [folderid]
           ,[parentfolderid]
           ,[foldername]
    FROM [msdb].[dbo].[sysdtspackagefolders90]
    WHERE [folderid] != '00000000-0000-0000-0000-000000000000' -- Root folder
      AND [folderid] != '08aa12d5-8f98-4dab-a4fc-980b150a5dc8' -- Maintenance Plans

GO

--
-- Move packages from sysdtspackages90 to sysssispackages
--

PRINT 'Moving packages...'
GO

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sysdtspackages90]') AND type = N'U') 
INSERT INTO [msdb].[dbo].[sysssispackages]
           ([name]
           ,[id]
           ,[description]
           ,[createdate]
           ,[folderid]
           ,[ownersid]
           ,[packagedata]
           ,[packageformat]
           ,[packagetype]
           ,[vermajor]
           ,[verminor]
           ,[verbuild]
           ,[vercomments]
           ,[verid]
           ,[isencrypted]
           ,[readrolesid]
           ,[writerolesid])
    SELECT [name]
           ,[id]
           ,[description]
           ,[createdate]
           ,[folderid]
           ,[ownersid]
           ,[packagedata]
           ,[packageformat]
           ,[packagetype]
           ,[vermajor]
           ,[verminor]
           ,[verbuild]
           ,[vercomments]
           ,[verid]
           ,[isencrypted]
           ,[readrolesid]
           ,[writerolesid]
    FROM [msdb].[dbo].[sysdtspackages90]

GO

--
-- Move log data from sysdtslog90 to sysssislog
--

PRINT 'Moving logs...'
GO

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sysdtslog90]') AND type = N'U') 
INSERT INTO [msdb].[dbo].[sysssislog]
           ([event]
           ,[computer]
           ,[operator]
           ,[source]
           ,[sourceid]
           ,[executionid]
           ,[starttime]
           ,[endtime]
           ,[datacode]
           ,[databytes]
           ,[message])
    SELECT [event]
           ,[computer]
           ,[operator]
           ,[source]
           ,[sourceid]
           ,[executionid]
           ,[starttime]
           ,[endtime]
           ,[datacode]
           ,[databytes]
           ,[message]
    FROM [msdb].[dbo].[sysdtslog90]

GO

--
-- Drop yukon objects
--

PRINT 'Dropping yukon stored procedures...'
GO

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_setpackageroles]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_setpackageroles]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_getpackageroles]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_getpackageroles]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_deletepackage]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_deletepackage] 

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_getpackage]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_getpackage]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_putpackage]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_putpackage]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_checkexists]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_checkexists]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_listpackages]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_listpackages]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_addlogentry]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_addlogentry]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_deletefolder]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_deletefolder] 

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_addfolder]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_addfolder]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_renamefolder]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_renamefolder]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_getfolder]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_getfolder]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_listfolders]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_listfolders]

GO

PRINT 'Dropping yukon tables...'
GO

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sysdtslog90]') AND type = N'U') 
DROP TABLE [dbo].[sysdtslog90]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sysdtspackages90]') AND type = N'U') 
DROP TABLE [dbo].[sysdtspackages90]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sysdtspackagefolders90]') AND type = N'U')  
DROP TABLE [dbo].[sysdtspackagefolders90]

GO

--
-- Create a view for the logs table for backwards compatibility
--

PRINT 'Creating sysdtslog90 view...'
GO

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sysdtslog90]') and type = N'V')
BEGIN
	DROP VIEW [dbo].[sysdtslog90]
END

GO

CREATE VIEW [dbo].[sysdtslog90]
AS
	SELECT [id]
		  ,[event]
		  ,[computer]
		  ,[operator]
		  ,[source]
		  ,[sourceid]
		  ,[executionid]
		  ,[starttime]
		  ,[endtime]
		  ,[datacode]
		  ,[databytes]
		  ,[message]
	  FROM [msdb].[dbo].[sysssislog]

GO

-------------------------------------------------------------


/*********************************************************************/
/* Create auxilary procedure to enable OBD (Off By Default component */
/*********************************************************************/
IF (OBJECT_ID(N'tempdb..#sp_enable_component', 'P') IS NOT NULL)
BEGIN
    DROP PROCEDURE [tempdb]..[#sp_enable_component]
END
GO 

CREATE PROCEDURE #sp_enable_component     
   @comp_name     sysname, 
   @advopt_old_value    INT OUT, 
   @comp_old_value   INT OUT 
AS
   BEGIN
   SELECT @advopt_old_value=cast(value_in_use as int) from sys.configurations where name = 'show advanced options';
   SELECT @comp_old_value=cast(value_in_use as int) from sys.configurations where name = @comp_name; 
   EXEC sp_configure 'show advanced options',1;
   RECONFIGURE WITH OVERRIDE;
   EXEC sp_configure @comp_name, 1; 
   RECONFIGURE WITH OVERRIDE;
   END
go

IF (OBJECT_ID(N'tempdb..#sp_restore_component_state ', 'P') IS NOT NULL)
BEGIN
    DROP PROCEDURE [tempdb]..[#sp_restore_component_state ]
END
GO 

CREATE PROCEDURE #sp_restore_component_state 
   @comp_name     sysname, 
   @advopt_old_value    INT, 
   @comp_old_value   INT 
AS
   BEGIN
   EXEC sp_configure @comp_name, @comp_old_value; 
   RECONFIGURE WITH OVERRIDE;
   EXEC sp_configure 'show advanced options',@advopt_old_value;
   RECONFIGURE WITH OVERRIDE;
   END
GO


/**************************************************************/
/* DMF Post-upgrade steps                                      */
/**************************************************************/
--
-- >>> CTP5 -> CTP6 Upgrade
--
-- Restoring previously saved data and converting TargetSets to ObjectSets
IF (OBJECT_ID('dbo.dmf_upgrade', 'U') IS NOT NULL)
BEGIN
	BEGIN TRANSACTION 

	-- Restore policies
	SET IDENTITY_INSERT dbo.syspolicy_policies_internal ON
	INSERT dbo.syspolicy_policies_internal (policy_id,name,condition_id,root_condition_id,date_created,execution_mode,policy_category_id,schedule_uid,description,help_text,help_link,is_enabled,job_id,created_by,modified_by,date_modified)
		SELECT policy_id,name,condition_id,root_condition_id,date_created,execution_mode,policy_category_id,schedule_uid,description,help_text,help_link,is_enabled,job_id,created_by,modified_by,date_modified
		FROM msdb.dbo.tmp_syspolicy_policies_internal
	SET IDENTITY_INSERT dbo.syspolicy_policies_internal OFF
	
	-- Restore Health state 
	SET IDENTITY_INSERT dbo.syspolicy_system_health_state_internal ON
	INSERT dbo.syspolicy_system_health_state_internal (health_state_id,policy_id,last_run_date,target_query_expression_with_id,target_query_expression,result)
		SELECT * FROM msdb.dbo.tmp_syspolicy_system_health_state_internal
	SET IDENTITY_INSERT dbo.syspolicy_system_health_state_internal OFF
	
	-- Restore Execution history
	SET IDENTITY_INSERT dbo.syspolicy_policy_execution_history_internal ON
	INSERT dbo.syspolicy_policy_execution_history_internal (history_id,policy_id,start_date,end_date,result,is_full_run,exception_message,exception) 
		SELECT * FROM msdb.dbo.tmp_syspolicy_policy_execution_history_internal
	SET IDENTITY_INSERT dbo.syspolicy_policy_execution_history_internal OFF

	SET IDENTITY_INSERT dbo.syspolicy_policy_execution_history_details_internal ON
	INSERT dbo.syspolicy_policy_execution_history_details_internal (detail_id,history_id,target_query_expression,target_query_expression_with_id,execution_date,result,result_detail,exception_message,exception) 
		SELECT * FROM msdb.dbo.tmp_syspolicy_policy_execution_history_details_internal
	SET IDENTITY_INSERT dbo.syspolicy_policy_execution_history_details_internal OFF
	
	-- Convert TargetSets to ObjectSets
	SET IDENTITY_INSERT dbo.syspolicy_target_sets_internal ON

	DECLARE @policy_id int, @policy_name sysname, @facet nvarchar(max), @os_name sysname, @os_id int, @ts_id int

	DECLARE ts_cur CURSOR FOR
		SELECT p.policy_id, p.name, c.facet, ts.target_set_id
		FROM msdb.dbo.tmp_syspolicy_target_sets_internal ts JOIN dbo.syspolicy_policies p ON (ts.policy_id = p.policy_id)
			JOIN dbo.syspolicy_conditions c ON (p.condition_id = c.condition_id)
		ORDER BY p.policy_id, ts.target_set_id
		
	OPEN ts_cur
	FETCH ts_cur INTO @policy_id , @policy_name , @facet, @ts_id
	WHILE @@FETCH_STATUS = 0
	BEGIN
		-- make sure ObjectSet name is unique
		SET @os_name = LEFT(@policy_name,118) + '_ObjectSet'
		IF EXISTS (SELECT * FROM dbo.syspolicy_object_sets WHERE object_set_name = @os_name)
		BEGIN
			SET @os_name = LEFT(@policy_name,82) + CAST(NEWID() AS nchar(36)) + '_ObjectSet'
		END
		
		-- if we go through multi-TS ObjectSet we only need to add it once
		-- cursor sort order guarantees @os_id remains correct 
		IF 0 = (SELECT ISNULL(object_set_id, 0) FROM syspolicy_policies WHERE policy_id = @policy_id)
		BEGIN
			Exec sp_syspolicy_add_object_set @os_name, @facet, @os_id OUTPUT 
			UPDATE syspolicy_policies_internal
				SET object_set_id = @os_id
				WHERE policy_id = @policy_id
		END
		INSERT syspolicy_target_sets_internal (target_set_id,object_set_id,type_skeleton,type,enabled) 
			SELECT target_set_id,@os_id,type_skeleton,type,1 
			FROM msdb.dbo.tmp_syspolicy_target_sets_internal WHERE target_set_id = @ts_id
		FETCH ts_cur INTO @policy_id , @policy_name , @facet, @ts_id
	END

	CLOSE ts_cur
	DEALLOCATE ts_cur

	SET IDENTITY_INSERT dbo.syspolicy_target_sets_internal OFF

	SET IDENTITY_INSERT dbo.syspolicy_target_set_levels_internal ON
	INSERT syspolicy_target_set_levels_internal (target_set_level_id,target_set_id,type_skeleton,condition_id,level_name)
		SELECT target_set_level_id,target_set_id,type_skeleton,condition_id,level_name 
		FROM msdb.dbo.tmp_syspolicy_target_set_levels_internal

	SET IDENTITY_INSERT dbo.syspolicy_target_set_levels_internal OFF

	-- Add missing levels for MultipartName sets
	DECLARE @tsl_id int
	SET @ts_id = 0
	SET @tsl_id = 0

	DECLARE @mpn_facet_id int
	SELECT @mpn_facet_id = management_facet_id FROM dbo.syspolicy_management_facets 
		WHERE name = 'IMultipartNameFacet'

	DECLARE os_cur CURSOR FOR
		SELECT object_set_id
		FROM dbo.syspolicy_object_sets_internal  
		WHERE facet_id = @mpn_facet_id 

	OPEN os_cur
	FETCH os_cur INTO @os_id
	WHILE @@FETCH_STATUS = 0
	BEGIN
		IF NOT EXISTS (SELECT * FROM syspolicy_target_sets_internal WHERE object_set_id = @os_id AND type = 'PROCEDURE')
		BEGIN
			Exec sp_syspolicy_add_target_set @object_set_id=@os_id, @type_skeleton='Server/Database/StoredProcedure', @type='PROCEDURE', @enabled=0, @target_set_id = @ts_id OUTPUT
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database', @condition_id=NULL, @level_name='Database', @target_set_level_id=@tsl_id  
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database/StoredProcedure', @condition_id=NULL, @level_name='StoredProcedure', @target_set_level_id=@tsl_id  
		END
		IF NOT EXISTS (SELECT * FROM syspolicy_target_sets_internal WHERE object_set_id = @os_id AND type = 'SYNONYM')
		BEGIN
			Exec sp_syspolicy_add_target_set @object_set_id=@os_id, @type_skeleton='Server/Database/Synonym', @type='SYNONYM', @enabled=0, @target_set_id = @ts_id OUTPUT
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database', @condition_id=NULL, @level_name='Database', @target_set_level_id=@tsl_id  
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database/Synonym', @condition_id=NULL, @level_name='Synonym', @target_set_level_id=@tsl_id  
		END
		IF NOT EXISTS (SELECT * FROM syspolicy_target_sets_internal WHERE object_set_id = @os_id AND type = 'TABLE')
		BEGIN
			Exec sp_syspolicy_add_target_set @object_set_id=@os_id, @type_skeleton='Server/Database/Table', @type='TABLE', @enabled=0, @target_set_id = @ts_id OUTPUT
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database', @condition_id=NULL, @level_name='Database', @target_set_level_id=@tsl_id  
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database/Table', @condition_id=NULL, @level_name='Table', @target_set_level_id=@tsl_id  
		END
		IF NOT EXISTS (SELECT * FROM syspolicy_target_sets_internal WHERE object_set_id = @os_id AND type = 'FUNCTION')
		BEGIN
			Exec sp_syspolicy_add_target_set @object_set_id=@os_id, @type_skeleton='Server/Database/UserDefinedFunction', @type='FUNCTION', @enabled=0, @target_set_id = @ts_id OUTPUT
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database', @condition_id=NULL, @level_name='Database', @target_set_level_id=@tsl_id  
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database/UserDefinedFunction', @condition_id=NULL, @level_name='UserDefinedFunction', @target_set_level_id=@tsl_id  
		END
		IF NOT EXISTS (SELECT * FROM syspolicy_target_sets_internal WHERE object_set_id = @os_id AND type = 'TYPE')
		BEGIN
			Exec sp_syspolicy_add_target_set @object_set_id=@os_id, @type_skeleton='Server/Database/UserDefinedType', @type='TYPE', @enabled=0, @target_set_id = @ts_id OUTPUT
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database', @condition_id=NULL, @level_name='Database', @target_set_level_id=@tsl_id  
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database/UserDefinedType', @condition_id=NULL, @level_name='UserDefinedType', @target_set_level_id=@tsl_id  
		END
		IF NOT EXISTS (SELECT * FROM syspolicy_target_sets_internal WHERE object_set_id = @os_id AND type = 'VIEW')
		BEGIN
			Exec sp_syspolicy_add_target_set @object_set_id=@os_id, @type_skeleton='Server/Database/View', @type='VIEW', @enabled=0, @target_set_id = @ts_id OUTPUT
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database', @condition_id=NULL, @level_name='Database', @target_set_level_id=@tsl_id  
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database/View', @condition_id=NULL, @level_name='View', @target_set_level_id=@tsl_id  
		END
		IF NOT EXISTS (SELECT * FROM syspolicy_target_sets_internal WHERE object_set_id = @os_id AND type = 'XMLSCHEMACOLLECTION')
		BEGIN
			Exec sp_syspolicy_add_target_set @object_set_id=@os_id, @type_skeleton='Server/Database/XmlSchemaCollection', @type='XMLSCHEMACOLLECTION', @enabled=0, @target_set_id = @ts_id OUTPUT
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database', @condition_id=NULL, @level_name='Database', @target_set_level_id=@tsl_id  
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database/XmlSchemaCollection', @condition_id=NULL, @level_name='XmlSchemaCollection', @target_set_level_id=@tsl_id  
		END
		
		FETCH os_cur INTO @os_id
	END

	CLOSE os_cur
	DEALLOCATE os_cur

	-- Clean up upgrade marker
	DROP TABLE dmf_upgrade
	
	-- And temp tables
	DROP TABLE msdb.dbo.tmp_syspolicy_target_sets_internal
	DROP TABLE msdb.dbo.tmp_syspolicy_target_set_levels_internal
	DROP TABLE msdb.dbo.tmp_syspolicy_policies_internal 
	DROP TABLE msdb.dbo.tmp_syspolicy_system_health_state_internal 
	DROP TABLE msdb.dbo.tmp_syspolicy_policy_execution_history_internal 
	DROP TABLE msdb.dbo.tmp_syspolicy_policy_execution_history_details_internal 
	
	COMMIT TRANSACTION
	PRINT 'DMF successfully upgraded' 
END

--
-- <<< CTP5 -> CTP6 Upgrade
--

-- Make sure Agent XPs are enabled regardless of the state of the server
-- We will restove the state afterwards

DECLARE @advopt_old_value int
DECLARE @comp_old_value int
EXECUTE #sp_enable_component 'Agent XPs', @advopt_old_value out, @comp_old_value out


-- sproc that creates a job for PBM. This covers the creation of job for all upgrade scenarios.
PRINT 'Executing msdb.dbo.sp_syspolicy_create_purge_job'
EXEC msdb.dbo.sp_syspolicy_create_purge_job

-- Now restore the Agent XPs to their old state
EXECUTE #sp_restore_component_state 'Agent XPs', @advopt_old_value, @comp_old_value

GO


-- Final upgrade step does not depend on any schema changes, and thus can be run for any upgrade

-- Make sure that the policies are using a valid evaluation mode
-- If a policy is using an evaluation mode that is not supported for a facet, then the evaluation mode is None and it is not enabled

UPDATE p
	SET p.execution_mode = 0, p.is_enabled = 0
	FROM 
		[msdb].[dbo].[syspolicy_policies_internal] p 
		INNER JOIN 
		[msdb].[dbo].[syspolicy_conditions] c 
		ON (p.condition_id = c.condition_id)
		INNER JOIN
		[msdb].[dbo].[syspolicy_management_facets] f
		ON (c.facet = f.name)
		WHERE p.execution_mode <> (p.execution_mode & f.execution_mode)

-- Upgrade from CTP5 & CTP6 to RC0 - correct typo in the management facet name
UPDATE [msdb].[dbo].[syspolicy_management_facets]
SET name = 'MessageType' 
WHERE name = 'Messagetype'



		
GO

/**************************************************************/
/* End of DMF Post-upgrade steps                               */
/**************************************************************/

IF (NOT OBJECT_ID(N'tempdb.dbo.#SqlAgentSignatures', 'U') IS NULL)
BEGIN
    DROP TABLE #SqlAgentSignatures
END

-------------------------------------------------------------------------
--
/**************************************************************/
/* Data Collector Post-upgrade steps                          */
/**************************************************************/
GO
-- Procedure to print Data collector status and collection set status
CREATE PROCEDURE #printdatacollectorstatus
AS
BEGIN
	DECLARE @parameter_name SYSNAME
	DECLARE @parameter_value sql_variant

	PRINT 'Data Collector Status'
	SELECT @parameter_name = parameter_name, @parameter_value = parameter_value 
	FROM  msdb.dbo.syscollector_config_store_internal
	WHERE parameter_name = 'CollectorEnabled'

	PRINT @parameter_name + ':'  + convert(varchar , @parameter_value)

	PRINT 'Collection set status'
	DECLARE @collection_set_uid UNIQUEIDENTIFIER
	DECLARE @name SYSNAME
	DECLARE @is_running BIT

	DECLARE @collection_set_cursor CURSOR
	SET @collection_set_cursor = CURSOR FOR
	SELECT collection_set_uid, name, is_running 
	FROM msdb.dbo.syscollector_collection_sets_internal

	OPEN @collection_set_cursor
	FETCH NEXT FROM @collection_set_cursor INTO @collection_set_uid, @name, @is_running

	WHILE @@FETCH_STATUS = 0
	BEGIN
		PRINT 'Uid:' + convert(varchar(50),@collection_set_uid)  + ', Name:' + @name + ', IsRunning:' + convert(varchar, @is_running)
	
		FETCH NEXT FROM @collection_set_cursor INTO @collection_set_uid, @name, @is_running
	END
	CLOSE @collection_set_cursor
	DEALLOCATE @collection_set_cursor
END
GO

-- 'Agent XPs' setting should be turned on if not error is thrown while restoring 
-- data collector status
PRINT 'sqlagent100_msdb_upgrade::Enabling Agent XPs before restoring data collector original state'

DECLARE @advopt_old_value int
DECLARE @comp_old_value int
EXECUTE #sp_enable_component 'Agent XPs', @advopt_old_value out, @comp_old_value out

EXECUTE #printdatacollectorstatus

-- Load the instmdw.sql script into the blob
PRINT 'sqlagent100_msdb_upgrade::uploading instmdw.sql to msdb ...'
EXECUTE [dbo].[sp_syscollector_upload_instmdw]

-- Restore collection set running status
PRINT 'sqlagent100_msdb_upgrade::Checking if collection set status were captured in temp table...'
IF (OBJECT_ID('tempdb..#data_collector_collectionset_status', 'U') IS NOT NULL)
BEGIN
	PRINT 'sqlagent100_msdb_upgrade::Restoring collection set running status...'

	UPDATE [dbo].[syscollector_collection_sets_internal]
	SET [dbo].[syscollector_collection_sets_internal].is_running = #data_collector_collectionset_status.is_running
	FROM #data_collector_collectionset_status 
	WHERE [dbo].[syscollector_collection_sets_internal].is_running <> #data_collector_collectionset_status.is_running
	AND [dbo].[syscollector_collection_sets_internal].collection_set_uid = #data_collector_collectionset_status.collection_set_uid
END

-- Check if temp table that holds data collector's old status before upgrade exists
-- and enable data collector if DC was enabled before running upgrade scripts
PRINT 'sqlagent100_msdb_upgrade::Checking if Data collector was enabled before upgrade...'
IF (OBJECT_ID('tempdb..#data_collector_status', 'U') IS NOT NULL)
BEGIN
	DECLARE @old_state_enabled_status INT

	-- Get the old state of data collector - predicate checks if the old and current state is not the same
	-- Expected:  @old_state_enabled_status = NULL if old and current states are same
	--            @old_state_enabled_status = 0  if old state was disabled and current state is enabled
	--            @old_state_enabled_status = 1  if old state was enabled and current state is disabled
	SELECT @old_state_enabled_status = oldstatus.data_collector_old_status 
	FROM [dbo].[syscollector_config_store_internal] cs,
	#data_collector_status oldstatus
	WHERE cs.parameter_name = 'CollectorEnabled'
	AND oldstatus.data_collector_old_status <> CONVERT(int, cs.parameter_value)

	-- @old_state_enabled_status is not null only if old and current states are not the same 
	IF(@old_state_enabled_status IS NOT NULL)
	BEGIN
		-- If pre-upgrade state was disabled and current state is enabled, Restore to disabled state
		IF(@old_state_enabled_status = 0)
		BEGIN
			PRINT 'sqlagent100_msdb_upgrade::Data collector was disabled before upgrade, Restoring Disabled state...'
			EXEC sp_syscollector_disable_collector
		END
		-- If pre-upgrade state was enabled and current state is disables, enabled data collector
		ELSE IF(@old_state_enabled_status = 1)
		BEGIN
			PRINT 'sqlagent100_msdb_upgrade::Data collector was enabled before upgrade, Restoring Enabled State ...'
			EXEC sp_syscollector_enable_collector
		END
		ELSE
		-- If pre-upgrade state was not 1 or 0, print message ( we should not get into this issue )
		BEGIN
			PRINT 'sqlagent100_msdb_upgrade::Invalid enable/disable state:' + convert(varchar, @old_state_enabled_status)
		END
	END
	ELSE
	BEGIN
		PRINT 'sqlagent100_msdb_upgrade::Current data collector state is same as pre-upgrade state'
	END
END

EXECUTE #printdatacollectorstatus

-- Now restore the Agent XPs to their old state
EXECUTE #sp_restore_component_state 'Agent XPs', @advopt_old_value, @comp_old_value

GO
/**************************************************************/
/* End of Data Collector Post-upgrade steps                   */
/**************************************************************/

DROP PROCEDURE #printdatacollectorstatus
DROP PROCEDURE #sp_enable_component
DROP PROCEDURE #sp_restore_component_state
GO

PRINT ''
PRINT '-----------------------------------------'
PRINT 'Execution of POSTINSTMSDB100.SQL complete'
PRINT '-----------------------------------------'
go

Print 'Upgrading Database Mail related objects...'

/*
	One of the main functions of this script is to handle migration
	of multiple Mail Host database to a single Mail Host Database in MSDB.
	Pre Beta 3 (CPT 15) versions of SQL Server 2005 allowed 
	Databae Mail object to exist in any database. This script will migrate 
        previously created profile, SentItems and log information to MSDB. 
	Finally the script will remove Database Mail objects in these other databases.

	Another goal of this script is to handle all metadata changes between different
	releases of the product starting with B-2 through subsequent CTPs
*/


USE msdb

SET NOCOUNT ON

-- remove obsolete configuration parameter
IF EXISTS(SELECT * FROM msdb.dbo.sysmail_configuration WHERE paramname = N'MaxNumberOfMailsPerDay')
  DELETE FROM msdb.dbo.sysmail_configuration WHERE paramname=N'MaxNumberOfMailsPerDay'

/*
 	Upgrade objects in MSDB if it was previously used as mailhost database
*/
BEGIN TRY
	IF EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='profile_name' and id =
			(SELECT OBJECT_ID(N'dbo.sysmail_mailitems', 'U')))
	BEGIN
		  -- convert data from profile_name column to profile_id column
		  exec sp_executesql N'
		  DECLARE @profile_name sysname
		  DECLARE @profile_id int 

		  DECLARE profile_name_cursor CURSOR LOCAL 
		  FOR
			 SELECT sp.profile_id, sp.name
			 FROM dbo.sysmail_profile sp, dbo.sysmail_mailitems mi
			 WHERE sp.name = mi.profile_name AND mi.profile_name IS NOT NULL

		  OPEN profile_name_cursor
		  FETCH NEXT FROM profile_name_cursor INTO @profile_id, @profile_name
		  WHILE (@@fetch_status = 0)
		  BEGIN
			 UPDATE dbo.sysmail_mailitems SET profile_id = @profile_id WHERE profile_name = @profile_name
			 FETCH NEXT FROM profile_name_cursor INTO @profile_id, @profile_name
		  END
		  Close profile_name_cursor
		  DEALLOCATE profile_name_cursor'

		  -- remove obsolete column
		  ALTER TABLE dbo.sysmail_mailitems DROP COLUMN profile_name
	END
END TRY
BEGIN CATCH
	print 'There was a problem upgrading MSDB mail host database.'
	print 'Unable to map existing profile name values to profile_id column'
	print 'Error State: ' + convert(varchar(10),ERROR_STATE() )
	print 'Error Message: ' + ERROR_MESSAGE()
	print 'Error Number: ' + convert(varchar(10),ERROR_NUMBER()) 
	print 'Error Severity: ' + convert(varchar(10),ERROR_SEVERITY())
END CATCH
GO

if EXISTS(Select * from msdb.dbo.sysmail_principalprofile)
BEGIN
	BEGIN TRY
		-- add existing mail users defined in MSDB to new role
		DECLARE @DBMailUser     sysname
		DECLARE principal_profile_cursor CURSOR LOCAL FOR
				SELECT dp.name FROM dbo.sysmail_principalprofile pp INNER JOIN sys.database_principals dp ON pp.principal_sid = dp.sid and dp.principal_id > 4

		OPEN principal_profile_cursor
		FETCH NEXT FROM principal_profile_cursor INTO @DBMailUser
		WHILE (@@fetch_status = 0)
		BEGIN
			EXEC msdb..sp_addrolemember @rolename = 'DatabaseMailUserRole',@membername = @DBMailUser
			FETCH NEXT FROM principal_profile_cursor INTO @DBMailUser
		END
		CLOSE principal_profile_cursor
		DEALLOCATE principal_profile_cursor
	END TRY
	BEGIN CATCH
		print 'There was a problem upgrading MSDB mail host database.'
		print 'Unable to add existing mail user to DatabaseMailUserRole'
		print 'Error State: ' + convert(varchar(10),ERROR_STATE() )
		print 'Error Message: ' + ERROR_MESSAGE()
		print 'Error Number: ' + convert(varchar(10),ERROR_NUMBER()) 
		print 'Error Severity: ' + convert(varchar(10),ERROR_SEVERITY())
	END CATCH
END
GO

if EXISTS(Select * from msdb.dbo.sysmail_principalprofile)
BEGIN
	BEGIN TRY
		-- cleaning up database mail related broker conversations
		if( SELECT is_broker_enabled from msdb.sys.databases WHERE name = 'msdb' ) = 0
		BEGIN
			PRINT 'NEED TO RE-ENABLE SSB'
			WHILE(1=1)
			BEGIN
				DECLARE @handle UNIQUEIDENTIFIER
				SET @handle = NULL
				SELECT TOP(1) @handle=conversation_handle FROM msdb.sys.conversation_endpoints
				WHERE (msdb.sys.conversation_endpoints.far_service IN 
							('SQL/Notifications/SysMailNotification/v1.0',
							'ExternalMailService',
							'InternalMailService',
							'SqlQueryNotificationService',
							'iMailRequestorService',
							'iMailResponderService',
							'http://schemas.microsoft.com/SQL/Notifications/EventNotificationService',
							'http://schemas.microsoft.com/SQL/Notifications/PostEventNotification'
							)
						)

				IF @handle IS NULL BREAK
				PRINT 'ENDING CONVERSATION ' + convert(varchar(256),@handle)
				END CONVERSATION @handle WITH CLEANUP
			END
		END

		-- We cannot turn the broker on, because we don't know whether it was disabled by the user
		-- ALTER DATABASE msdb SET ENABLE_BROKER
	END TRY
	BEGIN CATCH
		print 'There was a problem upgrading Mail Host databases.'
		print 'Unable to re-enable server broker. You might need to manually'
		print 'end any pending secure conversations.'
		print 'Error State: ' + convert(varchar(10),ERROR_STATE() )
		print 'Error Message: ' + ERROR_MESSAGE()
		print 'Error Number: ' + convert(varchar(10),ERROR_NUMBER()) 
		print 'Error Severity: ' + convert(varchar(10),ERROR_SEVERITY())
	END CATCH
END
GO

if EXISTS(SELECT * FROM msdb.dbo.sysmail_principalprofile) AND
   EXISTS(SELECT * FROM msdb.dbo.syscolumns WHERE name='database_id' and id =(SELECT OBJECT_ID(N'dbo.sysmail_principalprofile', 'U')))
BEGIN
	/*
 		Handle migration of multiple mail host databases into MSDB
	*/
	BEGIN TRY
		DECLARE @DBName		sysname
		DECLARE @DBNameQuote	sysname
		DECLARE @sql 		nvarchar(max)

		DECLARE @new_mailitem_id 	int
		DECLARE @old_mailitem_id 	int
		DECLARE @profile_name_exists 	bit
		DECLARE @Error			bit
		DECLARE @db_id 			int

		DECLARE  DBName_Cursor CURSOR FOR
			select name from sys.databases WHERE name NOT IN ('msdb', 'tempdb', 'master')
				and HAS_DBACCESS([name]) <> 0

		OPEN DBName_Cursor 

		FETCH NEXT FROM DBName_Cursor INTO @DBName

		WHILE @@FETCH_STATUS = 0
		BEGIN
			Print 'Checking database: ' + @DBName
			SET @DBNameQuote = QUOTENAME(@DBName)
			SELECT @db_id = db_id(@DBName)
			
			IF ( OBJECT_ID(@DBNameQuote + '.dbo.sysmail_log') IS NOT NULL) --Determine if Database Mail objects exist
			BEGIN
				Print @DBName + ' is a MailHost database.'
				
				--Going to migrate profiles from database to MSDB
						DECLARE @DBMailUser				sysname
				DECLARE @sid_MSDB				varbinary(85)
				DECLARE @sid_principal				varbinary(85)
				DECLARE @old_profile_id				int
				DECLARE @new_principal_id			int
				DECLARE @old_principal_id			int
				DECLARE @LoginName					sysname
				
				SET @sql = N'DECLARE  DBMail_Cursor CURSOR FOR
					SELECT p.name
						, pp.profile_id
						, msdb_p.sid
						, p.sid
						, pp.principal_id
					FROM msdb..sysmail_principalprofile pp 
						JOIN '+ @DBNameQuote +'.sys.database_principals p 
							ON pp.principal_id = p.principal_id
						LEFT JOIN msdb.sys.database_principals msdb_p 
							ON p.sid = msdb_p.sid
					where pp.database_id = ' + convert(nvarchar(10),@db_id)
				--print @sql
				EXEC(@sql)

				OPEN DBMail_Cursor

				FETCH NEXT FROM DBMail_Cursor INTO @DBMailUser, @old_profile_id, @sid_MSDB, @sid_principal, @old_principal_id

				WHILE @@FETCH_STATUS = 0
				BEGIN	
					
					Print 'Going to process user: ' + @DBMailUser
				
					IF (@sid_MSDB IS NULL) -- does not already exist in MSDB
					BEGIN
						IF (NOT EXISTS(Select * from sysusers where name = @DBMailUser))
						BEGIN
							IF (EXISTS(Select * from master.dbo.syslogins where sid = @sid_principal))
							BEGIN

								/* Determine the Login Name from the SID	*/
								SELECT @LoginName = name 
								FROM	master.dbo.syslogins
								WHERE	sid = @sid_principal
								
								PRINT 'Add USER to MSDB: ' + @DBMailUser

								SET @sql = N'CREATE USER ' + QUOTENAME(@DBMailUser) + ' FOR LOGIN ' + QUOTENAME(@LoginName)
								EXEC (@sql)
			
								IF (@old_principal_id > 4) -- do not add special accounts such as dbo, public, sys
								BEGIN
									Print 'Grant USER permission to send mail.'
									exec msdb..sp_addrolemember @rolename = 'DatabaseMailUserRole',@membername = @DBMailUser
								END
							END
							ELSE
							BEGIN
								PRINT 'Can not add user as the login does not exist.'
							END
							
						END
						ELSE
						BEGIN
							Print 'User has already been added to MSDB: ' + @DBMailUser
						
							IF (@old_principal_id > 4) -- do not add special accounts such as dbo, public, sys
							BEGIN
								PRINT 'Ensure user has the right to send mail.'
							
								exec msdb..sp_addrolemember @rolename = 'DatabaseMailUserRole',@membername = @DBMailUser

							END

						END


					END
					ELSE
					BEGIN
						Print 'Login already mapped to a user in MSDB'
					END

					IF (EXISTS(Select * from master.dbo.syslogins where sid = @sid_principal))
					BEGIN
						--Get principle_id
						SELECT @new_principal_id = principal_id 
						FROM msdb.sys.database_principals
						Where sid = @sid_principal

						IF (@new_principal_id is not Null)
						BEGIN
							print 'New Principal_id: ' + Convert(varchar(10),@new_principal_id) + '  Old profile_id: ' + convert(varchar(10),@old_profile_id) + '  Old principal id: ' + convert(varchar(10),@old_principal_id)

							SET @sql = N'
							IF NOT EXISTS(select * from msdb..sysmail_principalprofile
											Where profile_id = ' + Convert(varchar(10),@old_profile_id) + '
												AND database_id = 4
												AND principal_id = ' + Convert(varchar(10),@new_principal_id) + ')
							BEGIN
								--Update the Profile
								UPDATE msdb..sysmail_principalprofile
								SET database_id = 4
									, principal_id = ' + Convert(varchar(10),@new_principal_id) + '
								Where profile_id = ' + Convert(varchar(10),@old_profile_id) + '
									AND database_id = ' + Convert(varchar(10),@db_id) + '
									AND principal_id = ' + Convert(varchar(10),@old_principal_id) + '

								IF (@@rowcount = 1)
								BEGIN
									Print ''sysmail_principalprofile updated based on moving user to MSDB.''
								END
							END
							ELSE
							BEGIN
								Print ''This user already has already been configured with this profile in MSDB.''
							END'
							EXEC(@sql)
						END
						ELSE
						BEGIN
							Print 'sysmail_principalprofile table can not be updated for sid: ' + convert(varchar(100),@sid_principal)
						END
					END

					FETCH NEXT FROM DBMail_Cursor INTO @DBMailUser, @old_profile_id, @sid_MSDB, @sid_principal, @old_principal_id
					
				END

				CLOSE DBMail_Cursor
				DEALLOCATE DBMail_Cursor
						
				--/*	Move Data from user Mail Host database to MSDB */
				--Print 'Move Data from user Mail Host database to MSDB.'

				SET @sql = N'DECLARE  mailitem_id_Cursor CURSOR FOR
					SELECT mailitem_id FROM ' + @DBNameQuote + '.dbo.sysmail_mailitems'
				
				EXEC(@sql)
				
				OPEN mailitem_id_Cursor
				
				Set @Error = 0	--Assume no errors

				BEGIN TRANSACTION

				/* Disable Trigger so the "last_mod_date" and "last_mod_user colums" are not updated during transfer. */
				EXEC ('DISABLE TRIGGER [dbo].[trig_sysmail_attachments] ON [dbo].[sysmail_attachments]')
				EXEC ('DISABLE TRIGGER [dbo].[trig_sysmail_log] ON [dbo].[sysmail_log]')
				EXEC ('DISABLE TRIGGER [dbo].[trig_sysmail_mailitems]ON [dbo].[sysmail_mailitems]')

				Print 'Going to move ALL sysmail_log items not associated with a mailitem'

				SET @sql = N'INSERT INTO msdb.dbo.sysmail_log
					(event_type, log_date, description, process_id, mailitem_id, account_id, last_mod_date, last_mod_user)
					SELECT event_type, log_date, description, process_id, NULL, account_id, last_mod_date, last_mod_user
					FROM '+	@DBNameQuote +'.dbo.sysmail_log
					WHERE mailitem_id IS NULL '
				
				exec(@sql)

				FETCH NEXT FROM mailitem_id_Cursor INTO @old_mailitem_id
				WHILE @@FETCH_STATUS = 0
				BEGIN
					/****************************************/
					/*	MOVE dbo.sysmail_mailitems DATA	*/
					/****************************************/

					/* Need to check the schema defination of the table	*/
					SET @sql = N'USE ' + @DBNameQuote + '
							DECLARE @sql nvarchar(max) 
							IF (EXISTS(select * from syscolumns
							where id = object_id(''[dbo].[sysmail_mailitems]'')
								AND name = ''profile_name''))
							BEGIN						
							SET @sql = N''INSERT INTO msdb.dbo.sysmail_mailitems
							(profile_id, recipients, copy_recipients, blind_copy_recipients, subject, body, body_format, importance, sensitivity, file_attachments, attachment_encoding, query, execute_query_database, attach_query_result_as_file, query_result_header, query_result_width, query_result_separator, exclude_query_output, append_query_error, send_request_date, send_request_user, sent_account_id, sent_status, sent_date, last_mod_date, last_mod_user)
							SELECT CASE WHEN p.profile_id IS NULL THEN -1 ELSE p.profile_id END, recipients, copy_recipients, blind_copy_recipients, subject, body, body_format, importance, sensitivity, file_attachments, attachment_encoding, query, execute_query_database, attach_query_result_as_file, query_result_header, query_result_width, query_result_separator, exclude_query_output, append_query_error, send_request_date, send_request_user, sent_account_id, sent_status, sent_date, last_mod_date, mi.last_mod_user
							FROM '+	@DBNameQuote +'.dbo.sysmail_mailitems mi LEFT JOIN msdb..sysmail_profile p
								ON mi.profile_name = p.name
							WHERE mailitem_id = ' + CONVERT(VARCHAR(20),@old_mailitem_id) + ''' 
							END
							ELSE
							BEGIN
							SET @sql = N''INSERT INTO msdb.dbo.sysmail_mailitems
							(profile_id, recipients, copy_recipients, blind_copy_recipients, subject, body, body_format, importance, sensitivity, file_attachments, attachment_encoding, query, execute_query_database, attach_query_result_as_file, query_result_header, query_result_width, query_result_separator, exclude_query_output, append_query_error, send_request_date, send_request_user, sent_account_id, sent_status, sent_date, last_mod_date, last_mod_user)
							SELECT profile_id, recipients, copy_recipients, blind_copy_recipients, subject, body, body_format, importance, sensitivity, file_attachments, attachment_encoding, query, execute_query_database, attach_query_result_as_file, query_result_header, query_result_width, query_result_separator, exclude_query_output, append_query_error, send_request_date, send_request_user, sent_account_id, sent_status, sent_date, last_mod_date, last_mod_user
							FROM '+	@DBNameQuote +'.dbo.sysmail_mailitems 
							WHERE mailitem_id = ' + CONVERT(VARCHAR(20),@old_mailitem_id) + ''' 
							END

							EXEC(@sql)'

					
					EXEC(@sql)

					IF (@@error <> 0)	--Check for error
					BEGIN
						Set @Error = 1
					END
				
					SELECT @new_mailitem_id = @@identity

					/****************************************/
					/*	MOVE dbo.sysmail_log DATA	*/
					/****************************************/
					SET @sql = N'INSERT INTO msdb.dbo.sysmail_log
							(event_type, log_date, description, process_id, mailitem_id, account_id, last_mod_date, last_mod_user)
							SELECT event_type, log_date, description, process_id, ' + convert(varchar(5),@new_mailitem_id) + ', account_id, last_mod_date, last_mod_user
							FROM '+	@DBNameQuote +'.dbo.sysmail_log
							WHERE mailitem_id = ' + CONVERT(VARCHAR(20),@old_mailitem_id)
				
					EXEC(@sql)

					IF @@error <> 0 	--Check for error
					BEGIN
						Set @Error = 1
					END

					/****************************************/
					/*	MOVE dbo.sysmail_attachments DATA	*/ 
					/****************************************/
				SET @sql = N'USE ' + @DBNameQuote + ' 
								IF (object_id(''dbo.sysmail_attachments'') IS NOT NULL)
								begin
									INSERT INTO msdb.dbo.sysmail_attachments 
									(mailitem_id, filename, filesize, attachment, last_mod_date, last_mod_user)
									SELECT ' + convert(varchar(5),@new_mailitem_id) + ', filename, filesize, attachment, last_mod_date, last_mod_user
									FROM '+	@DBNameQuote +'.dbo.sysmail_attachments 
									WHERE mailitem_id = ' + CONVERT(VARCHAR(20),@old_mailitem_id)  + '
								end'
					EXEC(@sql)

					IF @@error <> 0 	--Check for error
					BEGIN
						Set @Error = 1
					END


					FETCH NEXT FROM mailitem_id_Cursor INTO @old_mailitem_id
				
				END
			
				IF @Error = 0
				BEGIN
					Print 'Completed data transfer to MSDB.'
					COMMIT TRANSACTION
				END
				ELSE
				BEGIN
					Print 'Not able to complete data transfer to MSDB.'
					ROLLBACK TRANSACTION

				END
			
				/* ENABLE Triggers as they were previously DISABLE	*/
				EXEC ('ENABLE TRIGGER [dbo].[trig_sysmail_attachments] ON [dbo].[sysmail_attachments]')
				EXEC ('ENABLE TRIGGER [dbo].[trig_sysmail_log] ON [dbo].[sysmail_log]')
				EXEC ('ENABLE TRIGGER [dbo].[trig_sysmail_mailitems]ON [dbo].[sysmail_mailitems]')

				CLOSE mailitem_id_Cursor
				DEALLOCATE mailitem_id_Cursor
				
				IF @Error = 0
				BEGIN
					/**********************************************************************/
					/*                                                                    */
					/* Uninstalls the tables, triggers and stored procedures necessary for*/
					/* sqlimail operations                                                */
					/*                                                                    */
					/*
					** Copyright Microsoft, Inc. 2004
					** All Rights Reserved.
					*/
					/**********************************************************************/
					
					
					
					/**************************************************************/
					--		Drop ALL Database Mail objects (i.e Functions/Procedures )
					/**************************************************************/ 
					
					PRINT ''
					PRINT 'Dropping old Database Mail FUNCTIONS and PROCEDURES ...'
					PRINT ''
					
					-----
					PRINT 'Dropping function ConvertToInt'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.ConvertToInt'', ''FN'') IS NULL
								DROP FUNCTION ConvertToInt')
					
					-----
					PRINT 'Dropping procedure sysmail_start_sp'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_start_sp'', ''P'') IS NULL
						DROP PROCEDURE dbo.sysmail_start_sp')
					
					-----
					PRINT 'Dropping procedure sysmail_stop_sp'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_stop_sp'', ''P'') IS NULL
						DROP PROCEDURE dbo.sysmail_stop_sp')
					
					-----
					PRINT 'Dropping procedure sysmail_logmailevent_sp'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_logmailevent_sp'', ''P'') IS NULL
						DROP PROCEDURE dbo.sysmail_logmailevent_sp')
					
					-----
					PRINT 'Dropping procedure sp_has_changedbuser_permission'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_has_changedbuser_permission'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_has_changedbuser_permission')
					
					-----
					PRINT 'Dropping procedure sp_getprofilerequestxml'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_getprofilerequestxml'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_getprofilerequestxml')
					
					-----
					PRINT 'Dropping procedure sp_sendandreturn'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_sendandreturn'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_sendandreturn')
					
					-----
					PRINT 'Dropping procedure sp_sendandblock'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_sendandblock'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_sendandblock')
					
					-----
					PRINT 'Dropping procedure sp_isprohibited'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_isprohibited'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_isprohibited')
					
					-----
					PRINT 'Dropping procedure sp_sendimailqueues'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_sendimailqueues'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_sendimailqueues')
					
					-----
					PRINT 'Dropping procedure sp_gettestprofilexml'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_gettestprofilexml'', ''P'') IS NULL
					DROP PROCEDURE dbo.sp_gettestprofilexml')
					
					-----
					PRINT 'Dropping procedure sp_testprofileimailqueues'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_testprofileimailqueues'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_testprofileimailqueues')
					
					-----
					PRINT 'Dropping procedure sp_endconversation'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_endconversation'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_endconversation')
					
					-----
					PRINT 'Dropping procedure sp_endconversation'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_endconversation'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_endconversation')
					
					-----
					PRINT 'Dropping procedure sp_readrequest'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_readrequest'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_readrequest')
					
					-----
					PRINT 'Dropping procedure sp_sendresponse'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_sendresponse'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_sendresponse')
					
					-----
					PRINT 'Dropping procedure sp_sendtestprofileresponse'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_sendtestprofileresponse'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_sendtestprofileresponse')
					
					-----
					PRINT 'Dropping procedure sp_getsendmailxml'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_getsendmailxml'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_getsendmailxml')
					
					-----
					PRINT 'Dropping procedure sendimail_sp'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sendimail_sp'', ''P'') IS NULL
						DROP PROCEDURE dbo.sendimail_sp')
					
					-----
					PRINT 'Dropping procedure sp_testimailprofile'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_testimailprofile'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_testimailprofile')
						
					-----
					PRINT 'Dropping procedure dbo.sp_add_quota_information'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_add_quota_information'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_add_quota_information')
			
					-----
					PRINT 'Dropping procedure dbo.sp_current_principal_mails'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_current_principal_mails'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_current_principal_mails')
			
					-----
					PRINT 'Dropping procedure dbo.sp_delete_quota_information'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_delete_quota_information'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_delete_quota_information')

			
					-----
					PRINT 'Dropping procedure  dbo.sp_ExernalMailQueueListener'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_ExernalMailQueueListener'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_ExernalMailQueueListener')

			
					-----
					PRINT 'Dropping procedure dbo.sp_GetAttachmentData'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_GetAttachmentData'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_GetAttachmentData')

			
					-----
					PRINT 'Dropping procedure dbo.sp_RunMailQuery'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_RunMailQuery'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_RunMailQuery')

			
					-----
					PRINT 'Dropping procedure dbo.sp_SendMailMessage'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_SendMailMessage'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_SendMailMessage')

			
					-----
					PRINT 'Dropping procedure dbo.sp_SendMailQueues'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_SendMailQueues'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_SendMailQueues')
			
					-----
					PRINT 'Dropping procedure dbo.sp_verify_quota_mail_count'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_verify_quota_mail_count'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_verify_quota_mail_count')

					-----
					PRINT 'Dropping procedure dbo.sp_activate_sqlimail'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_activate_sqlimail'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_activate_sqlimail')



			
					
					/**************************************************************/
					--    Drop all Database Mail TABLES 
					/**************************************************************/ 
					
					PRINT ''
					PRINT 'Dropping TABLES...'
					PRINT ''
					
					-----
					PRINT 'Dropping table sysmail_log'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_log'', ''U'') IS NULL
						DROP TABLE sysmail_log')
					
					----- 
					PRINT 'Dropping table sysmail_send_retries'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_send_retries'', ''U'') IS NULL
						DROP TABLE dbo.sysmail_send_retries')
					
					-----
					PRINT 'Dropping table sqlimail_data_transfer'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sqlimail_data_transfer'', ''U'') IS NULL
						DROP TABLE sqlimail_data_transfer')
					
					-----
					PRINT 'Dropping table sysmail_attachments'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_attachments'', ''U'') IS NULL
						DROP TABLE sysmail_attachments')
			
					-----
					PRINT 'Dropping table sysmail_query_transfer'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_query_transfer'', ''U'') IS NULL
						DROP TABLE sysmail_query_transfer')
			
					-----
					PRINT 'Dropping table sysmail_attachments_transfer'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_attachments_transfer'', ''U'') IS NULL
						DROP TABLE sysmail_attachments_transfer')
			
					-----
					PRINT 'Dropping table sysmail_quota_information'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_quota_information'', ''U'') IS NULL
						DROP TABLE sysmail_quota_information')
			
					-----
					PRINT 'Dropping table sysmail_mailitems'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_mailitems'', ''U'') IS NULL
						DROP TABLE sysmail_mailitems')
			
					
					/**************************************************************/
					--		Drop MESSAGES, CONTRACTS, QUEUES AND SERVICES 
					/**************************************************************/
					
					PRINT ''
					PRINT 'Dropping MESSAGES, CONTRACTS, QUEUES AND SERVICES...'
					PRINT ''
					
					-----
					PRINT 'Dropping service iMailRequestorService'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.services WHERE name =''iMailRequestorService'')
						DROP SERVICE iMailRequestorService')
					
					-----
					PRINT 'Dropping service iMailResponderService'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.services WHERE name =''iMailResponderService'')
						DROP SERVICE iMailResponderService')
					
					-----
					PRINT 'Dropping queue iMailRequestor'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.objects WHERE name = ''iMailRequestor'' AND type = ''SQ'')
						DROP QUEUE iMailRequestor')
					
					-----
					PRINT 'Dropping queue iMailResponder'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.objects WHERE name = ''iMailResponder'' AND type = ''SQ'')
						DROP QUEUE iMailResponder')
					
					-----
					PRINT 'Dropping service [SQL/Notifications/IMailNotification/v1.0]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.services WHERE name =''SQL/Notifications/IMailNotification/v1.0'')
						DROP SERVICE [SQL/Notifications/IMailNotification/v1.0]')
					   
					-----
					PRINT 'Dropping service [ExternalMailService]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.services WHERE name =''ExternalMailService'')
						DROP SERVICE [ExternalMailService]')

					-----
					PRINT 'Dropping service [InternalMailService]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.services WHERE name =''InternalMailService'')
						DROP SERVICE [InternalMailService]')

					-----
					PRINT 'Dropping queue iMailNotificationQueue'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.objects WHERE name = ''iMailNotificationQueue'' AND type = ''SQ'')
						DROP QUEUE iMailNotificationQueue')
					   
					-----
					PRINT 'Dropping contract [//www.microsoft.com/imail/contracts/SendMail/v1.0]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS(SELECT * FROM sys.service_contracts 
							WHERE name = ''//www.microsoft.com/imail/contracts/SendMail/v1.0'')          
						DROP CONTRACT [//www.microsoft.com/imail/contracts/SendMail/v1.0]')
					
					-----
					PRINT 'Dropping message type [{//www.microsoft.com/imail/messages}SendMail]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS(SELECT * FROM sys.service_message_types 
							WHERE name = ''{//www.microsoft.com/imail/messages}SendMail'')        
					DROP MESSAGE TYPE [{//www.microsoft.com/imail/messages}SendMail]')
					  
					-----
					PRINT 'Dropping contract [//www.microsoft.com/imail/contracts/TestProfile/v1.0]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS(SELECT * FROM sys.service_contracts 
							WHERE name = ''//www.microsoft.com/imail/contracts/TestProfile/v1.0'')         
						DROP CONTRACT [//www.microsoft.com/imail/contracts/TestProfile/v1.0]')
					
					-----
					PRINT 'Dropping message type [{//www.microsoft.com/imail/messages}TestProfile]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS(SELECT * FROM sys.service_message_types 
							WHERE name = ''{//www.microsoft.com/imail/messages}TestProfile'')        
						DROP MESSAGE TYPE [{//www.microsoft.com/imail/messages}TestProfile]')
					
					-----
					PRINT 'Dropping message type [{//www.microsoft.com/imail/messages}TestProfileResponse]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS(SELECT * FROM sys.service_message_types 
							WHERE name = ''{//www.microsoft.com/imail/messages}TestProfileResponse'')
						DROP MESSAGE TYPE [{//www.microsoft.com/imail/messages}TestProfileResponse]')
				
					-----
					PRINT 'Dropping certificates and related users'
					-----
					DECLARE @sqlcmd     nvarchar(max), 
						@CertName       sysname,
						@CertNameQuoted sysname,
						@MailLogin      sysname,
						@MailLoginQuoted sysname    
					
					SELECT @CertName       = N'SQLiMail-Certificate-' + @DBName,
						@CertNameQuoted = QUOTENAME( @CertName,''''),
						@MailLogin      = N'SQLiMail-' + @DBName + '-Certificate-Login',
						@MailLoginQuoted= QUOTENAME( @MailLogin,'''')
					
					-----
					PRINT 'Dropping user in msdb'
					-----
					SET @sqlcmd = 
					N'USE msdb
					IF(EXISTS (SELECT * FROM sys.database_principals WHERE name = N' + @MailLoginQuoted + '))
						DROP USER ' + QUOTENAME( @MailLogin)
					EXEC sp_executesql @sqlcmd
					
					-----
					PRINT 'Dropping user and certificate login in master'
					-----
					SET @sqlcmd = 
					N'USE master 
						IF(EXISTS (SELECT * FROM sys.database_principals WHERE name = N' + @MailLoginQuoted + '))
						DROP USER ' + QUOTENAME( @MailLogin) + '
						IF(EXISTS(select * from sys.server_principals where name = N' + @MailLoginQuoted + '))
						DROP LOGIN ' + QUOTENAME( @MailLogin)
					EXEC sp_executesql @sqlcmd
					
					-----
					PRINT 'Dropping user in this database'
					-----
					SET @sqlcmd = 
					N'USE ' + @DBNameQuote + '
					IF(EXISTS (SELECT * FROM sys.database_principals WHERE name = N' + @MailLoginQuoted + '))
						DROP USER ' + QUOTENAME( @MailLogin)

					EXEC sp_executesql @sqlcmd			
					
					-----
					PRINT 'Dropping the certificate in this db'
					-----
					SET @sqlcmd = 
					N'USE ' + @DBNameQuote + '	
					IF(EXISTS(SELECT * FROM sys.certificates WHERE name = N' + @CertNameQuoted + '))
					BEGIN
						DROP CERTIFICATE ' + QUOTENAME(@CertName) + ' 
					END'

					EXEC sp_executesql @sqlcmd			
					
					-----
					PRINT 'Dropping certificate from master'
					-----
					SET @sqlcmd = 
					N'USE master
					IF(EXISTS(SELECT * FROM sys.certificates WHERE name = N' + @CertNameQuoted + '))
						DROP CERTIFICATE ' + QUOTENAME(@CertName)

					EXEC sp_executesql @sqlcmd
				
				END
			END

			FETCH NEXT FROM DBName_Cursor INTO @DBName

		END	-- Loop through all databases.

		CLOSE DBName_Cursor
		DEALLOCATE DBName_Cursor

    	-- deleting all principal associations that are not in MSDB or public
	    exec sp_executesql N'
	    delete from msdb.dbo.sysmail_principalprofile
	    where database_id <> 4 and database_id <> 0'
	END TRY
	BEGIN CATCH
		print 'There was a problem upgrading Mail Host databases.'
		print 'Error State: ' + convert(varchar(10),ERROR_STATE() )
		print 'Error Message: ' + ERROR_MESSAGE()
		print 'Error Number: ' + convert(varchar(10),ERROR_NUMBER()) 
		print 'Error Severity: ' + convert(varchar(10),ERROR_SEVERITY())
	END CATCH
END

BEGIN TRY
	IF EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='principal_id' and id =
			(SELECT OBJECT_ID(N'dbo.sysmail_principalprofile', 'U')))
	BEGIN
		-- convert data from principal_id to principal_sid
		exec sp_executesql N'
		DECLARE @principal_sid varbinary(85)
		DECLARE @principal_id int 

		DECLARE principal_sid_cursor CURSOR LOCAL 
		FOR
		SELECT distinct principal_id
		FROM dbo.sysmail_principalprofile

		OPEN principal_sid_cursor 
		FETCH NEXT FROM principal_sid_cursor INTO @principal_id
		WHILE (@@fetch_status = 0)
		BEGIN
			IF @principal_id = 0
			   SET @principal_sid = 0x00
			ELSE
			   SELECT @principal_sid = dbo.get_principal_sid(@principal_id)

			IF @principal_sid IS NOT NULL -- principal_id is valid
			BEGIN
				UPDATE dbo.sysmail_principalprofile
				SET principal_sid = @principal_sid
				WHERE principal_id = @principal_id
			END
			ELSE
			BEGIN
				DELETE FROM dbo.sysmail_principalprofile
				WHERE principal_id = @principal_id
			END
			FETCH NEXT FROM principal_sid_cursor INTO @principal_id
		END
		CLOSE principal_sid_cursor 
		DEALLOCATE principal_sid_cursor'

		-- safety clean-up
		DELETE FROM dbo.sysmail_principalprofile WHERE principal_sid = 0xFFFF

		-- remove obsolete column
		ALTER TABLE dbo.sysmail_principalprofile DROP CONSTRAINT [SYSMAIL_PRINCIPALPROFILE_ProfilePrincipalMustBeUnique]
		ALTER TABLE dbo.sysmail_principalprofile DROP COLUMN principal_id
  		ALTER TABLE dbo.sysmail_principalprofile ADD CONSTRAINT [SYSMAIL_PRINCIPALPROFILE_ProfilePrincipalMustBeUnique] PRIMARY KEY CLUSTERED ([profile_id] ASC,[principal_sid] ASC)
	END
END TRY
BEGIN CATCH
	print 'There was a problem upgrading MSDB mail host database.'
	print 'Unable to map existing profile id values to profile_sid column'
	print 'Error State: ' + convert(varchar(10),ERROR_STATE() )
	print 'Error Message: ' + ERROR_MESSAGE()
	print 'Error Number: ' + convert(varchar(10),ERROR_NUMBER()) 
	print 'Error Severity: ' + convert(varchar(10),ERROR_SEVERITY())
END CATCH
GO

BEGIN TRY
    -- remove database_id column
    IF EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='database_id' and id =
       (SELECT OBJECT_ID(N'dbo.sysmail_principalprofile', 'U')))
    BEGIN
        ALTER TABLE dbo.sysmail_principalprofile DROP CONSTRAINT [SYSMAIL_PRINCIPALPROFILE_ProfilePrincipalMustBeUnique]
        ALTER TABLE dbo.sysmail_principalprofile DROP COLUMN database_id
        ALTER TABLE dbo.sysmail_principalprofile ADD CONSTRAINT [SYSMAIL_PRINCIPALPROFILE_ProfilePrincipalMustBeUnique] PRIMARY KEY CLUSTERED ([profile_id] ASC,[principal_sid] ASC)
    END
END TRY
BEGIN CATCH
	print 'There was a problem upgrading MSDB mail host database.'
	print 'Unable to create a primary key constraint on sysmail_principalprofile table'
	print 'Error State: ' + convert(varchar(10),ERROR_STATE() )
	print 'Error Message: ' + ERROR_MESSAGE()
	print 'Error Number: ' + convert(varchar(10),ERROR_NUMBER()) 
	print 'Error Severity: ' + convert(varchar(10),ERROR_SEVERITY())
END CATCH

Print 'Completed upgrade of Database Mail related objects...'
GO

-- Restoring implicit transactions state
DECLARE @is_implicit_transactions_set BIT

SELECT @is_implicit_transactions_set = CONVERT(BIT, value)
FROM fn_listextendedproperty(N'IMPLICIT_TRANSACTIONS', default, default, default, default, default, default);

IF (@is_implicit_transactions_set IS NOT NULL)
BEGIN
	EXEC sp_dropextendedproperty N'IMPLICIT_TRANSACTIONS'

  IF @is_implicit_transactions_set = 1
  BEGIN
    SET IMPLICIT_TRANSACTIONS ON
    PRINT 'Restored implicit transactions state to ON'
  END
  ELSE
  BEGIN
    SET IMPLICIT_TRANSACTIONS OFF
    PRINT 'Restored implicit transactions state to OFF'
  END
END 


GO

PRINT ''
PRINT '--------------------------------------------------'
PRINT 'Execution of Sqlagent100_msdb_upgrade.SQL complete'
PRINT '--------------------------------------------------'

PRINT '-----------------------------------------'
PRINT 'Starting execution of dummy.sql'
PRINT '-----------------------------------------'

-- This is a dummy script 
-- It is used by the upgrade script framework to deprecate another script 


use master
go

select @@version
go

PRINT '-----------------------------------------'
PRINT 'Ending execution of dummy.sql'
PRINT '-----------------------------------------'
--
-- this script is run by engine during engine startup after upgrade
--
raiserror(N'Executing replication upgrade scripts.', 10, 1)
go
use master
go
set nocount on
declare @script_status int
        ,@repl_script_key nvarchar(277)
        ,@repl_script_value nvarchar(8)
        ,@retcode int
        ,@repl_installed int

-- this script will be run regardless of whether replication is installed
-- check to see if replication is installed before proceeding
execute master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                     N'SOFTWARE\Microsoft\MSSQLServer\Replication',
                                     N'IsInstalled',
                                     @repl_installed OUTPUT,
                                     N'no_output'
select @repl_installed = isnull(@repl_installed, 0)

if @repl_installed = 0 and not exists(select * from sys.databases where is_cdc_enabled = 1)
begin
    raiserror(N'Neither Replication feature is installed nor CDC is enabled. Upgrade scripts will not be run.', 10, 1)
    return
end

select @repl_script_key = N'SOFTWARE\Microsoft\MSSQLServer\Replication\Setup'
        ,@repl_script_value = N'Upgraded'

-- Set script upgrade status to 0
set @script_status = 0
if @repl_installed != 0
begin
    execute @retcode = master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                                       @repl_script_key,
                                                       @repl_script_value,
                                                       N'REG_DWORD',
                                                       @script_status
end

begin try
    raiserror(N'Executing sp_vupgrade_replication.', 10, 1)
    exec @retcode = sys.sp_vupgrade_replication
    if @retcode != 0 or @@error != 0
    begin
        set @script_status = 0
    end
    else
    begin
        set @script_status = 1
        raiserror(N'sp_vupgrade_replication executed successfully', 10, 1)
    end
end try
begin catch
    -- re-raise the error non-fatally
    declare @error_msg nvarchar(2048)
    select @error_msg = ERROR_MESSAGE()
    raiserror(@error_msg, 10, 1)
    set @script_status = 0
end catch

if @script_status != 1
begin
    raiserror(N'Error executing sp_vupgrade_replication.', 10, 1)
end

-- write the status to the registry
if @repl_installed != 0
begin
    raiserror(N'Saving upgrade script status to ''%s''.', 10, 1, @repl_script_key)
    execute @retcode = master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                                       @repl_script_key,
                                                       @repl_script_value,
                                                       N'REG_DWORD',
                                                       @script_status
    if @retcode != 0 or @@error != 0
    begin
        raiserror(N'Error saving upgrade script status.', 10, 1)
    end
    else
    begin
        raiserror(N'Saved upgrade script status successfully.', 10, 1)
    end
end
go/****************************************************************************
//                            Copyright (c) Microsoft Corporation.
//
// @File: provisionsystemaccounts.sql
// @Owner: raghavt
//
// Purpose:
//                            Script to grant login and SA privileges to NT AUTHORITY\SYSTEM and
//                            the SQL Engine group. It also removes the FT group accounts no
//                            longer required in Katmai
//
// Notes:
//                            run by the SQL script upgrade framework.
//
//
// @EndHeader@
*****************************************************************************/
use master
go

PRINT '----------------------------------------'
PRINT 'Starting provisionsystemaccounts.sql ...';
PRINT '----------------------------------------'
go

-- Obtain the name of NT AUTHORITY\SYSTEM from a well-known SID 
-- (as it might be on an non-ENU installation)
-- During upgrade, add Local system as a login if it does not exist
-- 
DECLARE @systemAccountSID AS NVARCHAR(1024)
DECLARE @systemAccountSIDBinary AS VARBINARY(85)
DECLARE @systemAccountName AS SYSNAME;
SET @systemAccountSID = N'S-1-5-18';
-- convert textual SID to name
--
SELECT @systemAccountSIDBinary = sid_binary(@systemAccountSID);
SELECT @systemAccountName = suser_sname(@systemAccountSIDBinary);

IF (@systemAccountName IS NOT NULL)
BEGIN
	IF (NOT EXISTS (SELECT * FROM sys.server_principals WHERE NAME = @systemAccountName))
	BEGIN
		PRINT N'Adding login for ' + @systemAccountName
		DECLARE @strExec NVARCHAR (MAX)
		SET @strExec = N'CREATE LOGIN ' + QUOTENAME(@systemAccountName) + N' FROM WINDOWS'
		EXEC (@strExec)
	END
END

-- To enable Hadron functionality, Grant Alter Any AG and View Server State privileges to the local system account
-- 

BEGIN
	SET @strExec = N'GRANT ALTER ANY AVAILABILITY GROUP TO ' + QUOTENAME(@systemAccountName)
	EXEC (@strExec)
	SET @strExec = N'GRANT VIEW SERVER STATE TO ' + QUOTENAME(@systemAccountName )
	EXEC (@strExec)
END
go


-- Provision Writer Service SID as login and sysadmin
-- Step 1. Create the login
-- Step 2. Add as sysadmin
--
DECLARE @accountName AS SYSNAME
SET @accountName = N'NT SERVICE\SQLWriter'

IF (SUSER_SID(@accountName) IS NOT NULL AND NOT EXISTS (SELECT * FROM sys.server_principals WHERE NAME = @accountName))
BEGIN
	PRINT N'Adding login for ' + @accountName
	DECLARE @strExec NVARCHAR (MAX)
	SET @strExec = N'CREATE LOGIN ' + QUOTENAME(@accountName) + N' FROM WINDOWS'
	EXEC (@strExec)
	EXEC sys.sp_addsrvrolemember @accountName, N'sysadmin'
END
go


-- Provision Engine Service SID as login and sysadmin
-- This piece of code is required for upgrade from Yukon,Katmant and KJ only 
-- This provisioning step is no-op for RANU instances as SUSER_SID(@accountName) returns null
-- 
-- Step 1. Create the login
-- Step 2. Add as sysadmin
--
DECLARE @serviceName AS SYSNAME
DECLARE @accountName AS SYSNAME 
SET @serviceName = @@SERVICENAME 

if @serviceName = N'MSSQLSERVER'
	SET @accountName = N'NT SERVICE\'+ @serviceName
else
	SET @accountName = N'NT SERVICE\MSSQL$'+ @serviceName

IF (SUSER_SID(@accountName) IS NOT NULL AND NOT EXISTS (SELECT * FROM sys.server_principals WHERE NAME = @accountName))
BEGIN
	PRINT N'Adding login for ' + @accountName
	DECLARE @strExec NVARCHAR (MAX)
	SET @strExec = N'CREATE LOGIN ' + QUOTENAME(@accountName) + N' FROM WINDOWS'
	EXEC (@strExec)
	exec sys.sp_addsrvrolemember @accountName, N'sysadmin'
END
go

-- Provision WMI Service SID as login and sysadmin
-- Step 1. Create the login
-- Step 2. Add as sysadmin
--
DECLARE @accountName AS SYSNAME
SET @accountName = N'NT SERVICE\winmgmt'

IF (SUSER_SID(@accountName) IS NOT NULL AND NOT EXISTS (SELECT * FROM sys.server_principals WHERE NAME = @accountName))
BEGIN
	PRINT N'Adding login for ' + @accountName
	DECLARE @strExec NVARCHAR (MAX)
	SET @strExec = N'CREATE LOGIN ' + QUOTENAME(@accountName) + ' FROM WINDOWS'
	EXEC (@strExec)
	EXEC sys.sp_addsrvrolemember @accountName, N'sysadmin'
END
go


---- remove all SQL 2005 provision for msfteuser group account ---------------
---- those accounts are no longer needed for katmai and we need to remove them during upgrade ----------
declare @accountname nvarchar(256) 
declare @principalid int
declare ftgroup_cursor cursor static local for 
select name, principal_id from sys.server_principals where name like '%SQLServer%MSFTEUser%' and type = 'G'

open ftgroup_cursor
fetch ftgroup_cursor into @accountname, @principalid
while @@fetch_status >=0
begin
	if ((@accountname is not null) and (@principalid is not null))
	begin
		declare @sqlstr nvarchar(1024)
	
		exec sp_dropsrvrolemember @accountname, 'sysadmin'
		select @sqlstr = N'revoke exec on sys.sp_fulltext_getdata from '+quotename(@accountname)
		exec sp_executesql @sqlstr
		select @sqlstr = N'drop login '+quotename(@accountname)
		exec sp_executesql @sqlstr
	end

	fetch ftgroup_cursor into @accountname, @principalid
end

deallocate ftgroup_cursor
go




/*------------------------------------------------------------------------------

SYSDBUPG.SQL

This script upgrades the system database stored procs from the RTM level.
It is used only for system databases which are upgraded in post-RTM servicing.

The script is also run during mkmastr to produce upgraded refresh databases
for inclusion in the Express skus.

Databases which may be updated by this script:
	master
	model
	msdb

Databases not updated by this script:
	mssqlsystemresource
	distmdl
	adventureworks
	adventureworksdw
	user databases

Any changes to the following scripts are made here instead:
	U_TABLES.SQL
	PROCSYST.SQL
	XPSTAR.SQL
	INSTMSDB.SQL
	REPL_MASTER.SQL

Note:
  This script does not apply any sysmessages changes.
  Such changes are delivered via an updated SQLEVN70.RLL instead.


** Copyright (c) Microsoft Corporation.  All rights reserved.

------------------------------------------------------------------------------*/

/**************************************************************/
/* Record time of start of creates                            */
/**************************************************************/
SELECT start = getdate() INTO #sysdbupg
go

--------------------------------------------------------------------------------
--	U_TABLES.SQL
--------------------------------------------------------------------------------


--------------------------------------------------------------------------------
--	PROCSYST.SQL
--------------------------------------------------------------------------------

-- Grant SELECT on Shiloh views to PUBLIC (only if they were not explicitely denied)
--
use master
go

set nocount on
set implicit_transactions off
set ansi_nulls on	-- default for osql (consistent for suites)
set quoted_identifier on	-- Force all ye devs to do this correctly!
go

-- temporary grant select to QE dmv
GRANT SELECT on sys.dm_exec_query_resource_semaphores TO PUBLIC
GRANT SELECT on sys.dm_exec_query_memory_grants TO PUBLIC
go

if NOT EXISTS (
	SELECT * FROM sys.database_permissions 
	WHERE 	class = 1 and major_id = object_id (N'master.dbo.syscacheobjects') and 
			minor_id = 0 and grantee_principal_id = 0 and type = 'SL' and state = 'D')
	GRANT SELECT ON syscacheobjects TO PUBLIC;

if NOT EXISTS (
	SELECT * FROM sys.database_permissions 
	WHERE 	class = 1 and major_id = object_id (N'master.dbo.sysperfinfo') and 
			minor_id = 0 and grantee_principal_id = 0 and type = 'SL' and state = 'D')
	GRANT SELECT ON sysperfinfo TO PUBLIC;
go


--------------------------------------------------------------------------------
--	XPSTAR.SQL
--------------------------------------------------------------------------------


--------------------------------------------------------------------------------
--	INSTMSDB.SQL
--------------------------------------------------------------------------------
/*********************************************************************/
/* Create auxilary procedure to enable OBD (Off By Default component */
/*********************************************************************/
CREATE PROCEDURE #sp_enable_component     
   @comp_name     sysname, 
   @advopt_old_value    INT OUT, 
   @comp_old_value   INT OUT 
AS
   BEGIN
   SELECT @advopt_old_value=cast(value_in_use as int) from sys.configurations where name = 'show advanced options';
   SELECT @comp_old_value=cast(value_in_use as int) from sys.configurations where name = @comp_name; 
   EXEC sp_configure 'show advanced options',1;
   RECONFIGURE WITH OVERRIDE;
   EXEC sp_configure @comp_name, 1; 
   RECONFIGURE WITH OVERRIDE;
   END
go

CREATE PROCEDURE #sp_restore_component_state 
   @comp_name     sysname, 
   @advopt_old_value    INT, 
   @comp_old_value   INT 
AS
   BEGIN
   EXEC sp_configure @comp_name, @comp_old_value; 
   RECONFIGURE WITH OVERRIDE;
   EXEC sp_configure 'show advanced options',@advopt_old_value;
   RECONFIGURE WITH OVERRIDE;
   END
go

USE msdb
go

-- Change growth type to 10% for MSDB log and data files
declare @growth bigint
declare @is_percent_growth int

SET @growth = (SELECT growth FROM sys.database_files WHERE name = N'MSDBData')
SET @is_percent_growth = (SELECT is_percent_growth FROM sys.database_files WHERE name = N'MSDBData')

-- If data file grows by 256k (this is the RTM setting) change that to 10% 
IF( (@growth IS NOT NULL) AND (@growth = 32) AND 
	(@is_percent_growth IS NOT NULL) AND (@is_percent_growth = 0 ))
BEGIN
	PRINT 'Update [msdb] data file growth to 10%.'
	ALTER DATABASE [msdb] MODIFY FILE (NAME=N'MSDBData', FILEGROWTH=10%)
END


SET @growth = (SELECT growth FROM sys.database_files WHERE name = N'MSDBLog')
SET @is_percent_growth = (SELECT is_percent_growth FROM sys.database_files WHERE name = N'MSDBLog')

-- If log file grows by 256k (this is the RTM setting) change that to 10% 
IF( (@growth IS NOT NULL) AND (@growth = 32) AND 
	(@is_percent_growth IS NOT NULL) AND (@is_percent_growth = 0 ))
BEGIN
	PRINT 'Update [msdb] log file growth to 10%.'
	ALTER DATABASE [msdb] MODIFY FILE (NAME=N'MSDBLog', FILEGROWTH=10%)
END

GO
-- end 'Change growth type ...'

-- Updating the sysjobsteps.command column that was missed while upgrading Shiloh --> Yukon
IF ((SELECT max_length FROM sys.columns WHERE object_id = OBJECT_ID (N'dbo.sysjobsteps') AND name = N'command') <> -1)
BEGIN
	PRINT ''
	PRINT 'Updating sys.jobsteps.command to nvarchar(max)'  
	ALTER TABLE dbo.sysjobsteps ALTER COLUMN command NVARCHAR (MAX)
END
GO
-- end 'Update the sysjobsteps.command...


/**************************************************************/
/* drop certificate signature from Agent signed sps           */
/**************************************************************/

BEGIN TRANSACTION
declare @sp sysname
declare @exec_str nvarchar(1024)
declare ms_crs_sps cursor global for select object_name(crypts.major_id) 
   from sys.crypt_properties crypts, sys.certificates certs
   where crypts.thumbprint = certs.thumbprint
   and crypts.class = 1
   and certs.name = '##MS_AgentSigningCertificate##'
open ms_crs_sps
fetch next from ms_crs_sps into @sp
while @@fetch_status = 0
begin
   if exists(select * from sys.objects where name = @sp)
   begin
      print 'Dropping signature from: ' + @sp
      set @exec_str = N'drop signature from ' + quotename(@sp) + N' by certificate [##MS_AgentSigningCertificate##]'
      Execute(@exec_str)
      if (@@error <> 0)
      begin
         declare @err_str nvarchar(1024)
         set @err_str = 'Cannot drop signature from ' + quotename(@sp) + '. Terminating.'
         close ms_crs_sps
         deallocate ms_crs_sps
         ROLLBACK TRANSACTION
         RAISERROR(@err_str, 20, 127) WITH LOG
         return
      end
   end
   fetch next from ms_crs_sps into @sp
end
close ms_crs_sps
deallocate ms_crs_sps
COMMIT TRANSACTION
go

/**************************************************************/
/* sp_verify_subsystems                                       */
/**************************************************************/

PRINT ''
PRINT 'Updating procedure sp_verify_subsystems...'
go
ALTER PROCEDURE dbo.sp_verify_subsystems
   @syssubsytems_refresh_needed BIT = 0
AS
BEGIN
  SET NOCOUNT ON
   
  DECLARE @retval         INT
  DECLARE @InstRootPath nvarchar(512)
  DECLARE @ComRootPath nvarchar(512)
  DECLARE @DtsRootPath nvarchar(512)
  DECLARE @DTExec nvarchar(512)
  DECLARE @DTExecExists INT

  IF ( (@syssubsytems_refresh_needed=1) OR (NOT EXISTS(select * from syssubsystems)) )
  BEGIN
     EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\Setup', N'SQLPath', @InstRootPath OUTPUT
     IF @InstRootPath IS NULL
     BEGIN
       RAISERROR(14658, -1, -1) WITH LOG
       RETURN (1)
     END
     SELECT @InstRootPath = @InstRootPath + N'\binn\'

     EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\Microsoft Sql Server\90', N'VerSpecificRootDir', @ComRootPath OUTPUT
     IF @ComRootPath IS NULL
     BEGIN
       RAISERROR(14659, -1, -1) WITH LOG
       RETURN(1)
     END

     EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSDTS\Setup\DTSPath', N'', @DtsRootPath OUTPUT, N'no_output'
     IF (@DtsRootPath IS NOT NULL)
     BEGIN
       SELECT @DtsRootPath  = @DtsRootPath  + N'Binn\'
       SELECT @DTExec = @DtsRootPath + N'DTExec.exe'
       CREATE TABLE #t (file_exists int, is_directory int, parent_directory_exists int)
	   INSERT #t EXEC xp_fileexist @DTExec
       SELECT TOP 1 @DTExecExists=file_exists from #t
       DROP TABLE #t
       IF ((@DTExecExists IS NULL) OR (@DTExecExists = 0))
         SET @DtsRootPath = NULL
     END

     SELECT @ComRootPath  = @ComRootPath  + N'COM\'

     -- Procedure must start its own transaction if we don't have one already.
     DECLARE @TranCounter INT;
     SET @TranCounter = @@TRANCOUNT;
     IF @TranCounter = 0
     BEGIN
        BEGIN TRANSACTION;
     END

     -- Obtain processor count to determine maximum number of threads per subsystem
     DECLARE @xp_results TABLE
     (
     id              INT           NOT NULL,
     name            NVARCHAR(30)  COLLATE database_default NOT NULL,
     internal_value  INT           NULL,
     character_value NVARCHAR(212) COLLATE database_default NULL
     )
     INSERT INTO @xp_results
     EXECUTE master.dbo.xp_msver

     DECLARE @processor_count INT
     SELECT @processor_count = internal_value from @xp_results where id=16 -- ProcessorCount

     -- Modify database.
     BEGIN TRY

       --create subsystems
       --TSQL subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'TSQL')
       INSERT syssubsystems
       VALUES
       (
          1, N'TSQL',14556, FORMATMESSAGE(14557), FORMATMESSAGE(14557), FORMATMESSAGE(14557), FORMATMESSAGE(14557), FORMATMESSAGE(14557), 20 * @processor_count
       )
       
       --CmdExec subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'CmdExec')
       INSERT syssubsystems
       VALUES
       (
          3, N'CmdExec', 14550, @InstRootPath + N'SQLCMDSS90.DLL',NULL,N'CmdExecStart',N'CmdEvent',N'CmdExecStop', 10 * @processor_count
       )

       --Snapshot subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'Snapshot')
       INSERT syssubsystems
       VALUES
       (
          4, N'Snapshot',   14551, @InstRootPath + N'SQLREPSS90.DLL', @ComRootPath + N'SNAPSHOT.EXE', N'ReplStart',N'ReplEvent',N'ReplStop',100 * @processor_count
       )

       --LogReader subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'LogReader')
       INSERT syssubsystems
       VALUES
       (
          5, N'LogReader',  14552, @InstRootPath + N'SQLREPSS90.DLL', @ComRootPath + N'logread.exe',N'ReplStart',N'ReplEvent',N'ReplStop',25 * @processor_count
       )

       --Distribution subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'Distribution')
       INSERT syssubsystems
       VALUES
       (
          6, N'Distribution',  14553, @InstRootPath + N'SQLREPSS90.DLL', @ComRootPath + N'DISTRIB.EXE',N'ReplStart',N'ReplEvent',N'ReplStop',100 * @processor_count
       )

       --Merge subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'Merge')
       INSERT syssubsystems
       VALUES
       (
          7, N'Merge',   14554, @InstRootPath + N'SQLREPSS90.DLL',@ComRootPath + N'REPLMERG.EXE',N'ReplStart',N'ReplEvent',N'ReplStop',100 * @processor_count
       )

       --QueueReader subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'QueueReader')
       INSERT syssubsystems
       VALUES
       (
          8, N'QueueReader',   14581, @InstRootPath + N'sqlrepss90.dll',@ComRootPath + N'qrdrsvc.exe',N'ReplStart',N'ReplEvent',N'ReplStop',100 * @processor_count
       )

       --ANALYSISQUERY subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'ANALYSISQUERY')
       INSERT syssubsystems
       VALUES
       (
          9, N'ANALYSISQUERY', 14513, @InstRootPath + N'SQLOLAPSS90.DLL',NULL,N'OlapStart',N'OlapQueryEvent',N'OlapStop',100 * @processor_count
       )

       --ANALYSISCOMMAND subsystem
       IF NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'ANALYSISCOMMAND')
       INSERT syssubsystems
       VALUES
       (
          10, N'ANALYSISCOMMAND', 14514, @InstRootPath + N'SQLOLAPSS90.DLL',NULL,N'OlapStart',N'OlapCommandEvent',N'OlapStop',100 * @processor_count
       )

       IF(@DtsRootPath IS NOT NULL)
       BEGIN
          --DTS subsystem
          IF (NOT EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'SSIS') )
             INSERT syssubsystems
             VALUES
             (
                11, N'SSIS', 14538, @InstRootPath + N'SQLDTSSS90.DLL',@DtsRootPath + N'DTExec.exe',N'DtsStart',N'DtsEvent',N'DtsStop',100 * @processor_count
             )
          ELSE
             UPDATE syssubsystems SET agent_exe = @DtsRootPath + N'DTExec.exe' WHERE subsystem = N'SSIS'
       END
       ELSE
       BEGIN
          IF EXISTS(SELECT * FROM syssubsystems WHERE subsystem = N'SSIS')
            DELETE FROM syssubsystems WHERE subsystem = N'SSIS' 
       END

   END TRY
   BEGIN CATCH

       DECLARE @ErrorMessage NVARCHAR(400)
       DECLARE @ErrorSeverity INT
       DECLARE @ErrorState INT

       SELECT @ErrorMessage = ERROR_MESSAGE()
       SELECT @ErrorSeverity = ERROR_SEVERITY()
       SELECT @ErrorState = ERROR_STATE()

       -- Roll back the transaction that we started if we are not nested
       IF @TranCounter = 0
       BEGIN
         ROLLBACK TRANSACTION;
       END
       -- if we are nested inside another transaction just raise the 
       -- error and let the outer transaction do the rollback
       RAISERROR (@ErrorMessage, -- Message text.
                   @ErrorSeverity, -- Severity.
                   @ErrorState -- State.
                   )
       RETURN (1)                  
     END CATCH
  END --(NOT EXISTS(select * from syssubsystems))
  
  -- commit the transaction we started
  IF @TranCounter = 0
  BEGIN
    COMMIT TRANSACTION;
  END
  
  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_VERIFY_SCHEDULE                                         */
/**************************************************************/

PRINT ''
PRINT 'Updating procedure sp_verify_schedule...'
go
ALTER PROCEDURE sp_verify_schedule
  @schedule_id            INT,
  @name                   sysname,
  @enabled                TINYINT,
  @freq_type              INT,          
  @freq_interval          INT OUTPUT,   -- Output because we may set it to 0 if Frequency Type is one-time or auto-start
  @freq_subday_type       INT OUTPUT,   -- As above
  @freq_subday_interval   INT OUTPUT,   -- As above
  @freq_relative_interval INT OUTPUT,   -- As above
  @freq_recurrence_factor INT OUTPUT,   -- As above
  @active_start_date      INT OUTPUT,
  @active_start_time      INT OUTPUT,
  @active_end_date        INT OUTPUT,
  @active_end_time        INT OUTPUT,
  @owner_sid              VARBINARY(85) --Must be a valid sid. Will fail if this is NULL
AS
BEGIN
  DECLARE @return_code             INT
  DECLARE @res_valid_range         NVARCHAR(100)
  DECLARE @reason                  NVARCHAR(200)
  DECLARE @isAdmin                 INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name = LTRIM(RTRIM(@name))

  -- Make sure that NULL input/output parameters - if NULL - are initialized to 0
  SELECT @freq_interval          = ISNULL(@freq_interval, 0)
  SELECT @freq_subday_type       = ISNULL(@freq_subday_type, 0)
  SELECT @freq_subday_interval   = ISNULL(@freq_subday_interval, 0)
  SELECT @freq_relative_interval = ISNULL(@freq_relative_interval, 0)
  SELECT @freq_recurrence_factor = ISNULL(@freq_recurrence_factor, 0)
  SELECT @active_start_date      = ISNULL(@active_start_date, 0)
  SELECT @active_start_time      = ISNULL(@active_start_time, 0)
  SELECT @active_end_date        = ISNULL(@active_end_date, 0)
  SELECT @active_end_time        = ISNULL(@active_end_time, 0)


  -- Check owner 
  IF(ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
    SELECT @isAdmin = 1
  ELSE
    SELECT @isAdmin = 0


  -- If a non-sa is [illegally] trying to create a schedule for another user then raise an error
  IF ((@isAdmin <> 1) AND 
      (ISNULL(IS_MEMBER('SQLAgentOperatorRole'),0) <> 1 AND @schedule_id IS NULL) AND
      (@owner_sid <> SUSER_SID()))
  BEGIN
     RAISERROR(14366, -1, -1)
     RETURN(1) -- Failure
  END


  -- Now just check that the login id is valid (ie. it exists and isn't an NT group)
  IF (@owner_sid <> 0x010100000000000512000000) AND -- NT AUTHORITY\SYSTEM sid
     (@owner_sid <> 0x010100000000000514000000)     -- NT AUTHORITY\NETWORK SERVICE sid
  BEGIN
     IF (@owner_sid IS NULL) OR (EXISTS (SELECT *
                                      FROM master.dbo.syslogins
                                      WHERE (sid = @owner_sid)
                                      AND (isntgroup <> 0)))
     BEGIN
       -- NOTE: In the following message we quote @owner_login_name instead of @owner_sid
       --       since this is the parameter the user passed to the calling SP (ie. either
       --       sp_add_schedule, sp_add_job and sp_update_job)
       SELECT @res_valid_range = FORMATMESSAGE(14203)
       RAISERROR(14234, -1, -1, '@owner_login_name', @res_valid_range)
       RETURN(1) -- Failure
     END
  END
  
  -- Verify name (we disallow schedules called 'ALL' since this has special meaning in sp_delete_jobschedules)
  IF (UPPER(@name collate SQL_Latin1_General_CP1_CS_AS) = N'ALL')
  BEGIN
    RAISERROR(14200, -1, -1, '@name')
    RETURN(1) -- Failure
  END

  -- Verify enabled state
  IF (@enabled <> 0) AND (@enabled <> 1)
  BEGIN
    RAISERROR(14266, -1, -1, '@enabled', '0, 1')
    RETURN(1) -- Failure
  END

  -- Verify frequency type
  IF (@freq_type = 0x2) -- OnDemand is no longer supported
  BEGIN
    RAISERROR(14295, -1, -1)
    RETURN(1) -- Failure
  END
  IF (@freq_type NOT IN (0x1, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80))
  BEGIN
    RAISERROR(14266, -1, -1, '@freq_type', '0x1, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80')
    RETURN(1) -- Failure
  END

  -- Verify frequency sub-day type
  IF (@freq_subday_type <> 0) AND (@freq_subday_type NOT IN (0x1, 0x2, 0x4, 0x8))
  BEGIN
    RAISERROR(14266, -1, -1, '@freq_subday_type', '0x1, 0x2, 0x4, 0x8')
    RETURN(1) -- Failure
  END

  -- Default active start/end date/times (if not supplied, or supplied as NULLs or 0)
  IF (@active_start_date = 0)
    SELECT @active_start_date = DATEPART(yy, GETDATE()) * 10000 +
                                DATEPART(mm, GETDATE()) * 100 +
                                DATEPART(dd, GETDATE()) -- This is an ISO format: "yyyymmdd"
  IF (@active_end_date = 0)
    SELECT @active_end_date = 99991231  -- December 31st 9999
  IF (@active_start_time = 0)
    SELECT @active_start_time = 000000  -- 12:00:00 am
  IF (@active_end_time = 0)
    SELECT @active_end_time = 235959    -- 11:59:59 pm

  -- Verify active start/end dates
  IF (@active_end_date = 0)
    SELECT @active_end_date = 99991231

  EXECUTE @return_code = sp_verify_job_date @active_end_date, '@active_end_date'
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  EXECUTE @return_code = sp_verify_job_date @active_start_date, '@active_start_date'
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  IF (@active_end_date < @active_start_date)
  BEGIN
    RAISERROR(14288, -1, -1, '@active_end_date', '@active_start_date')
    RETURN(1) -- Failure
  END

  EXECUTE @return_code = sp_verify_job_time @active_end_time, '@active_end_time'
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  EXECUTE @return_code = sp_verify_job_time @active_start_time, '@active_start_time'
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  -- NOTE: It's valid for active_end_time to be less than active_start_time since in this
  --       case we assume that the user wants the active time zone to span midnight.
  --       But it's not valid for active_start_date and active_end_date to be the same for recurring sec/hour/minute schedules

  IF (@active_start_time = @active_end_time and (@freq_subday_type in (0x2, 0x4, 0x8)))
  BEGIN
    SELECT @res_valid_range = FORMATMESSAGE(14202)
    RAISERROR(14266, -1, -1, '@active_end_time', @res_valid_range)
    RETURN(1) -- Failure
  END

  -- NOTE: The rest of this procedure is a SQL implementation of VerifySchedule in job.c

  IF ((@freq_type = 0x1) OR  -- FREQTYPE_ONETIME
      (@freq_type = 0x40) OR -- FREQTYPE_AUTOSTART
      (@freq_type = 0x80))   -- FREQTYPE_ONIDLE
  BEGIN
    -- Set standard defaults for non-required parameters
    SELECT @freq_interval          = 0
    SELECT @freq_subday_type       = 0
    SELECT @freq_subday_interval   = 0
    SELECT @freq_relative_interval = 0
    SELECT @freq_recurrence_factor = 0

    -- Check that a one-time schedule isn't already in the past
    -- Bug 442883: let the creation of the one-time schedule succeed but leave a disabled schedule
    /*
    IF (@freq_type = 0x1) -- FREQTYPE_ONETIME
    BEGIN
      DECLARE @current_date INT
      DECLARE @current_time INT

      -- This is an ISO format: "yyyymmdd"
      SELECT @current_date = CONVERT(INT, CONVERT(VARCHAR, GETDATE(), 112))
      SELECT @current_time = (DATEPART(hh, GETDATE()) * 10000) + (DATEPART(mi, GETDATE()) * 100) + DATEPART(ss, GETDATE())
      IF (@active_start_date < @current_date) OR ((@active_start_date = @current_date) AND (@active_start_time <= @current_time))
      BEGIN
        SELECT @res_valid_range = '> ' + CONVERT(VARCHAR, @current_date) + ' / ' + CONVERT(VARCHAR, @current_time)
        SELECT @reason = '@active_start_date = ' + CONVERT(VARCHAR, @active_start_date) + ' / @active_start_time = ' + CONVERT(VARCHAR, @active_start_time)
        RAISERROR(14266, -1, -1, @reason, @res_valid_range)
        RETURN(1) -- Failure
      END
    END
    */

    GOTO ExitProc
  END

  -- Safety net: If the sub-day-type is 0 (and we know that the schedule is not a one-time or
  --             auto-start) then set it to 1 (FREQSUBTYPE_ONCE).  If the user wanted something
  --             other than ONCE then they should have explicitly set @freq_subday_type.
  IF (@freq_subday_type = 0)
    SELECT @freq_subday_type = 0x1 -- FREQSUBTYPE_ONCE

  IF ((@freq_subday_type <> 0x1) AND  -- FREQSUBTYPE_ONCE   (see qsched.h)
      (@freq_subday_type <> 0x2) AND  -- FREQSUBTYPE_SECOND (see qsched.h)
      (@freq_subday_type <> 0x4) AND  -- FREQSUBTYPE_MINUTE (see qsched.h)
      (@freq_subday_type <> 0x8))     -- FREQSUBTYPE_HOUR   (see qsched.h)
  BEGIN
    SELECT @reason = FORMATMESSAGE(14266, '@freq_subday_type', '0x1, 0x2, 0x4, 0x8')
    RAISERROR(14278, -1, -1, @reason)
    RETURN(1) -- Failure
  END
  IF ((@freq_subday_type <> 0x1) AND (@freq_subday_interval < 1))
     OR
     ((@freq_subday_type = 0x2) AND (@freq_subday_interval < 10))
  BEGIN
    SELECT @reason = FORMATMESSAGE(14200, '@freq_subday_interval')
    RAISERROR(14278, -1, -1, @reason)
    RETURN(1) -- Failure
  END

  IF (@freq_type = 0x4)      -- FREQTYPE_DAILY
  BEGIN
    SELECT @freq_recurrence_factor = 0
    IF (@freq_interval < 1)
    BEGIN
      SELECT @reason = FORMATMESSAGE(14572)
      RAISERROR(14278, -1, -1, @reason)
      RETURN(1) -- Failure
    END
  END

  IF (@freq_type = 0x8)      -- FREQTYPE_WEEKLY
  BEGIN
    IF (@freq_interval < 1)   OR
       (@freq_interval > 127) -- (2^7)-1 [freq_interval is a bitmap (Sun=1..Sat=64)]
    BEGIN
      SELECT @reason = FORMATMESSAGE(14573)
      RAISERROR(14278, -1, -1, @reason)
      RETURN(1) -- Failure
    END
  END

  IF (@freq_type = 0x10)    -- FREQTYPE_MONTHLY
  BEGIN
    IF (@freq_interval < 1)  OR
       (@freq_interval > 31)
    BEGIN
      SELECT @reason = FORMATMESSAGE(14574)
      RAISERROR(14278, -1, -1, @reason)
      RETURN(1) -- Failure
    END
  END

  IF (@freq_type = 0x20)     -- FREQTYPE_MONTHLYRELATIVE
  BEGIN
    IF (@freq_relative_interval <> 0x01) AND  -- RELINT_1ST
       (@freq_relative_interval <> 0x02) AND  -- RELINT_2ND
       (@freq_relative_interval <> 0x04) AND  -- RELINT_3RD
       (@freq_relative_interval <> 0x08) AND  -- RELINT_4TH
       (@freq_relative_interval <> 0x10)      -- RELINT_LAST
    BEGIN
      SELECT @reason = FORMATMESSAGE(14575)
      RAISERROR(14278, -1, -1, @reason)
      RETURN(1) -- Failure
    END
  END

  IF (@freq_type = 0x20)     -- FREQTYPE_MONTHLYRELATIVE
  BEGIN
    IF (@freq_interval <> 01) AND -- RELATIVE_SUN
       (@freq_interval <> 02) AND -- RELATIVE_MON
       (@freq_interval <> 03) AND -- RELATIVE_TUE
       (@freq_interval <> 04) AND -- RELATIVE_WED
       (@freq_interval <> 05) AND -- RELATIVE_THU
       (@freq_interval <> 06) AND -- RELATIVE_FRI
       (@freq_interval <> 07) AND -- RELATIVE_SAT
       (@freq_interval <> 08) AND -- RELATIVE_DAY
       (@freq_interval <> 09) AND -- RELATIVE_WEEKDAY
       (@freq_interval <> 10)     -- RELATIVE_WEEKENDDAY
    BEGIN
      SELECT @reason = FORMATMESSAGE(14576)
      RAISERROR(14278, -1, -1, @reason)
      RETURN(1) -- Failure
    END
  END

  IF ((@freq_type = 0x08)  OR   -- FREQTYPE_WEEKLY
      (@freq_type = 0x10)  OR   -- FREQTYPE_MONTHLY
      (@freq_type = 0x20)) AND  -- FREQTYPE_MONTHLYRELATIVE
      (@freq_recurrence_factor < 1)
  BEGIN
    SELECT @reason = FORMATMESSAGE(14577)
    RAISERROR(14278, -1, -1, @reason)
    RETURN(1) -- Failure
  END

ExitProc:
  -- If we made it this far the schedule is good
  RETURN(0) -- Success

END
go


/**************************************************************/
/* SP_ATTACH_SCHEDULE                                         */
/**************************************************************/
PRINT ''
PRINT 'Updating procedure sp_attach_schedule ...'
go
ALTER PROCEDURE sp_attach_schedule
(
  @job_id               UNIQUEIDENTIFIER    = NULL,     -- Must provide either this or job_name
  @job_name             sysname             = NULL,     -- Must provide either this or job_id
  @schedule_id          INT                 = NULL,     -- Must provide either this or schedule_name
  @schedule_name        sysname             = NULL,     -- Must provide either this or schedule_id
  @automatic_post       BIT                 = 1         -- If 1 will post notifications to all tsx servers to that run this job
)   
AS
BEGIN
  DECLARE @retval           INT
  DECLARE @sched_owner_sid  VARBINARY(85)
  DECLARE @job_owner_sid    VARBINARY(85)

  
  SET NOCOUNT ON

  -- Check that we can uniquely identify the job
  EXECUTE @retval = msdb.dbo.sp_verify_job_identifiers '@job_name',
                                                       '@job_id',
                                                        @job_name                   OUTPUT,
                                                        @job_id                     OUTPUT,
                                                        @owner_sid = @job_owner_sid OUTPUT
    IF (@retval <> 0)
        RETURN(1) -- Failure

  -- Check authority (only SQLServerAgent can add a schedule to a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)
        
  -- Check that we can uniquely identify the schedule
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @schedule_name    OUTPUT,
                                                            @schedule_id            = @schedule_id      OUTPUT,
                                                            @owner_sid              = @sched_owner_sid  OUTPUT,
                                                            @orig_server_id         = NULL
  IF (@retval <> 0)
      RETURN(1) -- Failure     

  --Schedules can only be attached to a job if the job and schedule have the 
  --same owner or the caller is a sysadmin
  IF ((@sched_owner_sid <> @job_owner_sid) AND 
     ((@sched_owner_sid <> SUSER_SID()) OR (@job_owner_sid <> SUSER_SID())) AND
      (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
     RAISERROR(14377, -1, -1)
     RETURN(1) -- Failure
  END

  -- If the record doesn't already exist create it
  IF( NOT EXISTS(SELECT *  
                 FROM msdb.dbo.sysjobschedules
                 WHERE (schedule_id = @schedule_id)
                   AND (job_id = @job_id)) )
  BEGIN
    INSERT INTO msdb.dbo.sysjobschedules (schedule_id, job_id)
    SELECT @schedule_id, @job_id
    
    SELECT @retval = @@ERROR

    -- Notify SQLServerAgent of the change, but only if we know the job has been cached
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                    AND (server_id = 0)))
    BEGIN
        EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                            @job_id      = @job_id,
                                            @schedule_id = @schedule_id,
                                            @action_type = N'I'
    END
    
    -- For a multi-server job, remind the user that they need to call sp_post_msx_operation
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                    AND (server_id <> 0)))
      -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines 
      IF (@automatic_post = 1)
        EXECUTE sp_post_msx_operation @operation = 'INSERT', @object_type = 'JOB', @job_id = @job_id
      ELSE
        RAISERROR(14547, 0, 1, N'INSERT', N'sp_post_msx_operation')

	-- update this job's subplan to point to this schedule
	UPDATE msdb.dbo.sysmaintplan_subplans
	  SET schedule_id = @schedule_id
	WHERE (job_id = @job_id)
	  AND (schedule_id IS NULL)
  END
  
  RETURN(@retval) -- 0 means success
END
GO


/**************************************************************/
/* SP_DETACH_SCHEDULE                                         */
/**************************************************************/
PRINT ''
PRINT 'Updating procedure sp_detach_schedule ...'
go
ALTER PROCEDURE sp_detach_schedule
(
  @job_id               UNIQUEIDENTIFIER    = NULL,     -- Must provide either this or job_name
  @job_name             sysname             = NULL,     -- Must provide either this or job_id
  @schedule_id          INT                 = NULL,     -- Must provide either this or schedule_name
  @schedule_name        sysname             = NULL,     -- Must provide either this or schedule_id
  @delete_unused_schedule BIT               = 0,        -- Can optionally delete schedule if it isn't referenced.
                                                        -- The default is to keep schedules 
  @automatic_post       BIT                 = 1         -- If 1 will post notifications to all tsx servers to that run this job
)   
AS
BEGIN
  DECLARE @retval   INT
  DECLARE @sched_owner_sid VARBINARY(85)
  DECLARE @job_owner_sid    VARBINARY(85)
  
  SET NOCOUNT ON

  -- Check that we can uniquely identify the job
  EXECUTE @retval = msdb.dbo.sp_verify_job_identifiers '@job_name',
                                                       '@job_id',
                                                        @job_name OUTPUT,
                                                        @job_id   OUTPUT,
                                                        @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check authority (only SQLServerAgent can add a schedule to a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)
        
  -- Check that we can uniquely identify the schedule
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @schedule_name OUTPUT,
                                                            @schedule_id            = @schedule_id   OUTPUT,
                                                            @owner_sid              = @sched_owner_sid OUTPUT,
                                                            @orig_server_id         = NULL,
                                                            @job_id_filter          = @job_id
  IF (@retval <> 0)
      RETURN(1) -- Failure
 
  -- If the record doesn't exist raise an error
  IF( NOT EXISTS(SELECT *  
                 FROM msdb.dbo.sysjobschedules
                 WHERE (schedule_id = @schedule_id)
                   AND (job_id = @job_id)) )
  BEGIN
    RAISERROR(14374, 0, 1, @schedule_name, @job_name)    
    RETURN(1) -- Failure   
  END
  ELSE
  BEGIN
  
    -- Only sysadmin can detach schedules from jobs they do not own
   IF (((@sched_owner_sid <> SUSER_SID()) OR (@job_owner_sid <> SUSER_SID())) AND
        (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
    BEGIN
      RAISERROR(14391, -1, -1)
      RETURN(1) -- Failure
    END

    DELETE FROM msdb.dbo.sysjobschedules
    WHERE (job_id = @job_id)
      AND (schedule_id = @schedule_id)
    
    SELECT @retval = @@ERROR
    
    --delete the schedule if requested and it isn't referenced
    IF(@retval = 0 AND @delete_unused_schedule = 1)
    BEGIN
        IF(NOT EXISTS(SELECT * 
                      FROM msdb.dbo.sysjobschedules
                      WHERE (schedule_id = @schedule_id)))
        BEGIN
            DELETE FROM msdb.dbo.sysschedules
            WHERE (schedule_id = @schedule_id)
        END
    END

    -- Update the job's version/last-modified information
    UPDATE msdb.dbo.sysjobs
    SET version_number = version_number + 1,
        date_modified = GETDATE()
    WHERE (job_id = @job_id)

    -- Notify SQLServerAgent of the change, but only if we know the job has been cached
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                    AND (server_id = 0)))
    BEGIN
        EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                            @job_id      = @job_id,
                                            @schedule_id = @schedule_id,
                                            @action_type = N'D'
    END

    -- For a multi-server job, remind the user that they need to call sp_post_msx_operation
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                    AND (server_id <> 0)))
      -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines 
      IF (@automatic_post = 1)
        EXECUTE sp_post_msx_operation @operation = 'INSERT', @object_type = 'JOB', @job_id = @job_id
      ELSE
        RAISERROR(14547, 0, 1, N'INSERT', N'sp_post_msx_operation')
    
	-- set this job's subplan to the first schedule in sysjobschedules or NULL if there is none 
	UPDATE msdb.dbo.sysmaintplan_subplans
	SET schedule_id = (	SELECT TOP(1) schedule_id
						FROM msdb.dbo.sysjobschedules
						WHERE (job_id = @job_id) )
	WHERE (job_id = @job_id)
	  AND (schedule_id = @schedule_id)
  END
  
  RETURN(@retval) -- 0 means success
END
GO

/**************************************************************/
/* SP_UPDATE_SCHEDULE                                         */
/**************************************************************/
PRINT ''
PRINT 'Updating procedure sp_update_schedule ...'
go
ALTER PROCEDURE sp_update_schedule
(
  @schedule_id              INT             = NULL,     -- Must provide either this or schedule_name
  @name                     sysname         = NULL,     -- Must provide either this or schedule_id
  @new_name                 sysname         = NULL,
  @enabled                  TINYINT         = NULL,
  @freq_type                INT             = NULL,
  @freq_interval            INT             = NULL,
  @freq_subday_type         INT             = NULL,
  @freq_subday_interval     INT             = NULL,
  @freq_relative_interval   INT             = NULL,
  @freq_recurrence_factor   INT             = NULL,
  @active_start_date        INT             = NULL, 
  @active_end_date          INT             = NULL,
  @active_start_time        INT             = NULL,
  @active_end_time          INT             = NULL,
  @owner_login_name         sysname         = NULL,
  @automatic_post           BIT             = 1         -- If 1 will post notifications to all tsx servers to 
                                                        -- update all jobs that use this schedule
)
AS
BEGIN
  DECLARE @retval                   INT
  DECLARE @owner_sid                VARBINARY(85)
  DECLARE @cur_owner_sid            VARBINARY(85)
  DECLARE @x_name                   sysname
  DECLARE @enable_only_used         INT

  DECLARE @x_enabled                TINYINT
  DECLARE @x_freq_type              INT
  DECLARE @x_freq_interval          INT
  DECLARE @x_freq_subday_type       INT
  DECLARE @x_freq_subday_interval   INT
  DECLARE @x_freq_relative_interval INT
  DECLARE @x_freq_recurrence_factor INT
  DECLARE @x_active_start_date      INT
  DECLARE @x_active_end_date        INT
  DECLARE @x_active_start_time      INT
  DECLARE @x_active_end_time        INT
  DECLARE @schedule_uid             UNIQUEIDENTIFIER

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name              = LTRIM(RTRIM(@name))
  SELECT @new_name          = LTRIM(RTRIM(@new_name))
  SELECT @owner_login_name  = LTRIM(RTRIM(@owner_login_name))
  -- Turn [nullable] empty string parameters into NULLs
  IF (@new_name = N'') SELECT @new_name = NULL

   -- If the owner is supplied get the sid and check it
  IF(@owner_login_name IS NOT NULL AND @owner_login_name <> '')
  BEGIN
      -- Get the sid for @owner_login_name SID 
      --force case insensitive comparation for NT users
      SELECT @owner_sid = dbo.SQLAGENT_SUSER_SID(@owner_login_name)
    -- Cannot proceed if @owner_login_name doesn't exist
    IF(@owner_sid IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@owner_login_name', @owner_login_name)
      RETURN(1) -- Failure
    END
  END

  -- Check that we can uniquely identify the schedule. This only returns a schedule that is visible to this user
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @name             OUTPUT,
                                                            @schedule_id            = @schedule_id      OUTPUT,
                                                            @owner_sid              = @cur_owner_sid    OUTPUT,
                                                            @orig_server_id         = NULL
  IF (@retval <> 0)
      RETURN(1) -- Failure   

  -- Is @enable the only parameter used beside jobname and jobid?
  IF ((@enabled                   IS NOT NULL) AND
       (@new_name                 IS NULL) AND
      (@freq_type                 IS NULL) AND
      (@freq_interval             IS NULL) AND
      (@freq_subday_type          IS NULL) AND
      (@freq_subday_interval      IS NULL) AND
      (@freq_relative_interval    IS NULL) AND
      (@freq_recurrence_factor    IS NULL) AND
      (@active_start_date         IS NULL) AND
      (@active_end_date           IS NULL) AND
      (@active_start_time         IS NULL) AND
      (@active_end_time           IS NULL) AND
      (@owner_login_name          IS NULL))
    SELECT @enable_only_used = 1
  ELSE
    SELECT @enable_only_used = 0
      
  -- Non-sysadmins can only update jobs schedules they own. 
  -- Members of SQLAgentReaderRole and SQLAgentOperatorRole can view job schedules, 
  -- but they should not be able to delete them
  IF ((@cur_owner_sid <> SUSER_SID())
       AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'),0) <> 1)
      AND (@enable_only_used <> 1 OR ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) <> 1))
  BEGIN
   RAISERROR(14394, -1, -1)
   RETURN(1) -- Failure
  END
  
  -- If the param @owner_login_name is null or doesn't get resolved by SUSER_SID() set it to the current owner of the schedule
  if(@owner_sid IS NULL)
      SELECT @owner_sid = @cur_owner_sid
       
   -- Set the x_ (existing) variables
  SELECT @x_name                   = name,
         @x_enabled                = enabled,
         @x_freq_type              = freq_type,
         @x_freq_interval          = freq_interval,
         @x_freq_subday_type       = freq_subday_type,
         @x_freq_subday_interval   = freq_subday_interval,
         @x_freq_relative_interval = freq_relative_interval,
         @x_freq_recurrence_factor = freq_recurrence_factor,
         @x_active_start_date      = active_start_date,
         @x_active_end_date        = active_end_date,
         @x_active_start_time      = active_start_time,
         @x_active_end_time        = active_end_time
  FROM msdb.dbo.sysschedules
  WHERE (schedule_id = @schedule_id )     
  
  
    -- Fill out the values for all non-supplied parameters from the existing values
  IF (@new_name               IS NULL) SELECT @new_name               = @x_name
  IF (@enabled                IS NULL) SELECT @enabled                = @x_enabled
  IF (@freq_type              IS NULL) SELECT @freq_type              = @x_freq_type
  IF (@freq_interval          IS NULL) SELECT @freq_interval          = @x_freq_interval
  IF (@freq_subday_type       IS NULL) SELECT @freq_subday_type       = @x_freq_subday_type
  IF (@freq_subday_interval   IS NULL) SELECT @freq_subday_interval   = @x_freq_subday_interval
  IF (@freq_relative_interval IS NULL) SELECT @freq_relative_interval = @x_freq_relative_interval
  IF (@freq_recurrence_factor IS NULL) SELECT @freq_recurrence_factor = @x_freq_recurrence_factor
  IF (@active_start_date      IS NULL) SELECT @active_start_date      = @x_active_start_date
  IF (@active_end_date        IS NULL) SELECT @active_end_date        = @x_active_end_date
  IF (@active_start_time      IS NULL) SELECT @active_start_time      = @x_active_start_time
  IF (@active_end_time        IS NULL) SELECT @active_end_time        = @x_active_end_time
      
  -- Check schedule (frequency and owner) parameters
  EXECUTE @retval = sp_verify_schedule @schedule_id             = @schedule_id,
                                       @name                    = @new_name,
                                       @enabled                 = @enabled,
                                       @freq_type               = @freq_type,
                                       @freq_interval           = @freq_interval            OUTPUT,
                                       @freq_subday_type        = @freq_subday_type         OUTPUT,
                                       @freq_subday_interval    = @freq_subday_interval     OUTPUT,
                                       @freq_relative_interval  = @freq_relative_interval   OUTPUT,
                                       @freq_recurrence_factor  = @freq_recurrence_factor   OUTPUT,
                                       @active_start_date       = @active_start_date        OUTPUT,
                                       @active_start_time       = @active_start_time        OUTPUT,
                                       @active_end_date         = @active_end_date          OUTPUT,
                                       @active_end_time         = @active_end_time          OUTPUT,
                                       @owner_sid               = @owner_sid
  IF (@retval <> 0)
    RETURN(1) -- Failure  

  -- Update the sysschedules table
  UPDATE msdb.dbo.sysschedules
  SET name                   = @new_name,
      owner_sid              = @owner_sid,
      enabled                = @enabled,
      freq_type              = @freq_type,
      freq_interval          = @freq_interval,
      freq_subday_type       = @freq_subday_type,
      freq_subday_interval   = @freq_subday_interval,
      freq_relative_interval = @freq_relative_interval,
      freq_recurrence_factor = @freq_recurrence_factor,
      active_start_date      = @active_start_date,
      active_end_date        = @active_end_date,
      active_start_time      = @active_start_time,
      active_end_time        = @active_end_time,
      date_modified          = GETDATE(),
      version_number         = version_number + 1
  WHERE (schedule_id = @schedule_id)

  SELECT @retval = @@error

 -- update any job that has repl steps
  DECLARE @job_id UNIQUEIDENTIFIER
  DECLARE jobsschedule_cursor CURSOR LOCAL FOR
  SELECT job_id
  FROM msdb.dbo.sysjobschedules
  WHERE (schedule_id = @schedule_id)
  
  IF @x_freq_type <> @freq_type
  BEGIN
    OPEN jobsschedule_cursor
    FETCH NEXT FROM jobsschedule_cursor INTO @job_id

    WHILE (@@FETCH_STATUS = 0)
    BEGIN 
      EXEC  sp_update_replication_job_parameter @job_id = @job_id,
                                                @old_freq_type = @x_freq_type,
                                                @new_freq_type = @freq_type
      FETCH NEXT FROM jobsschedule_cursor INTO @job_id
    END
    CLOSE jobsschedule_cursor
  END
  DEALLOCATE jobsschedule_cursor
  
  -- Notify SQLServerAgent of the change if this is attached to a local job
  IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobschedules AS jsched 
              JOIN msdb.dbo.sysjobservers AS jsvr
                    ON jsched.job_id = jsvr.job_id
                WHERE (jsched.schedule_id = @schedule_id)
                  AND (jsvr.server_id = 0)) )
  BEGIN 
      EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                          @schedule_id = @schedule_id,
                                          @action_type = N'U'              
  END


  -- Instruct the tsx servers to pick up the altered schedule
  IF (@automatic_post = 1)
  BEGIN
      SELECT @schedule_uid = schedule_uid 
      FROM sysschedules 
      WHERE schedule_id = @schedule_id

      IF(NOT @schedule_uid IS NULL)
      BEGIN
          -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines 
          EXECUTE @retval = sp_post_msx_operation @operation = 'INSERT', @object_type = 'SCHEDULE', @schedule_uid = @schedule_uid
      END
  END  

  RETURN(@retval) -- 0 means success
END
GO


/**************************************************************/
/* SP_DELETE_SCHEDULE                                         */
/**************************************************************/
PRINT ''
PRINT 'Updating procedure sp_delete_schedule ...'
go

ALTER PROCEDURE sp_delete_schedule
(
  @schedule_id          INT                 = NULL,     -- Must provide either this or schedule_name
  @schedule_name        sysname             = NULL,     -- Must provide either this or schedule_id
  @force_delete         bit                 = 0,
  @automatic_post       BIT                 = 1         -- If 1 will post notifications to all tsx servers to that run this schedule
)   
AS
BEGIN
  DECLARE @retval           INT
  DECLARE @owner_sid        VARBINARY(85)
  DECLARE @job_count        INT
  DECLARE @targ_server_id   INT

  SET NOCOUNT ON
  --Get the owners sid       
  SELECT @job_count = 0

  -- Check that we can uniquely identify the schedule. This only returns a schedule that is visible to this user
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @schedule_name    OUTPUT,
                                                            @schedule_id            = @schedule_id      OUTPUT,
                                                            @owner_sid              = @owner_sid        OUTPUT,
                                                            @orig_server_id         = NULL
  IF (@retval <> 0)
    RETURN(1) -- Failure 

  -- Non-sysadmins can only update jobs schedules they own. 
  -- Members of SQLAgentReaderRole and SQLAgentOperatorRole can view job schedules, 
  -- but they should not be able to delete them
  IF ((@owner_sid <> SUSER_SID()) AND
     (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'),0) <> 1))
  BEGIN
   RAISERROR(14394, -1, -1)
   RETURN(1) -- Failure
  END
    
  --check if there are jobs using this schedule
  SELECT @job_count = count(*)
  FROM sysjobschedules 
  WHERE (schedule_id = @schedule_id)   
  
  -- If we aren't force deleting the schedule make sure no jobs are using it
  IF ((@force_delete = 0) AND (@job_count > 0))
  BEGIN 
    RAISERROR(14372, -1, -1)
    RETURN (1) -- Failure 
  END

  -- Get the one of the terget server_id's. 
  -- Getting MIN(jsvr.server_id) works here because we are only interested in this ID
  -- to determine if the schedule ID is for local jobs or MSX jobs. 
  -- Note, an MSX job can't be run on the local server
  SELECT @targ_server_id = MIN(jsvr.server_id)
  FROM msdb.dbo.sysjobschedules AS jsched 
   JOIN msdb.dbo.sysjobservers AS jsvr
      ON jsched.job_id = jsvr.job_id
  WHERE (jsched.schedule_id = @schedule_id)

  --OK to delete the job - schedule link
  DELETE sysjobschedules 
  WHERE schedule_id = @schedule_id

  --OK to delete the schedule 
  DELETE sysschedules 
  WHERE schedule_id = @schedule_id

  -- @targ_server_id would be null if no jobs use this schedule
  IF (@targ_server_id IS NOT NULL)
  BEGIN
   -- Notify SQLServerAgent of the change but only if it the schedule was used by a local job
   IF (@targ_server_id = 0)
   BEGIN 
      -- Only send a notification if the schedule is force deleted. If it isn't force deleted
      -- a notification would have already been sent while detaching the schedule (sp_detach_schedule)
      IF (@force_delete = 1)
      BEGIN
        EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                   @schedule_id = @schedule_id,
                                   @action_type = N'D'
      END                   
   END
   ELSE
   BEGIN
    -- Instruct the tsx servers to pick up the altered schedule
    IF (@automatic_post = 1)
    BEGIN
      DECLARE @schedule_uid UNIQUEIDENTIFIER
      SELECT @schedule_uid = schedule_uid 
      FROM sysschedules 
      WHERE schedule_id = @schedule_id

      IF(NOT @schedule_uid IS NULL)
      BEGIN
        -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines 
        EXECUTE sp_post_msx_operation @operation = 'INSERT', @object_type = 'SCHEDULE', @schedule_uid = @schedule_uid
      END
    END
    ELSE
      RAISERROR(14547, 0, 1, N'INSERT', N'sp_post_msx_operation')
   END
  END
  
  RETURN(@retval) -- 0 means success
END
GO

/**************************************************************/
/* SP_ADD_JOBSCHEDULE                                         */
/**************************************************************/

PRINT ''
PRINT 'Updating procedure sp_add_jobschedule...'
go
ALTER PROCEDURE sp_add_jobschedule                 
  @job_id                 UNIQUEIDENTIFIER = NULL,
  @job_name               sysname          = NULL,
  @name                   sysname,
  @enabled                TINYINT          = 1,
  @freq_type              INT              = 1,
  @freq_interval          INT              = 0,
  @freq_subday_type       INT              = 0,
  @freq_subday_interval   INT              = 0,
  @freq_relative_interval INT              = 0,
  @freq_recurrence_factor INT              = 0,
  @active_start_date      INT              = NULL,     -- sp_verify_schedule assigns a default
  @active_end_date        INT              = 99991231, -- December 31st 9999
  @active_start_time      INT              = 000000,   -- 12:00:00 am
  @active_end_time        INT              = 235959,    -- 11:59:59 pm
  @schedule_id            INT              = NULL  OUTPUT,
  @automatic_post         BIT              = 1         -- If 1 will post notifications to all tsx servers to that run this job
AS
BEGIN
  DECLARE @retval           INT
  DECLARE @owner_login_name sysname

  SET NOCOUNT ON

  -- Check authority (only SQLServerAgent can add a schedule to a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Check that we can uniquely identify the job
  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Get the owner of the job. Prior to resusable schedules the job owner also owned the schedule
  SELECT @owner_login_name = dbo.SQLAGENT_SUSER_SNAME(owner_sid)
  FROM   sysjobs
  WHERE  (job_id = @job_id) 

  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) AND
      (SUSER_SNAME() <> @owner_login_name))
  BEGIN
   RAISERROR(14525, -1, -1)
   RETURN(1) -- Failure
  END

  -- Check authority (only SQLServerAgent can add a schedule to a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Add the schedule first
  EXECUTE @retval = msdb.dbo.sp_add_schedule @schedule_name          = @name,
                                             @enabled                = @enabled,
                                             @freq_type              = @freq_type,
                                             @freq_interval          = @freq_interval,
                                             @freq_subday_type       = @freq_subday_type,
                                             @freq_subday_interval   = @freq_subday_interval,
                                             @freq_relative_interval = @freq_relative_interval,
                                             @freq_recurrence_factor = @freq_recurrence_factor,
                                             @active_start_date      = @active_start_date,
                                             @active_end_date        = @active_end_date,
                                             @active_start_time      = @active_start_time,
                                             @active_end_time        = @active_end_time,
                                             @owner_login_name       = @owner_login_name,
                                             @schedule_id            = @schedule_id  OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure
 
 
  EXECUTE @retval = msdb.dbo.sp_attach_schedule @job_id           = @job_id, 
                                                @job_name         = NULL,
                                                @schedule_id      = @schedule_id,
                                                @schedule_name    = NULL,
                                                @automatic_post   = @automatic_post
  IF (@retval <> 0)
    RETURN(1) -- Failure
    
    

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_UPDATE_JOBSCHEDULE                                      */
/**************************************************************/

PRINT ''
PRINT 'Updating procedure sp_update_jobschedule...'
go
ALTER PROCEDURE sp_update_jobschedule              -- This SP is deprecated by sp_update_schedule.
  @job_id                 UNIQUEIDENTIFIER = NULL,
  @job_name               sysname          = NULL,
  @name                   sysname,
  @new_name               sysname          = NULL,
  @enabled                TINYINT          = NULL,
  @freq_type              INT              = NULL,
  @freq_interval          INT              = NULL,
  @freq_subday_type       INT              = NULL,
  @freq_subday_interval   INT              = NULL,
  @freq_relative_interval INT              = NULL,
  @freq_recurrence_factor INT              = NULL,
  @active_start_date      INT              = NULL,
  @active_end_date        INT              = NULL,
  @active_start_time      INT              = NULL,
  @active_end_time        INT              = NULL,
  @automatic_post         BIT              = 1         -- If 1 will post notifications to all tsx servers to that run this job
AS
BEGIN
  DECLARE @retval                   INT
  DECLARE @sched_count              INT
  DECLARE @schedule_id              INT
  DECLARE @job_owner_sid            VARBINARY(85)
  DECLARE @enable_only_used         INT

  DECLARE @x_name                   sysname
  DECLARE @x_enabled                TINYINT
  DECLARE @x_freq_type              INT
  DECLARE @x_freq_interval          INT
  DECLARE @x_freq_subday_type       INT
  DECLARE @x_freq_subday_interval   INT
  DECLARE @x_freq_relative_interval INT
  DECLARE @x_freq_recurrence_factor INT
  DECLARE @x_active_start_date      INT
  DECLARE @x_active_end_date        INT
  DECLARE @x_active_start_time      INT
  DECLARE @x_active_end_time        INT
  DECLARE @owner_sid                VARBINARY(85)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name     = LTRIM(RTRIM(@name))
  SELECT @new_name = LTRIM(RTRIM(@new_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@new_name = N'') SELECT @new_name = NULL

  -- Check authority (only SQLServerAgent can modify a schedule of a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = 'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Check that we can uniquely identify the job
  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                               @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure
    
  -- Is @enable the only parameter used beside jobname and jobid?
  IF ((@enabled                   IS NOT NULL) AND
     (@name                 IS NULL) AND
     (@new_name                IS NULL) AND
      (@freq_type              IS NULL) AND
      (@freq_interval             IS NULL) AND
      (@freq_subday_type          IS NULL) AND
      (@freq_subday_interval      IS NULL) AND
      (@freq_relative_interval       IS NULL) AND
      (@freq_recurrence_factor       IS NULL) AND
      (@active_start_date         IS NULL) AND
      (@active_end_date           IS NULL) AND
      (@active_start_time         IS NULL) AND
      (@active_end_time           IS NULL))
    SELECT @enable_only_used = 1
  ELSE
    SELECT @enable_only_used = 0
    

  IF ((SUSER_SID() <> @job_owner_sid)
     AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
      AND (@enable_only_used <> 1 OR ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) <> 1))
  BEGIN
   RAISERROR(14525, -1, -1)
   RETURN(1) -- Failure
  END

  -- Make sure the schedule_id can be uniquely identified and that it exists
  -- Note: It's safe use the values returned by the MIN() function because the SP errors out if more than 1 record exists
  SELECT @sched_count = COUNT(*),
         @schedule_id = MIN(sched.schedule_id),
         @owner_sid   = MIN(sched.owner_sid)
  FROM msdb.dbo.sysjobschedules as jsched
    JOIN msdb.dbo.sysschedules_localserver_view as sched
      ON jsched.schedule_id = sched.schedule_id
  WHERE (jsched.job_id = @job_id)
    AND (sched.name = @name)

  -- Need to use sp_update_schedule to update this ambiguous schedule name
  IF(@sched_count > 1)
  BEGIN
    RAISERROR(14375, -1, -1, @name, @job_name)
    RETURN(1) -- Failure
  END

  IF (@schedule_id IS NULL)
  BEGIN
   --raise an explicit message if the schedule does exist but isn't attached to this job
   IF EXISTS(SELECT * 
           FROM sysschedules_localserver_view
              WHERE (name = @name))
   BEGIN
      RAISERROR(14374, -1, -1, @name, @job_name)
   END
   ELSE
    BEGIN
      --If the schedule is from an MSX and a sysadmin is calling report a specific 'MSX' message
      IF(PROGRAM_NAME() NOT LIKE N'SQLAgent%' AND
         ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1 AND
         EXISTS(SELECT * 
                FROM msdb.dbo.sysschedules as sched
                  JOIN msdb.dbo.sysoriginatingservers_view as svr
                    ON sched.originating_server_id = svr.originating_server_id
                  JOIN msdb.dbo.sysjobschedules as js 
                    ON sched.schedule_id = js.schedule_id
                WHERE (svr.master_server = 1) AND
                      (sched.name = @name) AND
                      (js.job_id = @job_id)))
     BEGIN
       RAISERROR(14274, -1, -1)
     END
      ELSE
      BEGIN
        --Generic message that the schedule doesn't exist
        RAISERROR(14262, -1, -1, 'Schedule Name', @name)
      END
   END

   RETURN(1) -- Failure
  END

  -- Set the x_ (existing) variables
  SELECT @x_name                   = name,
         @x_enabled                = enabled,
         @x_freq_type              = freq_type,
         @x_freq_interval          = freq_interval,
         @x_freq_subday_type       = freq_subday_type,
         @x_freq_subday_interval   = freq_subday_interval,
         @x_freq_relative_interval = freq_relative_interval,
         @x_freq_recurrence_factor = freq_recurrence_factor,
         @x_active_start_date      = active_start_date,
         @x_active_end_date        = active_end_date,
         @x_active_start_time      = active_start_time,
         @x_active_end_time        = active_end_time
  FROM msdb.dbo.sysschedules_localserver_view
  WHERE (schedule_id = @schedule_id )

  
  -- Fill out the values for all non-supplied parameters from the existing values
  IF (@new_name               IS NULL) SELECT @new_name               = @x_name
  IF (@enabled                IS NULL) SELECT @enabled                = @x_enabled
  IF (@freq_type              IS NULL) SELECT @freq_type              = @x_freq_type
  IF (@freq_interval          IS NULL) SELECT @freq_interval          = @x_freq_interval
  IF (@freq_subday_type       IS NULL) SELECT @freq_subday_type       = @x_freq_subday_type
  IF (@freq_subday_interval   IS NULL) SELECT @freq_subday_interval   = @x_freq_subday_interval
  IF (@freq_relative_interval IS NULL) SELECT @freq_relative_interval = @x_freq_relative_interval
  IF (@freq_recurrence_factor IS NULL) SELECT @freq_recurrence_factor = @x_freq_recurrence_factor
  IF (@active_start_date      IS NULL) SELECT @active_start_date      = @x_active_start_date
  IF (@active_end_date        IS NULL) SELECT @active_end_date        = @x_active_end_date
  IF (@active_start_time      IS NULL) SELECT @active_start_time      = @x_active_start_time
  IF (@active_end_time        IS NULL) SELECT @active_end_time        = @x_active_end_time

  -- Check schedule (frequency and owner) parameters
  EXECUTE @retval = sp_verify_schedule @schedule_id             = @schedule_id,
                                       @name                    = @new_name,
                                       @enabled                 = @enabled,
                                       @freq_type               = @freq_type,
                                       @freq_interval           = @freq_interval            OUTPUT,
                                       @freq_subday_type        = @freq_subday_type         OUTPUT,
                                       @freq_subday_interval    = @freq_subday_interval     OUTPUT,
                                       @freq_relative_interval  = @freq_relative_interval   OUTPUT,
                                       @freq_recurrence_factor  = @freq_recurrence_factor   OUTPUT,
                                       @active_start_date       = @active_start_date        OUTPUT,
                                       @active_start_time       = @active_start_time        OUTPUT,
                                       @active_end_date         = @active_end_date          OUTPUT,
                                       @active_end_time         = @active_end_time          OUTPUT,
                                       @owner_sid               = @owner_sid
  IF (@retval <> 0)
    RETURN(1) -- Failure


  -- Update the JobSchedule
  UPDATE msdb.dbo.sysschedules
  SET name                   = @new_name,
      enabled                = @enabled,
      freq_type              = @freq_type,
      freq_interval          = @freq_interval,
      freq_subday_type       = @freq_subday_type,
      freq_subday_interval   = @freq_subday_interval,
      freq_relative_interval = @freq_relative_interval,
      freq_recurrence_factor = @freq_recurrence_factor,
      active_start_date      = @active_start_date,
      active_end_date        = @active_end_date,
      active_start_time      = @active_start_time,
      active_end_time        = @active_end_time,
      date_modified          = GETDATE(),
      version_number         = version_number + 1
  WHERE (schedule_id = @schedule_id)

  SELECT @retval = @@error

  -- Update the job's version/last-modified information
  UPDATE msdb.dbo.sysjobs
  SET version_number = version_number + 1,
      date_modified = GETDATE()
  WHERE (job_id = @job_id)

  -- Notify SQLServerAgent of the change, but only if we know the job has been cached
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id = 0)))
  BEGIN
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                        @job_id      = @job_id,
                                        @schedule_id = @schedule_id,
                                        @action_type = N'U'
  END

  -- For a multi-server job, remind the user that they need to call sp_post_msx_operation
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
    -- Instruct the tsx servers to pick up the altered schedule
    IF (@automatic_post = 1)
    BEGIN
      DECLARE @schedule_uid UNIQUEIDENTIFIER
      SELECT @schedule_uid = schedule_uid 
      FROM sysschedules 
      WHERE schedule_id = @schedule_id

      IF(NOT @schedule_uid IS NULL)
      BEGIN
        -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines 
        EXECUTE sp_post_msx_operation @operation = 'INSERT', @object_type = 'SCHEDULE', @schedule_uid = @schedule_uid
      END
    END
    ELSE
      RAISERROR(14547, 0, 1, N'INSERT', N'sp_post_msx_operation')

  -- Automatic addition and removal of -Continous parameter for replication agent
  EXECUTE sp_update_replication_job_parameter @job_id = @job_id,
                                              @old_freq_type = @x_freq_type,
                                              @new_freq_type = @freq_type

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_JOBSCHEDULE                                      */
/**************************************************************/

PRINT ''
PRINT 'Updating procedure sp_delete_jobschedule...'
go
ALTER PROCEDURE sp_delete_jobschedule           -- This SP is deprecated. Use sp_detach_schedule and sp_delete_schedule.
  @job_id           UNIQUEIDENTIFIER = NULL,
  @job_name         sysname          = NULL,
  @name             sysname,
  @keep_schedule    int              = 0,
  @automatic_post       BIT          = 1         -- If 1 will post notifications to all tsx servers to that run this schedule
AS
BEGIN
  DECLARE @retval           INT
  DECLARE @sched_count      INT
  DECLARE @schedule_id      INT
  DECLARE @job_owner_sid    VARBINARY(85)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name = LTRIM(RTRIM(@name))

  -- Check authority (only SQLServerAgent can delete a schedule of a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Check that we can uniquely identify the job
  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                               @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) AND
      (SUSER_SID() <> @job_owner_sid))
  BEGIN
   RAISERROR(14525, -1, -1)
   RETURN(1) -- Failure
  END


  IF (UPPER(@name collate SQL_Latin1_General_CP1_CS_AS) = N'ALL')
  BEGIN
    SELECT @schedule_id = -1  -- We use this in the call to sp_sqlagent_notify
    
    --Delete the schedule(s) if it isn't being used by other jobs
    DECLARE @temp_schedules_to_delete TABLE (schedule_id INT NOT NULL)


    --If user requests that the schedules be removed (the legacy behavoir)
    --make sure it isnt being used by other jobs
    IF (@keep_schedule = 0)
    BEGIN
        --Get the list of schedules to delete
        INSERT INTO @temp_schedules_to_delete
        SELECT DISTINCT schedule_id 
        FROM   msdb.dbo.sysschedules
        WHERE (schedule_id IN 
                (SELECT schedule_id
                FROM msdb.dbo.sysjobschedules
                WHERE (job_id = @job_id)))
            
        --make sure no other jobs use these schedules
        IF( EXISTS(SELECT *
                    FROM msdb.dbo.sysjobschedules 
                    WHERE (job_id <> @job_id)
                    AND (schedule_id in ( SELECT schedule_id 
                                            FROM @temp_schedules_to_delete ))))
        BEGIN
        RAISERROR(14367, -1, -1)   
        RETURN(1) -- Failure
        END
    END

    --OK to delete the jobschedule
    DELETE FROM msdb.dbo.sysjobschedules
    WHERE (job_id = @job_id)
    
    --OK to delete the schedule - temp_schedules_to_delete is empty if @keep_schedule <> 0
    DELETE FROM msdb.dbo.sysschedules
    WHERE schedule_id IN 
    (SELECT schedule_id from @temp_schedules_to_delete)
  END
  ELSE
  BEGIN

    -- Make sure the schedule_id can be uniquely identified and that it exists
    -- Note: It's safe use the values returned by the MIN() function because the SP errors out if more than 1 record exists
    SELECT @sched_count = COUNT(*),
           @schedule_id = MIN(sched.schedule_id)
    FROM msdb.dbo.sysjobschedules as jsched
      JOIN msdb.dbo.sysschedules_localserver_view as sched
        ON jsched.schedule_id = sched.schedule_id
    WHERE (jsched.job_id = @job_id)
      AND (sched.name = @name)
  
    -- Need to use sp_detach_schedule to remove this ambiguous schedule name
    IF(@sched_count > 1)
    BEGIN
      RAISERROR(14376, -1, -1, @name, @job_name)
      RETURN(1) -- Failure
    END

    IF (@schedule_id IS NULL)
    BEGIN    
     --raise an explicit message if the schedule does exist but isn't attached to this job
     IF EXISTS(SELECT * 
             FROM sysschedules_localserver_view
                WHERE (name = @name))
     BEGIN
      RAISERROR(14374, -1, -1, @name, @job_name)
     END
     ELSE
      BEGIN
        --If the schedule is from an MSX and a sysadmin is calling report a specific 'MSX' message
        IF(PROGRAM_NAME() NOT LIKE N'SQLAgent%' AND
           ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1 AND
           EXISTS(SELECT * 
                  FROM msdb.dbo.sysschedules as sched
                    JOIN msdb.dbo.sysoriginatingservers_view as svr
                      ON sched.originating_server_id = svr.originating_server_id
                    JOIN msdb.dbo.sysjobschedules as js 
                      ON sched.schedule_id = js.schedule_id
                  WHERE (svr.master_server = 1) AND
                        (sched.name = @name) AND
                        (js.job_id = @job_id)))
       BEGIN
         RAISERROR(14274, -1, -1)
       END
        ELSE
        BEGIN
          --Generic message that the schedule doesn't exist
          RAISERROR(14262, -1, -1, '@name', @name)
        END
     END

      RETURN(1) -- Failure
    END

    --If user requests that the schedule be removed (the legacy behavoir)
    --make sure it isnt being used by another job
    IF (@keep_schedule = 0)
    BEGIN
      IF( EXISTS(SELECT * 
                 FROM msdb.dbo.sysjobschedules
                 WHERE (schedule_id = @schedule_id)
                   AND (job_id <> @job_id) ))
      BEGIN
        RAISERROR(14368, -1, -1, @name)
        RETURN(1) -- Failure
      END
    END

    --Delete the job schedule link first
    DELETE FROM msdb.dbo.sysjobschedules
    WHERE (job_id = @job_id)
    AND (schedule_id = @schedule_id)
    --Delete schedule if required
    IF (@keep_schedule = 0)
    BEGIN
      --Now delete the schedule if required
      DELETE FROM msdb.dbo.sysschedules
      WHERE (schedule_id = @schedule_id)   
    END

  END


  -- Update the job's version/last-modified information
  UPDATE msdb.dbo.sysjobs
  SET version_number = version_number + 1,
      date_modified = GETDATE()
  WHERE (job_id = @job_id)

  -- Notify SQLServerAgent of the change, but only if we know the job has been cached
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id = 0)))
  BEGIN
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                        @job_id      = @job_id,
                                        @schedule_id = @schedule_id,
                                        @action_type = N'D'
  END

  -- For a multi-server job, remind the user that they need to call sp_post_msx_operation
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
    -- Instruct the tsx servers to pick up the altered schedule
    IF (@automatic_post = 1)
    BEGIN
      DECLARE @schedule_uid UNIQUEIDENTIFIER
      SELECT @schedule_uid = schedule_uid 
      FROM sysschedules 
      WHERE schedule_id = @schedule_id

      IF(NOT @schedule_uid IS NULL)
      BEGIN
        -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines 
        EXECUTE sp_post_msx_operation @operation = 'INSERT', @object_type = 'SCHEDULE', @schedule_uid = @schedule_uid
      END
    END
    ELSE
      RAISERROR(14547, 0, 1, N'INSERT', N'sp_post_msx_operation')

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_SQLAGENT_HAS_SERVER_ACCESS                              */
/**************************************************************/

PRINT ''
PRINT 'Updating procedure sp_sqlagent_has_server_access...'
go

ALTER PROCEDURE sp_sqlagent_has_server_access
  @login_name         sysname = NULL,
  @is_sysadmin_member INT     = NULL OUTPUT
AS
BEGIN
  DECLARE @has_server_access BIT
  DECLARE @is_sysadmin       BIT
  DECLARE @actual_login_name sysname
  DECLARE @cachedate         DATETIME

  SET NOCOUNT ON

  SELECT @cachedate = NULL

  -- remove expired entries from the cache
  DELETE msdb.dbo.syscachedcredentials
  WHERE  DATEDIFF(MINUTE, cachedate, GETDATE()) >= 29

  -- query the cache
  SELECT  @is_sysadmin = is_sysadmin_member,
          @has_server_access = has_server_access,
          @cachedate = cachedate
  FROM    msdb.dbo.syscachedcredentials
  WHERE   login_name = @login_name
  AND     DATEDIFF(MINUTE, cachedate, GETDATE()) < 29

  IF (@cachedate IS NOT NULL)
  BEGIN
    -- no output variable
    IF (@is_sysadmin_member IS NULL)
    BEGIN
      -- Return result row
      SELECT has_server_access = @has_server_access,
             is_sysadmin       = @is_sysadmin,
             actual_login_name = @login_name
      RETURN
    END
    ELSE
    BEGIN
      SELECT @is_sysadmin_member = @is_sysadmin
      RETURN
    END
  END -- select from cache

  -- Set defaults
  SELECT @has_server_access = 0
  SELECT @is_sysadmin = 0
  SELECT @actual_login_name = FORMATMESSAGE(14205)

  IF (@login_name IS NULL)
  BEGIN
    SELECT has_server_access = 1,
           is_sysadmin       = IS_SRVROLEMEMBER(N'sysadmin'),
           actual_login_name = SUSER_SNAME()
    RETURN
  END

  IF (@login_name LIKE '%\%')
  BEGIN
    -- Handle the LocalSystem account ('NT AUTHORITY\SYSTEM') as a special case
    IF (UPPER(@login_name collate SQL_Latin1_General_CP1_CS_AS) = N'NT AUTHORITY\SYSTEM')
    BEGIN
      IF (EXISTS (SELECT *
                  FROM master.dbo.syslogins
                  WHERE (UPPER(loginname collate SQL_Latin1_General_CP1_CS_AS) = N'BUILTIN\ADMINISTRATORS')))
      BEGIN
        SELECT @has_server_access = hasaccess,
               @is_sysadmin = sysadmin,
               @actual_login_name = loginname
        FROM master.dbo.syslogins
        WHERE (UPPER(loginname collate SQL_Latin1_General_CP1_CS_AS) = N'BUILTIN\ADMINISTRATORS')
      END
    END
    ELSE
    BEGIN
      -- Check if the NT login has been explicitly denied access
      IF (EXISTS (SELECT *
                  FROM master.dbo.syslogins
                  WHERE (loginname = @login_name)
                    AND (denylogin = 1)))
      BEGIN
        SELECT @has_server_access = 0,
               @is_sysadmin = sysadmin,
               @actual_login_name = loginname
        FROM master.dbo.syslogins
        WHERE (loginname = @login_name)
      END
      ELSE
      BEGIN
        -- declare table variable for storing results
        DECLARE @xp_results TABLE
        (
        account_name      sysname      COLLATE database_default NOT NULL PRIMARY KEY,
        type              NVARCHAR(10) COLLATE database_default NOT NULL,
        privilege         NVARCHAR(10) COLLATE database_default NOT NULL,
        mapped_login_name sysname      COLLATE database_default NOT NULL,
        permission_path   sysname      COLLATE database_default NULL
        )

        -- Call xp_logininfo to determine server access
        INSERT INTO @xp_results
        EXECUTE master.dbo.xp_logininfo @login_name

        SELECT @has_server_access = CASE COUNT(*)
                                      WHEN 0 THEN 0
                                      ELSE 1
                                    END
        FROM @xp_results
        SELECT @actual_login_name = mapped_login_name,
               @is_sysadmin = CASE UPPER(privilege collate SQL_Latin1_General_CP1_CS_AS)
                                WHEN 'ADMIN' THEN 1
                                ELSE 0
                             END
        FROM @xp_results
      END
    END
  END
  ELSE
  BEGIN
    -- Standard login
    IF (EXISTS (SELECT *
                FROM master.dbo.syslogins
                WHERE (loginname = @login_name)))
    BEGIN
      SELECT @has_server_access = hasaccess,
             @is_sysadmin = sysadmin,
             @actual_login_name = loginname
      FROM master.dbo.syslogins
      WHERE (loginname = @login_name)
    END
  END

  -- update the cache only if something is found
  IF  (UPPER(@actual_login_name collate SQL_Latin1_General_CP1_CS_AS) <> '(UNKNOWN)')
  BEGIN
    -- Procedure starts its own transaction.
    BEGIN TRANSACTION;
    
    -- Modify database.
    -- use a try catch login to prevent any error when trying 
    -- to insert/update syscachedcredentials table
    -- no need to fail since the job owner has been validated
    BEGIN TRY      
      IF EXISTS (SELECT * FROM msdb.dbo.syscachedcredentials WITH (TABLOCKX) WHERE login_name = @login_name)
      BEGIN
        UPDATE msdb.dbo.syscachedcredentials
        SET    has_server_access = @has_server_access,
              is_sysadmin_member = @is_sysadmin,
              cachedate = GETDATE()
        WHERE  login_name = @login_name
      END
      ELSE
      BEGIN
        INSERT INTO msdb.dbo.syscachedcredentials(login_name, has_server_access, is_sysadmin_member) 
        VALUES(@login_name, @has_server_access, @is_sysadmin)
      END
	  END TRY
	  BEGIN CATCH
		  -- If an error occurred we want to ignore it
	  END CATCH
	  
	  -- The procedure must commit the transaction it started.
	  COMMIT TRANSACTION;  
  END
  
  IF (@is_sysadmin_member IS NULL)
    -- Return result row
    SELECT has_server_access = @has_server_access,
           is_sysadmin       = @is_sysadmin,
           actual_login_name = @actual_login_name
  ELSE
    -- output variable only
    SELECT @is_sysadmin_member = @is_sysadmin
END
go

/**************************************************************/
/* SP_ADD_JOBSTEP_INTERNAL                                    */
/**************************************************************/

PRINT ''
PRINT 'Updating procedure sp_add_jobstep_internal...'
go
ALTER PROCEDURE dbo.sp_add_jobstep_internal
  @job_id                UNIQUEIDENTIFIER = NULL,   -- Must provide either this or job_name
  @job_name              sysname          = NULL,   -- Must provide either this or job_id
  @step_id               INT              = NULL,   -- The proc assigns a default
  @step_name             sysname,
  @subsystem             NVARCHAR(40)     = N'TSQL',
  @command               NVARCHAR(max)    = NULL,
  @additional_parameters NTEXT            = NULL,
  @cmdexec_success_code  INT              = 0,
  @on_success_action     TINYINT          = 1,      -- 1 = Quit With Success, 2 = Quit With Failure, 3 = Goto Next Step, 4 = Goto Step
  @on_success_step_id    INT              = 0,
  @on_fail_action        TINYINT          = 2,      -- 1 = Quit With Success, 2 = Quit With Failure, 3 = Goto Next Step, 4 = Goto Step
  @on_fail_step_id       INT              = 0,
  @server                sysname          = NULL,
  @database_name         sysname          = NULL,
  @database_user_name    sysname          = NULL,
  @retry_attempts        INT              = 0,      -- No retries
  @retry_interval        INT              = 0,      -- 0 minute interval
  @os_run_priority       INT              = 0,      -- -15 = Idle, -1 = Below Normal, 0 = Normal, 1 = Above Normal, 15 = Time Critical)
  @output_file_name      NVARCHAR(200)    = NULL,
  @flags                 INT              = 0,       --  0 = Normal, 
                                                     --  1 = Encrypted command (read only), 
                                                     --  2 = Append output files (if any), 
                                                     --  4 = Write TSQL step output to step history
                                                     --  8 = Write log to table (overwrite existing history)
                                                     -- 16 = Write log to table (append to existing history)
  @proxy_id               int               = NULL,
  @proxy_name              sysname           = NULL,
  -- mutual exclusive; must specify only one of above 2 parameters to 
  -- identify the proxy. 
  @step_uid UNIQUEIDENTIFIER              = NULL OUTPUT
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @max_step_id    INT
  DECLARE @job_owner_sid  VARBINARY(85)
  DECLARE @subsystem_id   INT
  DECLARE @auto_proxy_name sysname
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @step_name          = LTRIM(RTRIM(@step_name))
  SELECT @subsystem          = LTRIM(RTRIM(@subsystem))
  SELECT @server             = LTRIM(RTRIM(@server))
  SELECT @database_name      = LTRIM(RTRIM(@database_name))
  SELECT @database_user_name = LTRIM(RTRIM(@database_user_name))
  SELECT @output_file_name   = LTRIM(RTRIM(@output_file_name))
  SELECT @proxy_name         = LTRIM(RTRIM(@proxy_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@server             = N'') SELECT @server             = NULL
  IF (@database_name      = N'') SELECT @database_name      = NULL
  IF (@database_user_name = N'') SELECT @database_user_name = NULL
  IF (@output_file_name   = N'') SELECT @output_file_name   = NULL
  IF (@proxy_name         = N'') SELECT @proxy_name         = NULL

  -- Check authority (only SQLServerAgent can add a step to a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                               @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) AND
      (SUSER_SID() <> @job_owner_sid))
  BEGIN
     RAISERROR(14525, -1, -1)
     RETURN(1) -- Failure
  END
  

  -- check proxy identifiers only if a proxy has been provided
  IF (@proxy_id IS NOT NULL) or (@proxy_name IS NOT NULL)
  BEGIN
    EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                  '@proxy_id',
                                                   @proxy_name OUTPUT,
                                                   @proxy_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
   END

  -- Default step id (if not supplied)
  IF (@step_id IS NULL)
  BEGIN
    SELECT @step_id = ISNULL(MAX(step_id), 0) + 1
    FROM msdb.dbo.sysjobsteps
    WHERE (job_id = @job_id)
  END

  -- Check parameters
  EXECUTE @retval = sp_verify_jobstep @job_id,
                                      @step_id,
                                      @step_name,
                                      @subsystem,
                                      @command,
                                      @server,
                                      @on_success_action,
                                      @on_success_step_id,
                                      @on_fail_action,
                                      @on_fail_step_id,
                                      @os_run_priority,
                                      @database_name      OUTPUT,
                                      @database_user_name OUTPUT,
                                      @flags,
                                      @output_file_name,
                                               @proxy_id

  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Get current maximum step id
  SELECT @max_step_id = ISNULL(MAX(step_id), 0)
  FROM msdb.dbo.sysjobsteps
  WHERE (job_id = @job_id)

  DECLARE @TranCounter INT;
  SET @TranCounter = @@TRANCOUNT;
  IF @TranCounter = 0
  BEGIN
	  -- start our own transaction if there is no outer transaction
      BEGIN TRANSACTION;
  END
  
  -- Modify database.
  BEGIN TRY
    -- Update the job's version/last-modified information
    UPDATE msdb.dbo.sysjobs
    SET version_number = version_number + 1,
        date_modified = GETDATE()
    WHERE (job_id = @job_id)

    -- Adjust step id's (unless the new step is being inserted at the 'end')
    -- NOTE: We MUST do this before inserting the step.
    IF (@step_id <= @max_step_id)
    BEGIN
      UPDATE msdb.dbo.sysjobsteps
      SET step_id = step_id + 1
      WHERE (step_id >= @step_id)
        AND (job_id = @job_id)

      -- Clean up OnSuccess/OnFail references
      UPDATE msdb.dbo.sysjobsteps
      SET on_success_step_id = on_success_step_id + 1
      WHERE (on_success_step_id >= @step_id)
        AND (job_id = @job_id)

      UPDATE msdb.dbo.sysjobsteps
      SET on_fail_step_id = on_fail_step_id + 1
      WHERE (on_fail_step_id >= @step_id)
        AND (job_id = @job_id)

      UPDATE msdb.dbo.sysjobsteps
      SET on_success_step_id = 0,
          on_success_action = 1  -- Quit With Success
      WHERE (on_success_step_id = @step_id)
        AND (job_id = @job_id)

      UPDATE msdb.dbo.sysjobsteps
      SET on_fail_step_id = 0,
          on_fail_action = 2     -- Quit With Failure
      WHERE (on_fail_step_id = @step_id)
        AND (job_id = @job_id)
    END

    SELECT @step_uid = NEWID()

    -- Insert the step
    INSERT INTO msdb.dbo.sysjobsteps
           (job_id,
            step_id,
            step_name,
            subsystem,
            command,
            flags,
            additional_parameters,
            cmdexec_success_code,
            on_success_action,
            on_success_step_id,
            on_fail_action,
            on_fail_step_id,
            server,
            database_name,
            database_user_name,
            retry_attempts,
            retry_interval,
            os_run_priority,
            output_file_name,
            last_run_outcome,
            last_run_duration,
            last_run_retries,
            last_run_date,
            last_run_time,
            proxy_id,
         step_uid)
    VALUES (@job_id,
            @step_id,
            @step_name,
            @subsystem,
            @command,
            @flags,
            @additional_parameters,
            @cmdexec_success_code,
            @on_success_action,
            @on_success_step_id,
            @on_fail_action,
            @on_fail_step_id,
            @server,
            @database_name,
            @database_user_name,
            @retry_attempts,
            @retry_interval,
            @os_run_priority,
            @output_file_name,
            0,
            0,
            0,
            0,
            0,
         @proxy_id,
         @step_uid)
         
  IF @TranCounter = 0
  BEGIN
	  -- start our own transaction if there is no outer transaction
      COMMIT TRANSACTION;
  END

  END TRY
  BEGIN CATCH

      -- Prepare tp echo error information to the caller.
      DECLARE @ErrorMessage NVARCHAR(400)
      DECLARE @ErrorSeverity INT
      DECLARE @ErrorState INT

      SELECT @ErrorMessage = ERROR_MESSAGE()
      SELECT @ErrorSeverity = ERROR_SEVERITY()
      SELECT @ErrorState = ERROR_STATE()
      
      IF @TranCounter = 0
      BEGIN
          -- Transaction started in procedure.
          -- Roll back complete transaction.
          ROLLBACK TRANSACTION;
      END
      RAISERROR (@ErrorMessage, -- Message text.
                  @ErrorSeverity, -- Severity.
                  @ErrorState -- State.
                  )
      RETURN (1)                  
  END CATCH
  
  -- Make sure that SQLServerAgent refreshes the job if the 'Has Steps' property has changed
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.sysjobsteps
       WHERE (job_id = @job_id)) = 1)
  BEGIN
    -- NOTE: We only notify SQLServerAgent if we know the job has been cached
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                  AND (server_id = 0)))
      EXECUTE msdb.dbo.sp_sqlagent_notify @op_type       = N'J',
                                            @job_id      = @job_id,
                                            @action_type = N'U'
  END

  -- For a multi-server job, push changes to the target servers
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
  BEGIN
    EXECUTE msdb.dbo.sp_post_msx_operation 'INSERT', 'JOB', @job_id
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_SQLAGENT_REFRESH_JOB                                    */
/**************************************************************/

PRINT ''
PRINT 'Updating procedure sp_sqlagent_refresh_job...'
go
ALTER PROCEDURE sp_sqlagent_refresh_job
  @job_id      UNIQUEIDENTIFIER = NULL,
  @server_name sysname          = NULL -- This parameter allows a TSX to use this SP when updating a job
AS
BEGIN
  DECLARE @server_id INT

  SET NOCOUNT ON

  IF (@server_name IS NULL) OR (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = '(LOCAL)')
    SELECT @server_name = CONVERT(sysname, SERVERPROPERTY('ServerName'))

  SELECT @server_name = UPPER(@server_name)

  SELECT @server_id = server_id
  FROM msdb.dbo.systargetservers_view
  WHERE (UPPER(server_name) = ISNULL(@server_name, UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))))

  SELECT @server_id = ISNULL(@server_id, 0)

  SELECT sjv.job_id,
         sjv.name,
         sjv.enabled,
         sjv.start_step_id,
         owner = dbo.SQLAGENT_SUSER_SNAME(sjv.owner_sid),
         sjv.notify_level_eventlog,
         sjv.notify_level_email,
         sjv.notify_level_netsend,
         sjv.notify_level_page,
         sjv.notify_email_operator_id,
         sjv.notify_netsend_operator_id,
         sjv.notify_page_operator_id,
         sjv.delete_level,
         has_step = (SELECT COUNT(*)
                     FROM msdb.dbo.sysjobsteps sjst
                     WHERE (sjst.job_id = sjv.job_id)),
         sjv.version_number,
         last_run_date = ISNULL(sjs.last_run_date, 0),
         last_run_time = ISNULL(sjs.last_run_time, 0),
         sjv.originating_server,
         sjv.description,
         agent_account = CASE sjv.owner_sid
              WHEN 0xFFFFFFFF THEN 1
              ELSE                 0
         END
  FROM msdb.dbo.sysjobservers sjs,
	   msdb.dbo.sysjobs_view  sjv
           
  WHERE ((@job_id IS NULL) OR (@job_id = sjv.job_id))
    AND (sjv.job_id = sjs.job_id)
    AND (sjs.server_id = @server_id)
  ORDER BY sjv.job_id
  OPTION (FORCE ORDER)

  RETURN(@@error) -- 0 means success
END
go
	
/**************************************************************/
/* SP_DELETE_JOB                                              */
/**************************************************************/

PRINT ''
PRINT 'Updating procedure sp_delete_job...'
go

ALTER PROCEDURE sp_delete_job
  @job_id               UNIQUEIDENTIFIER = NULL, -- If provided should NOT also provide job_name
  @job_name             sysname          = NULL, -- If provided should NOT also provide job_id
  @originating_server      sysname         = NULL, -- Reserved (used by SQLAgent)
  @delete_history       BIT              = 1,    -- Reserved (used by SQLAgent)
  @delete_unused_schedule   BIT              = 1     -- For backward compatibility schedules are deleted by default if they are not 
                                        -- being used by another job. With the introduction of reusable schedules in V9 
                                        -- callers should set this to 0 so the schedule will be preserved for reuse.
AS
BEGIN
  DECLARE @current_msx_server sysname
  DECLARE @bMSX_job           BIT
  DECLARE @retval             INT
  DECLARE @local_machine_name sysname
  DECLARE @category_id        INT
  DECLARE @job_owner_sid      VARBINARY(85)
  
  SET NOCOUNT ON
  -- Remove any leading/trailing spaces from parameters
  SELECT @originating_server = UPPER(LTRIM(RTRIM(@originating_server)))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@originating_server = N'') SELECT @originating_server = NULL

  -- Change server name to always reflect real servername or servername\instancename
  IF (@originating_server IS NOT NULL AND @originating_server = '(LOCAL)')
    SELECT @originating_server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT,
                                                 @owner_sid = @job_owner_sid OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

  END

  -- We need either a job name or a server name, not both
  IF ((@job_name IS NULL)     AND (@originating_server IS NULL)) OR
     ((@job_name IS NOT NULL) AND (@originating_server IS NOT NULL))
  BEGIN
    RAISERROR(14279, -1, -1)
    RETURN(1) -- Failure
  END

  -- Get category to see if it is a misc. replication agent. @category_id will be
  -- NULL if there is no @job_id.
  select @category_id = category_id from msdb.dbo.sysjobs where job_id = @job_id

  -- If job name was given, determine if the job is from an MSX
  IF (@job_id IS NOT NULL)
  BEGIN
    SELECT @bMSX_job = CASE UPPER(originating_server)
                         WHEN UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))) THEN 0
                         ELSE 1
                       END
    FROM msdb.dbo.sysjobs_view
    WHERE (job_id = @job_id)
  END

  -- If server name was given, warn user if different from current MSX
  IF (@originating_server IS NOT NULL)
  BEGIN
    EXECUTE @retval = master.dbo.xp_getnetname @local_machine_name OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

    IF ((@originating_server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))) OR (@originating_server = UPPER(@local_machine_name)))
      SELECT @originating_server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'MSXServerName',
                                           @current_msx_server OUTPUT,
                                           N'no_output'

    SELECT @current_msx_server = UPPER(@current_msx_server)
    -- If server name was given but it's not the current MSX, print a warning
    SELECT @current_msx_server = LTRIM(RTRIM(@current_msx_server))
    IF ((@current_msx_server IS NOT NULL) AND (@current_msx_server <> N'') AND (@originating_server <> @current_msx_server))
      RAISERROR(14224, 0, 1, @current_msx_server)
  END

  -- Check authority (only SQLServerAgent can delete a non-local job)
  IF (((@originating_server IS NOT NULL) AND (@originating_server <> UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))))) OR (@bMSX_job = 1)) AND
     (PROGRAM_NAME() NOT LIKE N'SQLAgent%')
  BEGIN
    RAISERROR(14274, -1, -1)
    RETURN(1) -- Failure
  END
  
  -- Check permissions beyond what's checked by the sysjobs_view
  -- SQLAgentReader and SQLAgentOperator roles that can see all jobs
  -- cannot delete jobs they do not own
  IF (@job_id IS NOT NULL)
  BEGIN
   IF (@job_owner_sid <> SUSER_SID()                     -- does not own the job
       AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)) -- is not sysadmin
   BEGIN
     RAISERROR(14525, -1, -1);
     RETURN(1) -- Failure
    END
  END

  -- Do the delete (for a specific job)
  IF (@job_id IS NOT NULL)
  BEGIN
    -- Note: This temp table is referenced by msdb.dbo.sp_delete_job_references
    CREATE TABLE #temp_jobs_to_delete (job_id UNIQUEIDENTIFIER NOT NULL, job_is_cached INT NOT NULL)

    DECLARE @temp_schedules_to_delete TABLE (schedule_id INT NOT NULL)  

    INSERT INTO #temp_jobs_to_delete
    SELECT job_id, (SELECT COUNT(*)
                    FROM msdb.dbo.sysjobservers
                    WHERE (job_id = @job_id)
                      AND (server_id = 0))
    FROM msdb.dbo.sysjobs_view
    WHERE (job_id = @job_id)

    -- Check if we have any work to do
    IF (NOT EXISTS (SELECT *
                    FROM #temp_jobs_to_delete))
    BEGIN
      DROP TABLE #temp_jobs_to_delete
      RETURN(0) -- Success
    END

    -- Post the delete to any target servers (need to do this BEFORE
    -- deleting the job itself, but AFTER clearing all all pending
    -- download instructions).  Note that if the job is NOT a
    -- multi-server job then sp_post_msx_operation will catch this and
    -- will do nothing. Since it will do nothing that is why we need
    -- to NOT delete any pending delete requests, because that delete
    -- request might have been for the last target server and thus
    -- this job isn't a multi-server job anymore so posting the global
    -- delete would do nothing.
    DELETE FROM msdb.dbo.sysdownloadlist
    WHERE (object_id = @job_id)
      and (operation_code != 3) -- Delete
    EXECUTE msdb.dbo.sp_post_msx_operation 'DELETE', 'JOB', @job_id

    -- Must do this before deleting the job itself since sp_sqlagent_notify does a lookup on sysjobs_view
    -- Note: Don't notify agent in this call. It is done after the transaction is committed
    --       just in case this job is in the process of deleting itself
    EXECUTE msdb.dbo.sp_delete_job_references @notify_sqlagent = 0

    -- Delete all traces of the job
    BEGIN TRANSACTION

   --Get the schedules to delete before deleting records from sysjobschedules
    IF(@delete_unused_schedule = 1)
    BEGIN
        --Get the list of schedules to delete
        INSERT INTO @temp_schedules_to_delete
        SELECT DISTINCT schedule_id 
        FROM   msdb.dbo.sysschedules
        WHERE (schedule_id IN 
                (SELECT schedule_id
                FROM msdb.dbo.sysjobschedules
                WHERE (job_id = @job_id)))
    END


    DELETE FROM msdb.dbo.sysjobschedules
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)
    
    DELETE FROM msdb.dbo.sysjobservers
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

    DELETE FROM msdb.dbo.sysjobsteps
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

    DELETE FROM msdb.dbo.sysjobs
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)
    
    --Delete the schedule(s) if requested to and it isn't being used by other jobs
    IF(@delete_unused_schedule = 1)
    BEGIN
      --Now OK to delete the schedule
      DELETE FROM msdb.dbo.sysschedules
      WHERE schedule_id IN 
        (SELECT schedule_id
         FROM @temp_schedules_to_delete as sdel
         WHERE NOT EXISTS(SELECT * 
                          FROM msdb.dbo.sysjobschedules AS js
                          WHERE (js.schedule_id = sdel.schedule_id)))
    END


    -- Delete the job history if requested    
    IF (@delete_history = 1)
    BEGIN
      DELETE FROM msdb.dbo.sysjobhistory
      WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)
    END
    -- All done
    COMMIT TRANSACTION

    -- Now notify agent to delete the job.
    IF(EXISTS(SELECT * FROM #temp_jobs_to_delete WHERE job_is_cached > 0))
    BEGIN
      DECLARE @nt_user_name   NVARCHAR(100)
      SELECT @nt_user_name = ISNULL(NT_CLIENT(), ISNULL(SUSER_SNAME(), FORMATMESSAGE(14205)))
      --Call the xp directly. sp_sqlagent_notify checks sysjobs_view and the record has already been deleted
      EXEC master.dbo.xp_sqlagent_notify N'J', @job_id, 0, 0, N'D', @nt_user_name, 1, @@trancount, NULL, NULL
    END

  END
  ELSE
  -- Do the delete (for all jobs originating from the specific server)
  IF (@originating_server IS NOT NULL)
  BEGIN
    EXECUTE msdb.dbo.sp_delete_all_msx_jobs @msx_server = @originating_server

    -- NOTE: In this case there is no need to propagate the delete via sp_post_msx_operation
    --       since this type of delete is only ever performed on a TSX.
  END

  IF (OBJECT_ID(N'tempdb.dbo.#temp_jobs_to_delete', 'U') IS NOT NULL)	
    DROP TABLE #temp_jobs_to_delete

  RETURN(0) -- 0 means success
END
go

/**************************************************************/
/* SP_SQLAGENT_IS_SRVROLEMEMBER                               */
/**************************************************************/

PRINT ''
PRINT 'Updating procedure sp_sqlagent_is_srvrolemember...'
go

ALTER PROCEDURE sp_sqlagent_is_srvrolemember
   @role_name sysname, @login_name sysname
AS
BEGIN
  DECLARE @is_member        INT
  SET NOCOUNT ON
  
  IF @role_name IS NULL OR @login_name IS NULL
    RETURN(0)
  
  SELECT @is_member = 0
  --IS_SRVROLEMEMBER works only if the login to be tested is provisioned with sqlserver
  if( @login_name = SUSER_SNAME())
    SELECT @is_member = IS_SRVROLEMEMBER(@role_name)
  else
    SELECT @is_member = IS_SRVROLEMEMBER(@role_name, @login_name)
    
  
  --try to impersonate. A try catch is used because we can have @name as NT groups also
  IF @is_member IS NULL
  BEGIN
    BEGIN TRY
      if( is_srvrolemember('sysadmin') = 1)
      begin
      EXECUTE AS LOGIN = @login_name -- impersonate 
        SELECT @is_member = IS_SRVROLEMEMBER(@role_name)  -- check role membership 
      REVERT -- revert back
      end
    END TRY
    BEGIN CATCH
      SELECT @is_member = 0
    END CATCH
  END
 
  RETURN ISNULL(@is_member,0)
END
GO

/**************************************************************/
/* sp_verify_jobstep                                          */
/**************************************************************/

PRINT ''
PRINT 'Updating procedure sp_verify_jobstep...'
go

ALTER PROCEDURE sp_verify_jobstep
  @job_id             UNIQUEIDENTIFIER,
  @step_id            INT,
  @step_name          sysname,
  @subsystem          NVARCHAR(40),
  @command            NVARCHAR(max),
  @server             sysname,
  @on_success_action  TINYINT,
  @on_success_step_id INT,
  @on_fail_action     TINYINT,
  @on_fail_step_id    INT,
  @os_run_priority    INT,
  @database_name      sysname OUTPUT,
  @database_user_name sysname OUTPUT,
  @flags              INT,
  @output_file_name   NVARCHAR(200),
  @proxy_id         INT 
AS
BEGIN
  DECLARE @max_step_id             INT
  DECLARE @retval                  INT
  DECLARE @valid_values            VARCHAR(50)
  DECLARE @database_name_temp      sysname
  DECLARE @database_user_name_temp sysname
  DECLARE @temp_command            NVARCHAR(max)
  DECLARE @iPos                    INT
  DECLARE @create_count            INT
  DECLARE @destroy_count           INT
  DECLARE @is_olap_subsystem       BIT
  DECLARE @owner_sid               VARBINARY(85)
  DECLARE @owner_name              sysname
  -- Remove any leading/trailing spaces from parameters
  SELECT @subsystem        = LTRIM(RTRIM(@subsystem))
  SELECT @server           = LTRIM(RTRIM(@server))
  SELECT @output_file_name = LTRIM(RTRIM(@output_file_name))

  -- Get current maximum step id
  SELECT @max_step_id = ISNULL(MAX(step_id), 0)
  FROM msdb.dbo.sysjobsteps
  WHERE (job_id = @job_id)

  -- Check step id
  IF (@step_id < 1) OR (@step_id > @max_step_id + 1)
  BEGIN
    SELECT @valid_values = '1..' + CONVERT(VARCHAR, @max_step_id + 1)
    RAISERROR(14266, -1, -1, '@step_id', @valid_values)
    RETURN(1) -- Failure
  END

  -- Check subsystem
  EXECUTE @retval = sp_verify_subsystem @subsystem
  IF (@retval <> 0)
    RETURN(1) -- Failure
  

  --check if proxy is allowed for this subsystem for current user
  IF (@proxy_id IS NOT NULL)
  BEGIN
    --get the job owner
    SELECT @owner_sid = owner_sid FROM sysjobs
    WHERE  job_id = @job_id
    IF @owner_sid = 0xFFFFFFFF
    BEGIN
      --ask to verify for the special account
      EXECUTE @retval = sp_verify_proxy_permissions 
        @subsystem_name = @subsystem, 
        @proxy_id = @proxy_id, 
        @name = NULL, 
        @raise_error = 1, 
        @allow_disable_proxy = 1, 
        @verify_special_account = 1
      IF (@retval <> 0)
        RETURN(1) -- Failure
    END
    ELSE
    BEGIN
      SELECT @owner_name = SUSER_SNAME(@owner_sid)
      EXECUTE @retval = sp_verify_proxy_permissions 
      @subsystem_name = @subsystem, 
      @proxy_id = @proxy_id, 
      @name = @owner_name, 
      @raise_error = 1, 
      @allow_disable_proxy = 1
      IF (@retval <> 0)
        RETURN(1) -- Failure
    END
  END

  --Only sysadmin can specify @output_file_name 
  IF (@output_file_name IS NOT NULL) AND  (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(14582, -1, -1)
    RETURN(1) -- Failure    
  END

  --Determmine if this is a olap subsystem jobstep
  IF ( UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) in (N'ANALYSISQUERY', N'ANALYSISCOMMAND') )
    SELECT @is_olap_subsystem = 1
  ELSE
    SELECT @is_olap_subsystem = 0

  -- Check step name
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobsteps
              WHERE (job_id = @job_id)
                AND (step_name = @step_name)))
  BEGIN
    RAISERROR(14261, -1, -1, '@step_name', @step_name)
    RETURN(1) -- Failure
  END

  -- Check on-success action/step
  IF (@on_success_action <> 1) AND -- Quit Qith Success
     (@on_success_action <> 2) AND -- Quit Qith Failure
     (@on_success_action <> 3) AND -- Goto Next Step
     (@on_success_action <> 4)     -- Goto Step
  BEGIN
    RAISERROR(14266, -1, -1, '@on_success_action', '1, 2, 3, 4')
    RETURN(1) -- Failure
  END
  IF (@on_success_action = 4) AND
     ((@on_success_step_id < 1) OR (@on_success_step_id = @step_id))
  BEGIN
    -- NOTE: We allow forward references to non-existant steps to prevent the user from
    --       having to make a second update pass to fix up the flow
    RAISERROR(14235, -1, -1, '@on_success_step', @step_id)
    RETURN(1) -- Failure
  END

  -- Check on-fail action/step
  IF (@on_fail_action <> 1) AND -- Quit Qith Success
     (@on_fail_action <> 2) AND -- Quit Qith Failure
     (@on_fail_action <> 3) AND -- Goto Next Step
     (@on_fail_action <> 4)     -- Goto Step
  BEGIN
    RAISERROR(14266, -1, -1, '@on_failure_action', '1, 2, 3, 4')
    RETURN(1) -- Failure
  END
  IF (@on_fail_action = 4) AND
     ((@on_fail_step_id < 1) OR (@on_fail_step_id = @step_id))
  BEGIN
    -- NOTE: We allow forward references to non-existant steps to prevent the user from
    --       having to make a second update pass to fix up the flow
    RAISERROR(14235, -1, -1, '@on_failure_step', @step_id)
    RETURN(1) -- Failure
  END

  -- Warn the user about forward references
  IF ((@on_success_action = 4) AND (@on_success_step_id > @max_step_id))
    RAISERROR(14236, 0, 1, '@on_success_step_id')
  IF ((@on_fail_action = 4) AND (@on_fail_step_id > @max_step_id))
    RAISERROR(14236, 0, 1, '@on_fail_step_id')

  --Special case the olap subsystem. It can have any server name. 
  --Default it to the local server if @server is null 
  IF(@is_olap_subsystem = 1)
  BEGIN
    IF(@server IS NULL)
    BEGIN
    --TODO: needs error better message ? >> 'Specify the OLAP server name in the %s parameter'
      --Must specify the olap server name
      RAISERROR(14262, -1, -1, '@server', @server)
      RETURN(1) -- Failure    
    END
  END
  ELSE
  BEGIN
    IF (@server IS NOT NULL)
    BEGIN
        -- @server may contain port number remove it before looking up.
        declare @srv sysname
        declare @index int
        set @index = PATINDEX('%,%', @server)

        IF @index <> 0
          set @srv = substring(@server, 0, @index)
        ELSE
          set @srv = @server

      -- Check server (this is the replication server, NOT the job-target server)
      IF(NOT EXISTS (SELECT *
                                              FROM master.dbo.sysservers
                                              WHERE (UPPER(srvname) = UPPER(@srv))))
      BEGIN
        RAISERROR(14234, -1, -1, '@srv', 'sp_helpserver')
        RETURN(1) -- Failure
      END
    END
  END
  -- Check run priority: must be a valid value to pass to SetThreadPriority:
  -- [-15 = IDLE, -1 = BELOW_NORMAL, 0 = NORMAL, 1 = ABOVE_NORMAL, 15 = TIME_CRITICAL]
  IF (@os_run_priority NOT IN (-15, -1, 0, 1, 15))
  BEGIN
    RAISERROR(14266, -1, -1, '@os_run_priority', '-15, -1, 0, 1, 15')
    RETURN(1) -- Failure
  END

  -- Check flags
  IF ((@flags < 0) OR (@flags > 50))
  BEGIN
    RAISERROR(14266, -1, -1, '@flags', '0..50')
    RETURN(1) -- Failure
  END

  -- Check output file
  IF (@output_file_name IS NOT NULL) AND (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) NOT IN ('TSQL', 'CMDEXEC', 'ANALYSISQUERY', 'ANALYSISCOMMAND', 'SSIS' ))
  BEGIN
    RAISERROR(14545, -1, -1, '@output_file_name', @subsystem)
    RETURN(1) -- Failure
  END

  -- Check writing to table flags
  IF (@flags IS NOT NULL) AND (((@flags & 8) <> 0) OR ((@flags & 16) <> 0)) AND (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) NOT IN ('TSQL', 'CMDEXEC', 'ANALYSISQUERY', 'ANALYSISCOMMAND', 'SSIS' ))
  BEGIN
    RAISERROR(14545, -1, -1, '@flags', @subsystem)
    RETURN(1) -- Failure
  END

  -- For CmdExec steps database-name and database-user-name should both be null
  IF (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = N'CMDEXEC')
    SELECT @database_name = NULL,
           @database_user_name = NULL

  -- For non-TSQL steps, database-user-name should be null
  IF (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) <> 'TSQL')
    SELECT @database_user_name = NULL

  -- For a TSQL step, get (and check) the username of the caller in the target database.
  IF (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = 'TSQL')
  BEGIN
    SET NOCOUNT ON

    -- But first check if remote server name has been supplied
    IF (@server IS NOT NULL)
      SELECT @server = NULL

    -- Default database to 'master' if not supplied
    IF (LTRIM(RTRIM(@database_name)) IS NULL)
      SELECT @database_name = N'master'

    -- Check the database (although this is no guarantee that @database_user_name can access it)
    IF (DB_ID(@database_name) IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@database_name', @database_name)
      RETURN(1) -- Failure
    END

    SELECT @database_user_name = LTRIM(RTRIM(@database_user_name))

    -- Only if a SysAdmin is creating the job can the database user name be non-NULL [since only
    -- SysAdmin's can call SETUSER].
    -- NOTE: In this case we don't try to validate the user name (it's too costly to do so)
    --       so if it's bad we'll get a runtime error when the job executes.
    IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
    BEGIN
      -- If this is a multi-server job then @database_user_name must be null
      IF (@database_user_name IS NOT NULL)
      BEGIN
        IF (EXISTS (SELECT *
                    FROM msdb.dbo.sysjobs       sj,
                         msdb.dbo.sysjobservers sjs
                    WHERE (sj.job_id = sjs.job_id)
                      AND (sj.job_id = @job_id)
                      AND (sjs.server_id <> 0)))
        BEGIN
          RAISERROR(14542, -1, -1, N'database_user_name')
          RETURN(1) -- Failure
        END
      END

      -- For a SQL-user, check if it exists
      IF (@database_user_name NOT LIKE N'%\%')
      BEGIN
        SELECT @database_user_name_temp = REPLACE(@database_user_name, N'''', N'''''')
        SELECT @database_name_temp = REPLACE(@database_name, N'''', N'''''')

        EXECUTE(N'DECLARE @ret INT
                  SELECT @ret = COUNT(*)
                  FROM ' + @database_name_temp + N'.dbo.sysusers
                  WHERE (name = N''' + @database_user_name_temp + N''')
                  HAVING (COUNT(*) > 0)')
        IF (@@ROWCOUNT = 0)
        BEGIN
          RAISERROR(14262, -1, -1, '@database_user_name', @database_user_name)
          RETURN(1) -- Failure
        END
      END
    END
    ELSE
      SELECT @database_user_name = NULL

  END  -- End of TSQL property verification

  RETURN(0) -- Success
END
go

 
/**************************************************************/
/* sysmail_delete_mailitems_sp                                */
/**************************************************************/
-----
PRINT 'Updating sysmail_delete_mailitems_sp'
-----
GO
ALTER PROCEDURE sysmail_delete_mailitems_sp
   @sent_before DATETIME   = NULL, -- sent before
   @sent_status varchar(8)   = NULL -- sent status
AS
BEGIN

   SET @sent_status       = LTRIM(RTRIM(@sent_status))
   IF @sent_status           = '' SET @sent_status = NULL

   IF ( (@sent_status IS NOT NULL) AND
         (LOWER(@sent_status collate SQL_Latin1_General_CP1_CS_AS) NOT IN ( 'unsent', 'sent', 'failed', 'retrying') ) )
   BEGIN
      RAISERROR(14266, -1, -1, '@sent_status', 'unsent, sent, failed, retrying')
      RETURN(1) -- Failure
   END

   IF ( @sent_before IS NULL AND @sent_status IS NULL )
   BEGIN
      RAISERROR(14608, -1, -1, '@sent_before', '@sent_status')  
      RETURN(1) -- Failure
   END

   DELETE FROM msdb.dbo.sysmail_allitems 
   WHERE 
        ((@sent_before IS NULL) OR ( send_request_date < @sent_before))
   AND ((@sent_status IS NULL) OR (sent_status = @sent_status))

   DECLARE @localmessage nvarchar(255)
    SET @localmessage = FORMATMESSAGE(14665, SUSER_SNAME(), @@ROWCOUNT)
    exec msdb.dbo.sysmail_logmailevent_sp @event_type=1, @description=@localmessage

END
GO

/**************************************************************/
/* sp_sysmail_activate                                */
/**************************************************************/
-----
PRINT 'Updating procedure sp_sysmail_activate'
-----
GO
-- sp_sysmail_activate : Starts the DatabaseMail process if it isn't already running
--
ALTER PROCEDURE sp_sysmail_activate
AS
BEGIN
    DECLARE @mailDbName sysname
    DECLARE @mailDbId INT
    DECLARE @mailEngineLifeMin INT
    DECLARE @loggingLevel nvarchar(256)
    DECLARE @loggingLevelInt int   
    DECLARE @parameter_value nvarchar(256)
    DECLARE @localmessage nvarchar(max)
    DECLARE @rc INT

    SET NOCOUNT ON
    EXEC sp_executesql @statement = N'RECEIVE TOP(0) * FROM msdb.dbo.ExternalMailQueue'

    EXEC @rc = msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'DatabaseMailExeMinimumLifeTime', 
                                                        @parameter_value = @parameter_value OUTPUT
    IF(@rc <> 0)
        RETURN (1)

    --ConvertToInt will return the default if @parameter_value is null or config value can't be converted
    --Setting max exe lifetime is 1 week (604800 secs). Can't see a reason for it to ever run longer that this
    SET @mailEngineLifeMin = dbo.ConvertToInt(@parameter_value, 604800, 600) 
    
    --Try and get the optional logging level for the DatabaseMail process
    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'LoggingLevel', 
                                                  @parameter_value = @loggingLevel OUTPUT

    --Convert logging level into string value for passing into XP
    SET @loggingLevelInt = dbo.ConvertToInt(@loggingLevel, 3, 2) 
    IF @loggingLevelInt = 1
       SET @loggingLevel = 'Normal'
    ELSE IF @loggingLevelInt = 3
       SET @loggingLevel = 'Verbose'
    ELSE -- default
       SET @loggingLevel = 'Extended'

    SET @mailDbName = DB_NAME() 
    SET @mailDbId   = DB_ID()

    EXEC @rc = master..xp_sysmail_activate @mailDbId, @mailDbName, @mailEngineLifeMin, @loggingLevel
    IF(@rc <> 0)
    BEGIN
        SET @localmessage = FORMATMESSAGE(14637)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage
    END
    ELSE
    BEGIN
        SET @localmessage = FORMATMESSAGE(14638)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=0, @description=@localmessage
    END

    RETURN @rc
END
GO

/**************************************************************/
/* Update the RunMailQuery stored procedure          */
/**************************************************************/
/****** Object:  StoredProcedure [dbo].[sp_RunMailQuery]    Script Date: 06/06/2006 12:26:03 ******/
use msdb
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
ALTER PROCEDURE [dbo].[sp_RunMailQuery]
   @query                    NVARCHAR(max),
   @attach_results             BIT,
    @query_attachment_filename  NVARCHAR(260) = NULL,
   @no_output                  BIT,
   @query_result_header        BIT,
   @separator                  VARCHAR(1),
   @echo_error                 BIT,
   @dbuse                      sysname,
   @width                   INT,
    @temp_table_uid             uniqueidentifier,
   @query_no_truncate          BIT,
   @query_result_no_padding             BIT
AS
BEGIN
    SET NOCOUNT ON
    SET QUOTED_IDENTIFIER ON

    DECLARE @rc             INT,
            @prohibitedExts NVARCHAR(1000),
            @fileSizeStr    NVARCHAR(256),
            @fileSize       INT,
            @attach_res_int INT,
            @no_output_int  INT,
            @no_header_int  INT,
            @echo_error_int INT,
         @query_no_truncate_int INT,
         @query_result_no_padding_int   INT,
            @mailDbName     sysname,
            @uid            uniqueidentifier,
            @uidStr         VARCHAR(36)

    --
    --Get config settings and verify parameters
    --
    SET @query_attachment_filename = LTRIM(RTRIM(@query_attachment_filename))

    --Get the maximum file size allowed for attachments from sysmailconfig.
    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'MaxFileSize', 
                                                @parameter_value = @fileSizeStr OUTPUT
    --ConvertToInt will return the default if @fileSizeStr is null
    SET @fileSize = dbo.ConvertToInt(@fileSizeStr, 0x7fffffff, 100000)

    IF (@attach_results = 1)
    BEGIN
        --Need this if attaching the query
        EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'ProhibitedExtensions', 
                                                    @parameter_value = @prohibitedExts OUTPUT

        -- If attaching query results to a file and a filename isn't given create one
        IF ((@query_attachment_filename IS NOT NULL) AND (LEN(@query_attachment_filename) > 0))
        BEGIN 
          EXEC @rc = sp_isprohibited @query_attachment_filename, @prohibitedExts
          IF (@rc <> 0)
          BEGIN
              RAISERROR(14630, 16, 1, @query_attachment_filename, @prohibitedExts)
              RETURN 2
          END
        END
        ELSE
        BEGIN
            --If queryfilename is not specified, generate a random name (doesn't have to be unique)
           SET @query_attachment_filename = 'QueryResults' + CONVERT(varchar, ROUND(RAND() * 1000000, 0)) + '.txt'
        END
    END

    --Init variables used in the query execution
    SET @mailDbName = db_name()
    SET @uidStr = convert(varchar(36), @temp_table_uid)

    SET @attach_res_int        = CONVERT(int, @attach_results)
    SET @no_output_int         = CONVERT(int, @no_output)
    IF(@query_result_header = 0) SET @no_header_int  = 1 ELSE SET @no_header_int  = 0
    SET @echo_error_int        = CONVERT(int, @echo_error)
    SET @query_no_truncate_int = CONVERT(int, @query_no_truncate)
    SET @query_result_no_padding_int = CONVERT(int, @query_result_no_padding )

    EXEC @rc = master..xp_sysmail_format_query  
                @query        = @query,
                @message      = @mailDbName,
                    @subject     = @uidStr,
                    @dbuse       = @dbuse, 
                    @attachments = @query_attachment_filename,
                    @attach_results = @attach_res_int,
                    -- format params
                    @separator      = @separator,
                    @no_header      = @no_header_int,
                    @no_output      = @no_output_int,
                    @echo_error     = @echo_error_int,
                @max_attachment_size = @fileSize,
                    @width       = @width, 
                    @query_no_truncate = @query_no_truncate_int,
                    @query_result_no_padding    = @query_result_no_padding_int
   RETURN @rc
END
GO

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_validate_user')))
  DROP PROCEDURE sp_validate_user
go
use msdb
GO
/****** Object:  StoredProcedure [dbo].sp_validate_user ********/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
CREATE PROCEDURE [dbo].[sp_validate_user]
	@send_request_user sysname,
	@user_sid varbinary(85) OUTPUT
  WITH EXECUTE AS 'dbo'
AS
BEGIN
    SET NOCOUNT ON
-- And make sure ARITHABORT is on. This is the default for yukon DB's
    SET ARITHABORT ON

	declare @sid varbinary(85)
	create table #temp
	([account name] sysname, 
	[type] char(8),
	[privilege] char(9),
	[mapped login name] sysname,
	[permission path] sysname)
	
	insert #temp exec master.dbo.xp_logininfo @send_request_user
	select @sid = suser_sid([permission path]) from #temp

	SET @user_sid = NULL
	
	IF EXISTS(SELECT * 
        FROM msdb.dbo.sysmail_principalprofile as pp
        WHERE (pp.is_default = 1) AND
            (pp.principal_sid = @sid))
	BEGIN
		SET @user_sid = @sid
	END
END
GO


use msdb
GO
/****** Object:  StoredProcedure [dbo].[sp_send_dbmail]    Script Date: 06/06/2006 12:24:17 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
-- sp_send_dbmail : Sends a mail from Yukon outbox.
--
ALTER PROCEDURE [dbo].[sp_send_dbmail]
   @profile_name               sysname    = NULL,        
   @recipients                 VARCHAR(MAX)  = NULL, 
   @copy_recipients            VARCHAR(MAX)  = NULL,
   @blind_copy_recipients      VARCHAR(MAX)  = NULL,
   @subject                    NVARCHAR(255) = NULL,
   @body                       NVARCHAR(MAX) = NULL, 
   @body_format                VARCHAR(20)      = NULL, 
   @importance                 VARCHAR(6)    = 'NORMAL',
   @sensitivity                VARCHAR(12)      = 'NORMAL',
   @file_attachments           NVARCHAR(MAX) = NULL,  
   @query                      NVARCHAR(MAX) = NULL,
   @execute_query_database     sysname    = NULL,  
   @attach_query_result_as_file BIT    = 0,
        @query_attachment_filename  NVARCHAR(260)  = NULL,  
        @query_result_header        BIT         = 1,
   @query_result_width         INT        = 256,            
   @query_result_separator     CHAR(1)    = ' ',
   @exclude_query_output       BIT        = 0,
   @append_query_error         BIT        = 0,
   @query_no_truncate          BIT        = 0,
   @query_result_no_padding            BIT        = 0,
   @mailitem_id               INT         = NULL OUTPUT
  WITH EXECUTE AS 'dbo'
AS
BEGIN
    SET NOCOUNT ON

    -- And make sure ARITHABORT is on. This is the default for yukon DB's
    SET ARITHABORT ON

    --Declare variables used by the procedure internally
    DECLARE @profile_id         INT,
            @temp_table_uid     uniqueidentifier,
            @sendmailxml        VARCHAR(max),
            @CR_str             NVARCHAR(2),
            @localmessage       NVARCHAR(255),
            @QueryResultsExist  INT,
            @AttachmentsExist   INT,
            @RetErrorMsg        NVARCHAR(4000), --Impose a limit on the error message length to avoid memory abuse 
            @rc                 INT,
            @procName           sysname,
       @trancountSave      INT,
       @tranStartedBool    INT,
            @is_sysadmin        BIT,
            @send_request_user  sysname,
            @database_user_id   INT,
			@sid				varbinary(85)
		
		SET @sid = NULL

    -- Initialize 
    SELECT  @rc                 = 0,
            @QueryResultsExist  = 0,
            @AttachmentsExist   = 0,
            @temp_table_uid     = NEWID(),
            @procName           = OBJECT_NAME(@@PROCID),
            @tranStartedBool  = 0,
       @trancountSave      = @@TRANCOUNT

    EXECUTE AS CALLER
       SELECT @is_sysadmin       = IS_SRVROLEMEMBER('sysadmin'),
              @send_request_user = SUSER_SNAME(),
              @database_user_id  = USER_ID()
    REVERT

    --Check if SSB is enabled in this database
    IF (ISNULL(DATABASEPROPERTYEX(DB_NAME(), N'IsBrokerEnabled'), 0) <> 1)
    BEGIN
       RAISERROR(14650, 16, 1)
       RETURN 1
    END

    --Report error if the mail queue has been stopped. 
    --sysmail_stop_sp/sysmail_start_sp changes the receive status of the SSB queue
    IF NOT EXISTS (SELECT * FROM sys.service_queues WHERE name = N'ExternalMailQueue' AND is_receive_enabled = 1)
    BEGIN
       RAISERROR(14641, 16, 1)
       RETURN 1
    END

	
    -- Get the relevant profile_id 
    --
    IF (@profile_name IS NULL)
    BEGIN
        -- Use the global or users default if profile name is not supplied
        SELECT TOP (1) @profile_id = pp.profile_id
        FROM msdb.dbo.sysmail_principalprofile as pp
        WHERE (pp.is_default = 1) AND
            (dbo.get_principal_id(pp.principal_sid) = @database_user_id OR pp.principal_sid = 0x00)
        ORDER BY dbo.get_principal_id(pp.principal_sid) DESC

        --Was a profile found
        IF(@profile_id IS NULL)
        BEGIN
		   EXEC msdb.dbo.sp_validate_user @send_request_user, @sid OUTPUT
		   SELECT TOP (1) @profile_id = pp.profile_id
		   FROM msdb.dbo.sysmail_principalprofile as pp
		   WHERE (pp.is_default = 1) AND
		   (pp.principal_sid = @sid)
		   ORDER BY dbo.get_principal_id(pp.principal_sid) DESC
		   IF(@profile_id IS NULL)
		   BEGIN
				RAISERROR(14636, 16, 1)
				RETURN 1
		   END
        END
    END
    ELSE
    BEGIN
        --Get primary account if profile name is supplied
        EXEC @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id = NULL, 
                         @profile_name = @profile_name, 
                         @allow_both_nulls = 0, 
                         @allow_id_name_mismatch = 0,
                         @profileid = @profile_id OUTPUT
        IF (@rc <> 0)
            RETURN @rc

        --Make sure this user has access to the specified profile.
        --sysadmins can send on any profiles
        IF ( @is_sysadmin <> 1)
        BEGIN
            --Not a sysadmin so check users access to profile
            iF NOT EXISTS(SELECT * 
                        FROM msdb.dbo.sysmail_principalprofile 
                        WHERE ((profile_id = @profile_id) AND
                                (dbo.get_principal_id(principal_sid) = @database_user_id OR principal_sid = 0x00)))
            BEGIN
			   EXEC msdb.dbo.sp_validate_user @send_request_user, @sid OUTPUT
			   IF(@sid IS NULL)
			   BEGIN
					RAISERROR(14607, -1, -1, 'profile')
					RETURN 1
			   END
            END
        END
    END

    --Attach results must be specified
    IF @attach_query_result_as_file IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'attach_query_result_as_file')
       RETURN 2
    END

    --No output must be specified
    IF @exclude_query_output IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'exclude_query_output')
       RETURN 3
    END

    --No header must be specified
    IF @query_result_header IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'query_result_header')
       RETURN 4
    END

    -- Check if query_result_separator is specifed
    IF @query_result_separator IS NULL OR DATALENGTH(@query_result_separator) = 0
    BEGIN
       RAISERROR(14618, 16, 1, 'query_result_separator')
       RETURN 5
    END

    --Echo error must be specified
    IF @append_query_error IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'append_query_error')
       RETURN 6
    END

    --@body_format can be TEXT (default) or HTML
    IF (@body_format IS NULL)
    BEGIN
       SET @body_format = 'TEXT'
    END
    ELSE
    BEGIN
       SET @body_format = UPPER(@body_format)

       IF @body_format NOT IN ('TEXT', 'HTML') 
       BEGIN
          RAISERROR(14626, 16, 1, @body_format)
          RETURN 13
       END
    END

    --Importance must be specified
    IF @importance IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'importance')
       RETURN 15
    END

    SET @importance = UPPER(@importance)

    --Importance must be one of the predefined values
    IF @importance NOT IN ('LOW', 'NORMAL', 'HIGH')
    BEGIN
       RAISERROR(14622, 16, 1, @importance)
       RETURN 16
    END

    --Sensitivity must be specified
    IF @sensitivity IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'sensitivity')
       RETURN 17
    END

    SET @sensitivity = UPPER(@sensitivity)

    --Sensitivity must be one of predefined values
    IF @sensitivity NOT IN ('NORMAL', 'PERSONAL', 'PRIVATE', 'CONFIDENTIAL')
    BEGIN
       RAISERROR(14623, 16, 1, @sensitivity)
       RETURN 18
    END

    --Message body cannot be null. Atleast one of message, subject, query,
    --attachments must be specified.
    IF( (@body IS NULL AND @query IS NULL AND @file_attachments IS NULL AND @subject IS NULL)
       OR
    ( (LEN(@body) IS NULL OR LEN(@body) <= 0)  
       AND (LEN(@query) IS NULL  OR  LEN(@query) <= 0)
       AND (LEN(@file_attachments) IS NULL OR LEN(@file_attachments) <= 0)
       AND (LEN(@subject) IS NULL OR LEN(@subject) <= 0)
    )
    )
    BEGIN
       RAISERROR(14624, 16, 1, '@body, @query, @file_attachments, @subject')
       RETURN 19
    END   
    ELSE
       IF @subject IS NULL OR LEN(@subject) <= 0
          SET @subject='SQL Server Message'

    --Recipients cannot be empty. Atleast one of the To, Cc, Bcc must be specified
    IF ( (@recipients IS NULL AND @copy_recipients IS NULL AND 
       @blind_copy_recipients IS NULL
        )     
       OR
        ( (LEN(@recipients) IS NULL OR LEN(@recipients) <= 0)
       AND (LEN(@copy_recipients) IS NULL OR LEN(@copy_recipients) <= 0)
       AND (LEN(@blind_copy_recipients) IS NULL OR LEN(@blind_copy_recipients) <= 0)
        )
    )
    BEGIN
       RAISERROR(14624, 16, 1, '@recipients, @copy_recipients, @blind_copy_recipients')
       RETURN 20
    END

    --If query is not specified, attach results and no header cannot be true.
    IF ( (@query IS NULL OR LEN(@query) <= 0) AND @attach_query_result_as_file = 1)
    BEGIN
       RAISERROR(14625, 16, 1)
       RETURN 21
    END

    --
    -- Execute Query if query is specified
    IF ((@query IS NOT NULL) AND (LEN(@query) > 0))
    BEGIN
        EXECUTE AS CALLER
        EXEC @rc = sp_RunMailQuery 
                    @query                     = @query,
               @attach_results            = @attach_query_result_as_file,
                    @query_attachment_filename = @query_attachment_filename,
               @no_output                 = @exclude_query_output,
               @query_result_header       = @query_result_header,
               @separator                 = @query_result_separator,
               @echo_error                = @append_query_error,
               @dbuse                     = @execute_query_database,
               @width                     = @query_result_width,
                @temp_table_uid            = @temp_table_uid,
            @query_no_truncate         = @query_no_truncate,
            @query_result_no_padding           = @query_result_no_padding
      -- This error indicates that query results size was over the configured MaxFileSize.
      -- Note, an error has already beed raised in this case
      IF(@rc = 101)
         GOTO ErrorHandler;
         REVERT
 
         -- Always check the transfer tables for data. They may also contain error messages
         -- Only one of the tables receives data in the call to sp_RunMailQuery
         IF(@attach_query_result_as_file = 1)
         BEGIN
             IF EXISTS(SELECT * FROM sysmail_attachments_transfer WHERE uid = @temp_table_uid)
            SET @AttachmentsExist = 1
         END
         ELSE
         BEGIN
             IF EXISTS(SELECT * FROM sysmail_query_transfer WHERE uid = @temp_table_uid AND uid IS NOT NULL)
            SET @QueryResultsExist = 1
         END

         -- Exit if there was an error and caller doesn't want the error appended to the mail
         IF (@rc <> 0 AND @append_query_error = 0)
         BEGIN
            --Error msg with be in either the attachment table or the query table 
            --depending on the setting of @attach_query_result_as_file
            IF(@attach_query_result_as_file = 1)
            BEGIN
               --Copy query results from the attachments table to mail body
               SELECT @RetErrorMsg = CONVERT(NVARCHAR(4000), attachment)
               FROM sysmail_attachments_transfer 
               WHERE uid = @temp_table_uid
            END
            ELSE
            BEGIN
               --Copy query results from the query table to mail body
               SELECT @RetErrorMsg = text_data 
               FROM sysmail_query_transfer 
               WHERE uid = @temp_table_uid
            END

            GOTO ErrorHandler;
         END
         SET @AttachmentsExist = @attach_query_result_as_file
    END
    ELSE
    BEGIN
        --If query is not specified, attach results cannot be true.
        IF (@attach_query_result_as_file = 1)
        BEGIN
           RAISERROR(14625, 16, 1)
           RETURN 21
        END
    END

    --Get the prohibited extensions for attachments from sysmailconfig.
    IF ((@file_attachments IS NOT NULL) AND (LEN(@file_attachments) > 0)) 
    BEGIN
        EXECUTE AS CALLER
        EXEC @rc = sp_GetAttachmentData 
                        @attachments = @file_attachments, 
                        @temp_table_uid = @temp_table_uid,
                        @exclude_query_output = @exclude_query_output
        REVERT
        IF (@rc <> 0)
            GOTO ErrorHandler;
        
        IF EXISTS(SELECT * FROM sysmail_attachments_transfer WHERE uid = @temp_table_uid)
            SET @AttachmentsExist = 1
    END

    -- Start a transaction if not already in one. 
    -- Note: For rest of proc use GOTO ErrorHandler for falures  
    if (@trancountSave = 0) 
       BEGIN TRAN @procName

    SET @tranStartedBool = 1

    -- Store complete mail message for history/status purposes  
    INSERT sysmail_mailitems
    (
       profile_id,   
       recipients,
       copy_recipients,
       blind_copy_recipients,
       subject,
       body, 
       body_format, 
       importance,
       sensitivity,
       file_attachments,  
       attachment_encoding,
       query,
       execute_query_database,
       attach_query_result_as_file,
       query_result_header,
       query_result_width,          
       query_result_separator,
       exclude_query_output,
       append_query_error,
            send_request_user
    )
    VALUES
    (
       @profile_id,        
       @recipients, 
       @copy_recipients,
       @blind_copy_recipients,
       @subject,
       @body, 
       @body_format, 
       @importance,
       @sensitivity,
       @file_attachments,  
       'MIME',
       @query,
       @execute_query_database,  
       @attach_query_result_as_file,
       @query_result_header,
       @query_result_width,            
       @query_result_separator,
       @exclude_query_output,
       @append_query_error,
            @send_request_user
    )

    SELECT @rc          = @@ERROR,
           @mailitem_id = @@IDENTITY

    IF(@rc <> 0)
        GOTO ErrorHandler;

    --Copy query into the message body
    IF(@QueryResultsExist = 1)
    BEGIN
      -- if the body is null initialize it
        UPDATE sysmail_mailitems
        SET body = N''
        WHERE mailitem_id = @mailitem_id
        AND body is null

        --Add CR    
        SET @CR_str = CHAR(13) + CHAR(10)
        UPDATE sysmail_mailitems
        SET body.WRITE(@CR_str, NULL, NULL)
        WHERE mailitem_id = @mailitem_id

   --Copy query results to mail body
        UPDATE sysmail_mailitems
        SET body.WRITE( (SELECT text_data from sysmail_query_transfer WHERE uid = @temp_table_uid), NULL, NULL )
        WHERE mailitem_id = @mailitem_id

    END

    --Copy into the attachments table
    IF(@AttachmentsExist = 1)
    BEGIN
        --Copy temp attachments to sysmail_attachments      
        INSERT INTO sysmail_attachments(mailitem_id, filename, filesize, attachment)
        SELECT @mailitem_id, filename, filesize, attachment
        FROM sysmail_attachments_transfer
        WHERE uid = @temp_table_uid
    END

    -- Create the primary SSB xml maessage
    SET @sendmailxml = '<requests:SendMail xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.microsoft.com/databasemail/requests RequestTypes.xsd" xmlns:requests="http://schemas.microsoft.com/databasemail/requests"><MailItemId>'
                        + CONVERT(NVARCHAR(20), @mailitem_id) + N'</MailItemId></requests:SendMail>'

    -- Send the send request on queue.
    EXEC @rc = sp_SendMailQueues @sendmailxml
    IF @rc <> 0
    BEGIN
       RAISERROR(14627, 16, 1, @rc, 'send mail')
       GOTO ErrorHandler;
    END

    -- Print success message if required
    IF (@exclude_query_output = 0)
    BEGIN
       SET @localmessage = FORMATMESSAGE(14635)
       PRINT @localmessage
    END  

    --
    -- See if the transaction needs to be commited
    --
    IF (@trancountSave = 0 and @tranStartedBool = 1)
       COMMIT TRAN @procName

    -- All done OK
    goto ExitProc;

    -----------------
    -- Error Handler
    -----------------
ErrorHandler:
    IF (@tranStartedBool = 1) 
       ROLLBACK TRAN @procName

    ------------------
    -- Exit Procedure
    ------------------
ExitProc:
   
    --Always delete query and attactment transfer records. 
   --Note: Query results can also be returned in the sysmail_attachments_transfer table
    DELETE sysmail_attachments_transfer WHERE uid = @temp_table_uid
    DELETE sysmail_query_transfer WHERE uid = @temp_table_uid

   --Raise an error it the query execution fails
   -- This will only be the case when @append_query_error is set to 0 (false)
   IF( (@RetErrorMsg IS NOT NULL) AND (@exclude_query_output=0) )
   BEGIN
      RAISERROR(14661, -1, -1, @RetErrorMsg)
   END

    RETURN (@rc)
END

GO

USE [msdb]
GO
/****** Object:  StoredProcedure [dbo].[sp_ProcessResponse]    Script Date: 08/04/2006 14:54:57 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
-- Processes responses from dbmail 
--
ALTER PROCEDURE [dbo].[sp_ProcessResponse]
    @conv_handle        uniqueidentifier,
    @message_type_name  NVARCHAR(256),
    @xml_message_body   NVARCHAR(max)
AS
BEGIN
    DECLARE 
        @idoc               INT,
        @mailitem_id        INT,
        @sent_status        INT,
        @rc                 INT,
        @index              INT,
        @processId          INT,
        @sent_date          DATETIME,
        @localmessage       NVARCHAR(max),
        @LogMessage         NVARCHAR(max),
        @retry_hconv        uniqueidentifier,
        @paramStr           NVARCHAR(256),
        @accRetryDelay      INT

    --------------------------
    --Always send the response 
    ;SEND ON CONVERSATION @conv_handle MESSAGE TYPE @message_type_name (@xml_message_body)


    --
    -- Need to handle the case where a sent retry is requested. 
    -- This is done by setting a conversation timer, The timer with go off in the external queue

    -- Get the handle to the xml document
    EXEC @rc = sp_xml_preparedocument 
                    @idoc OUTPUT, 
                    @xml_message_body, 
                    N'<ns xmlns:responses="http://schemas.microsoft.com/databasemail/responses" />'
    IF(@rc <> 0)
    BEGIN
        --Log the error. The response has already sent to the Internal queue. 
        -- This will update the mail with the latest staus
        SET @localmessage = FORMATMESSAGE(14655, CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage

        GOTO ErrorHandler;
    END

    -- Execute a SELECT statement that uses the OPENXML rowset provider to get the MailItemId and sent status.
    SELECT @mailitem_id     = MailItemId, 
           @sent_status     = SentStatus
    FROM OPENXML (@idoc, '/responses:SendMail', 1)
        WITH (MailItemId    INT      './MailItemId/@Id', 
              SentStatus    INT      './SentStatus/@Status')

    --Close the handle to the xml document
    EXEC sp_xml_removedocument @idoc

    IF(@mailitem_id IS NULL OR @sent_status IS NULL)
    BEGIN  
        --Log error and continue.
        SET @localmessage = FORMATMESSAGE(14652, CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage

        GOTO ErrorHandler;
    END      

	-- Update the status of the email item
	UPDATE msdb.dbo.sysmail_mailitems
	SET sent_status = @sent_status
	WHERE mailitem_id = @mailitem_id

    --
    -- A send retry has been requested. Set a conversation timer
    IF(@sent_status = 3)
    BEGIN
        -- Get the associated mail item data for the given @conversation_handle (if it exists)
       SELECT @retry_hconv = conversation_handle
       FROM sysmail_send_retries as sr
            RIGHT JOIN sysmail_mailitems as mi
            ON sr.mailitem_id = mi.mailitem_id
       WHERE mi.mailitem_id = @mailitem_id

        --Must be the first retry attempt. Create a sysmail_send_retries record to track retries
        IF(@retry_hconv IS NULL)
        BEGIN
            INSERT sysmail_send_retries(conversation_handle, mailitem_id) --last_send_attempt_date
            VALUES(@conv_handle, @mailitem_id)
        END
        ELSE
        BEGIN
            --Update existing retry record
            UPDATE sysmail_send_retries
            SET last_send_attempt_date = GETDATE(),
                send_attempts = send_attempts + 1
            WHERE mailitem_id = @mailitem_id

        END

        --Get the global retry delay time
        EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'AccountRetryDelay', 
                                                    @parameter_value = @paramStr OUTPUT
        --ConvertToInt will return the default if @paramStr is null
        SET @accRetryDelay = dbo.ConvertToInt(@paramStr, 0x7fffffff, 300) -- 5 min default


        --Now set the dialog timer. This triggers the send retry
        ;BEGIN CONVERSATION TIMER (@conv_handle) TIMEOUT = @accRetryDelay 

    END
    ELSE
    BEGIN
        --Only end theconversation if a retry isn't being attempted
        END CONVERSATION @conv_handle
    END


    -- All done OK
    goto ExitProc;

    -----------------
    -- Error Handler
    -----------------
ErrorHandler:

    ------------------
    -- Exit Procedure
    ------------------
ExitProc:
    RETURN (@rc);

END

go

/**************************************************************/
/* SP_HELP_JOBHISTORY                                         */
/**************************************************************/

use [msdb]

PRINT ''
PRINT 'Creating procedure sp_help_jobhistory...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobhistory_full')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobhistory_full
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobhistory_summary')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobhistory_summary
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobhistory_sem')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobhistory_sem
go
CREATE PROCEDURE sp_help_jobhistory_full
               @job_id               UNIQUEIDENTIFIER,
               @job_name             sysname,
               @step_id              INT,
               @sql_message_id       INT,
               @sql_severity         INT,
               @start_run_date       INT,
               @end_run_date         INT,
               @start_run_time       INT,
               @end_run_time         INT,
               @minimum_run_duration INT,
               @run_status           INT,
               @minimum_retries      INT,
               @oldest_first         INT,
               @server               sysname,
               @mode                 VARCHAR(7),
               @order_by             INT,
               @distributed_job_history BIT
AS
IF(@distributed_job_history = 1)
  SELECT null as instance_id, 
     sj.job_id,
     job_name = sj.name,
     null as step_id,
     null as step_name,
     null as sql_message_id,
     null as sql_severity,
     sjh.last_outcome_message as message,
     sjh.last_run_outcome as run_status,
     sjh.last_run_date as run_date,
     sjh.last_run_time as run_time,
    sjh.last_run_duration as run_duration,
     null as operator_emailed,
     null as operator_netsentname,
     null as operator_paged,
     null as retries_attempted,
     sts.server_name as server
  FROM msdb.dbo.sysjobservers                sjh
  JOIN msdb.dbo.systargetservers sts ON (sts.server_id = sjh.server_id)
  JOIN msdb.dbo.sysjobs_view     sj  ON(sj.job_id = sjh.job_id)
  WHERE 
  (@job_id = sjh.job_id)
  AND ((@start_run_date       IS NULL) OR (sjh.last_run_date >= @start_run_date))
  AND ((@end_run_date         IS NULL) OR (sjh.last_run_date <= @end_run_date))
  AND ((@start_run_time       IS NULL) OR (sjh.last_run_time >= @start_run_time))
  AND ((@minimum_run_duration IS NULL) OR (sjh.last_run_duration >= @minimum_run_duration))
  AND ((@run_status           IS NULL) OR (@run_status = sjh.last_run_outcome))
  AND ((@server               IS NULL) OR (sts.server_name = @server))
ELSE
  SELECT sjh.instance_id, -- This is included just for ordering purposes
     sj.job_id,
     job_name = sj.name,
     sjh.step_id,
     sjh.step_name,
     sjh.sql_message_id,
     sjh.sql_severity,
     sjh.message,
     sjh.run_status,
     sjh.run_date,
     sjh.run_time,
     sjh.run_duration,
     operator_emailed = so1.name,
     operator_netsent = so2.name,
     operator_paged = so3.name,
     sjh.retries_attempted,
     sjh.server
  FROM msdb.dbo.sysjobhistory                sjh
     LEFT OUTER JOIN msdb.dbo.sysoperators so1  ON (sjh.operator_id_emailed = so1.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so2  ON (sjh.operator_id_netsent = so2.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so3  ON (sjh.operator_id_paged = so3.id),
     msdb.dbo.sysjobs_view sj
  WHERE (sj.job_id = sjh.job_id)
  AND ((@job_id               IS NULL) OR (@job_id = sjh.job_id))
  AND ((@step_id              IS NULL) OR (@step_id = sjh.step_id))
  AND ((@sql_message_id       IS NULL) OR (@sql_message_id = sjh.sql_message_id))
  AND ((@sql_severity         IS NULL) OR (@sql_severity = sjh.sql_severity))
  AND ((@start_run_date       IS NULL) OR (sjh.run_date >= @start_run_date))
  AND ((@end_run_date         IS NULL) OR (sjh.run_date <= @end_run_date))
  AND ((@start_run_time       IS NULL) OR (sjh.run_time >= @start_run_time))
  AND ((@end_run_time         IS NULL) OR (sjh.run_time <= @end_run_time))
  AND ((@minimum_run_duration IS NULL) OR (sjh.run_duration >= @minimum_run_duration))
  AND ((@run_status           IS NULL) OR (@run_status = sjh.run_status))
  AND ((@minimum_retries      IS NULL) OR (sjh.retries_attempted >= @minimum_retries))
  AND ((@server               IS NULL) OR (sjh.server = @server))
  ORDER BY (sjh.instance_id * @order_by)

GO
CREATE PROCEDURE sp_help_jobhistory_summary
               @job_id               UNIQUEIDENTIFIER,
               @job_name             sysname,
               @step_id              INT,
               @sql_message_id       INT,
               @sql_severity         INT,
               @start_run_date       INT,
               @end_run_date         INT,
               @start_run_time       INT,
               @end_run_time         INT,
               @minimum_run_duration INT,
               @run_status           INT,
               @minimum_retries      INT,
               @oldest_first         INT,
               @server               sysname,
               @mode                 VARCHAR(7),
               @order_by             INT,
               @distributed_job_history BIT
AS
-- Summary format: same WHERE clause as for full, just a different SELECT list
IF(@distributed_job_history = 1)
  SELECT sj.job_id,
     job_name = sj.name,
     sjh.last_run_outcome as run_status,
     sjh.last_run_date as run_date,
     sjh.last_run_time as run_time,
     sjh.last_run_duration as run_duration,
     null as operator_emailed,
     null as operator_netsentname,
     null as operator_paged,
     null as retries_attempted,
     sts.server_name as server
  FROM msdb.dbo.sysjobservers                sjh
  JOIN msdb.dbo.systargetservers sts ON (sts.server_id = sjh.server_id)
  JOIN msdb.dbo.sysjobs_view     sj  ON(sj.job_id = sjh.job_id)
  WHERE 
  (@job_id = sjh.job_id)
  AND ((@start_run_date       IS NULL) OR (sjh.last_run_date >= @start_run_date))
  AND ((@end_run_date         IS NULL) OR (sjh.last_run_date <= @end_run_date))
  AND ((@start_run_time       IS NULL) OR (sjh.last_run_time >= @start_run_time))
  AND ((@minimum_run_duration IS NULL) OR (sjh.last_run_duration >= @minimum_run_duration))
  AND ((@run_status           IS NULL) OR (@run_status = sjh.last_run_outcome))
  AND ((@server               IS NULL) OR (sts.server_name = @server))
ELSE
  SELECT sj.job_id,
     job_name = sj.name,
     sjh.run_status,
     sjh.run_date,
     sjh.run_time,
     sjh.run_duration,
     operator_emailed = substring(so1.name, 1, 20),
     operator_netsent = substring(so2.name, 1, 20),
     operator_paged = substring(so3.name, 1, 20),
     sjh.retries_attempted,
     sjh.server
  FROM msdb.dbo.sysjobhistory                sjh
     LEFT OUTER JOIN msdb.dbo.sysoperators so1  ON (sjh.operator_id_emailed = so1.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so2  ON (sjh.operator_id_netsent = so2.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so3  ON (sjh.operator_id_paged = so3.id),
     msdb.dbo.sysjobs_view                 sj
  WHERE (sj.job_id = sjh.job_id)
  AND ((@job_id               IS NULL) OR (@job_id = sjh.job_id))
  AND ((@step_id              IS NULL) OR (@step_id = sjh.step_id))
  AND ((@sql_message_id       IS NULL) OR (@sql_message_id = sjh.sql_message_id))
  AND ((@sql_severity         IS NULL) OR (@sql_severity = sjh.sql_severity))
  AND ((@start_run_date       IS NULL) OR (sjh.run_date >= @start_run_date))
  AND ((@end_run_date         IS NULL) OR (sjh.run_date <= @end_run_date))
  AND ((@start_run_time       IS NULL) OR (sjh.run_time >= @start_run_time))
  AND ((@end_run_time         IS NULL) OR (sjh.run_time <= @end_run_time))
  AND ((@minimum_run_duration IS NULL) OR (sjh.run_duration >= @minimum_run_duration))
  AND ((@run_status           IS NULL) OR (@run_status = sjh.run_status))
  AND ((@minimum_retries      IS NULL) OR (sjh.retries_attempted >= @minimum_retries))
  AND ((@server               IS NULL) OR (sjh.server = @server))
  ORDER BY (sjh.instance_id * @order_by)

GO
CREATE PROCEDURE sp_help_jobhistory_sem
               @job_id               UNIQUEIDENTIFIER,
               @job_name             sysname,
               @step_id              INT,
               @sql_message_id       INT,
               @sql_severity         INT,
               @start_run_date       INT,
               @end_run_date         INT,
               @start_run_time       INT,
               @end_run_time         INT,
               @minimum_run_duration INT,
               @run_status           INT,
               @minimum_retries      INT,
               @oldest_first         INT,
               @server               sysname,
               @mode                 VARCHAR(7),
               @order_by             INT,
               @distributed_job_history BIT
AS
-- SQL Enterprise Manager format
IF(@distributed_job_history = 1)
  SELECT sj.job_id,
     null as step_name,
     sjh.last_outcome_message as message,
     sjh.last_run_outcome as run_status,
     sjh.last_run_date as run_date,
     sjh.last_run_time as run_time,
     sjh.last_run_duration as run_duration,
     null as operator_emailed,
     null as operator_netsentname,
     null as operator_paged
  FROM msdb.dbo.sysjobservers                sjh
  JOIN msdb.dbo.systargetservers sts ON (sts.server_id = sjh.server_id)
  JOIN msdb.dbo.sysjobs_view     sj  ON(sj.job_id = sjh.job_id)
  WHERE 
  (@job_id = sjh.job_id)
ELSE
  SELECT sjh.step_id,
     sjh.step_name,
     sjh.message,
     sjh.run_status,
     sjh.run_date,
     sjh.run_time,
     sjh.run_duration,
     operator_emailed = so1.name,
     operator_netsent = so2.name,
     operator_paged = so3.name
  FROM msdb.dbo.sysjobhistory                sjh
     LEFT OUTER JOIN msdb.dbo.sysoperators so1  ON (sjh.operator_id_emailed = so1.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so2  ON (sjh.operator_id_netsent = so2.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so3  ON (sjh.operator_id_paged = so3.id),
     msdb.dbo.sysjobs_view                 sj
  WHERE (sj.job_id = sjh.job_id)
  AND (@job_id = sjh.job_id)
  ORDER BY (sjh.instance_id * @order_by)
GO
ALTER PROCEDURE [dbo].[sp_help_jobhistory]
  @job_id               UNIQUEIDENTIFIER = NULL,
  @job_name             sysname          = NULL,
  @step_id              INT              = NULL,
  @sql_message_id       INT              = NULL,
  @sql_severity         INT              = NULL,
  @start_run_date       INT              = NULL,     -- YYYYMMDD
  @end_run_date         INT              = NULL,     -- YYYYMMDD
  @start_run_time       INT              = NULL,     -- HHMMSS
  @end_run_time         INT              = NULL,     -- HHMMSS
  @minimum_run_duration INT              = NULL,     -- HHMMSS
  @run_status           INT              = NULL,     -- SQLAGENT_EXEC_X code
  @minimum_retries      INT              = NULL,
  @oldest_first         INT              = 0,        -- Or 1
  @server               sysname          = NULL,
  @mode                 VARCHAR(7)       = 'SUMMARY' -- Or 'FULL' or 'SEM'
AS
BEGIN
  DECLARE @retval   INT
  DECLARE @order_by INT  -- Must be INT since it can be -1

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @server   = LTRIM(RTRIM(@server))
  SELECT @mode     = LTRIM(RTRIM(@mode))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@server = N'')   SELECT @server = NULL

  -- Check job id/name (if supplied)
  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check @start_run_date
  IF (@start_run_date IS NOT NULL)
  BEGIN
    EXECUTE @retval = sp_verify_job_date @start_run_date, '@start_run_date'
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check @end_run_date
  IF (@end_run_date IS NOT NULL)
  BEGIN
    EXECUTE @retval = sp_verify_job_date @end_run_date, '@end_run_date'
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check @start_run_time
  EXECUTE @retval = sp_verify_job_time @start_run_time, '@start_run_time'
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check @end_run_time
  EXECUTE @retval = sp_verify_job_time @end_run_time, '@end_run_time'
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check @run_status
  IF ((@run_status < 0) OR (@run_status > 5))
  BEGIN
    RAISERROR(14198, -1, -1, '@run_status', '0..5')
    RETURN(1) -- Failure
  END

  -- Check mode
  SELECT @mode = UPPER(@mode collate SQL_Latin1_General_CP1_CS_AS)
  IF (@mode NOT IN ('SUMMARY', 'FULL', 'SEM'))
  BEGIN
    RAISERROR(14266, -1, -1, '@mode', 'SUMMARY, FULL, SEM')
    RETURN(1) -- Failure
  END

  SELECT @order_by = -1
  IF (@oldest_first = 1)
    SELECT @order_by = 1

  DECLARE @distributed_job_history BIT 
  SET @distributed_job_history = 0
  
  IF (@job_id IS NOT NULL) AND ( EXISTS (SELECT *
                              FROM msdb.dbo.sysjobs       sj,
                                 msdb.dbo.sysjobservers sjs
                              WHERE (sj.job_id = sjs.job_id)
                                 AND (sj.job_id = @job_id)
                                 AND (sjs.server_id <> 0)))
   SET @distributed_job_history = 1

  -- Return history information filtered by the supplied parameters.
  -- Having actual queries in subprocedures allows better query plans because query optimizer sniffs correct parameters
  IF (@mode = 'FULL')
  BEGIN
  -- NOTE: SQLDMO relies on the 'FULL' format; ** DO NOT CHANGE IT **
	  EXECUTE sp_help_jobhistory_full
	     @job_id,
	     @job_name,
	     @step_id,
	     @sql_message_id,
	     @sql_severity,
	     @start_run_date,
	     @end_run_date,
	     @start_run_time,
	     @end_run_time,
	     @minimum_run_duration,
	     @run_status,
	     @minimum_retries,
	     @oldest_first,
	     @server,
	     @mode,
	     @order_by,
	     @distributed_job_history
  END
  ELSE
  IF (@mode = 'SUMMARY')
  BEGIN
    -- Summary format: same WHERE clause as for full, just a different SELECT list
    EXECUTE sp_help_jobhistory_summary
         @job_id,
         @job_name,
         @step_id,
         @sql_message_id,
         @sql_severity,
         @start_run_date,
         @end_run_date,
         @start_run_time,
         @end_run_time,
         @minimum_run_duration,
         @run_status,
         @minimum_retries,
         @oldest_first,
         @server,
         @mode,
         @order_by,
         @distributed_job_history
  END
  ELSE
  IF (@mode = 'SEM')
  BEGIN
    -- SQL Enterprise Manager format
    EXECUTE sp_help_jobhistory_sem
         @job_id,
         @job_name,
         @step_id,
         @sql_message_id,
         @sql_severity,
         @start_run_date,
         @end_run_date,
         @start_run_time,
         @end_run_time,
         @minimum_run_duration,
         @run_status,
         @minimum_retries,
         @oldest_first,
         @server,
         @mode,
         @order_by,
         @distributed_job_history
  END
  RETURN(0) -- Success
END
GO

GRANT EXECUTE ON sp_help_jobhistory  TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobhistory_full TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobhistory_summary TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobhistory_sem TO SQLAgentUserRole

/**************************************************************/
/* sysmail_event_log                                         */
/**************************************************************/
use msdb
go
PRINT ''
PRINT 'Creating view sysmail_event_log...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysmail_event_log')
              AND (type = 'V')))
  DROP VIEW sysmail_event_log
go

CREATE VIEW sysmail_event_log
AS
SELECT log_id,
       CASE event_type 
          WHEN 0 THEN 'success' 
          WHEN 1 THEN 'information' 
          WHEN 2 THEN 'warning' 
          ELSE 'error' 
       END as event_type,
       log_date,
       description,
       process_id,
       sl.mailitem_id,
       account_id,
       sl.last_mod_date,
       sl.last_mod_user
FROM [dbo].[sysmail_log]  sl
WHERE (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) OR 
      (EXISTS ( SELECT mailitem_id FROM [dbo].[sysmail_allitems] ai WHERE sl.mailitem_id = ai.mailitem_id ))


GO

GRANT SELECT  ON [dbo].[sysmail_event_log]                TO DatabaseMailUserRole

USE [msdb]
GO
/****** Object:  StoredProcedure [dbo].[sp_sysmail_activate]    Script Date: 06/22/2006 17:56:51 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- sp_sysmail_activate : Starts the DatabaseMail process if it isn't already running
--
ALTER PROCEDURE [dbo].[sp_sysmail_activate]

AS
BEGIN
    DECLARE @mailDbName sysname
    DECLARE @mailDbId INT
    DECLARE @mailEngineLifeMin INT
    DECLARE @loggingLevel nvarchar(256)
    DECLARE @loggingLevelInt int   
    DECLARE @parameter_value nvarchar(256)
    DECLARE @localmessage nvarchar(max)
	DECLARE @readFromConfigFile INT
    DECLARE @rc INT

    SET NOCOUNT ON
    EXEC sp_executesql @statement = N'RECEIVE TOP(0) * FROM msdb.dbo.ExternalMailQueue'

    EXEC @rc = msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'DatabaseMailExeMinimumLifeTime', 
                                                        @parameter_value = @parameter_value OUTPUT
    IF(@rc <> 0)
        RETURN (1)

    --ConvertToInt will return the default if @parameter_value is null or config value can't be converted
    --Setting max exe lifetime is 1 week (604800 secs). Can't see a reason for it to ever run longer that this
    SET @mailEngineLifeMin = dbo.ConvertToInt(@parameter_value, 604800, 600) 

	EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'ReadFromConfigurationFile', 
                                                  @parameter_value = @parameter_value OUTPUT
	--Try to read the optional read from configuration file:
    SET @readFromConfigFile = dbo.ConvertToInt(@parameter_value, 1, 0) 

    --Try and get the optional logging level for the DatabaseMail process
    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'LoggingLevel', 
                                                  @parameter_value = @loggingLevel OUTPUT

    --Convert logging level into string value for passing into XP
    SET @loggingLevelInt = dbo.ConvertToInt(@loggingLevel, 3, 2) 
    IF @loggingLevelInt = 1
       SET @loggingLevel = 'Normal'
    ELSE IF @loggingLevelInt = 3
       SET @loggingLevel = 'Verbose'
    ELSE -- default
       SET @loggingLevel = 'Extended'

    SET @mailDbName = DB_NAME()
    SET @mailDbId   = DB_ID()

    EXEC @rc = master..xp_sysmail_activate @mailDbId, @mailDbName, @readFromConfigFile,
	@mailEngineLifeMin, @loggingLevel
    IF(@rc <> 0)
    BEGIN
        SET @localmessage = FORMATMESSAGE(14637)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage
    END
    ELSE
    BEGIN
        SET @localmessage = FORMATMESSAGE(14638)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=0, @description=@localmessage
    END

    RETURN @rc
END
GO


USE [msdb]
GO
/****** Object:  StoredProcedure [dbo].[sysmail_delete_profile_sp]    Script Date: 07/19/2006 16:26:21 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO

ALTER PROCEDURE [dbo].[sysmail_delete_profile_sp]
   @profile_id int = NULL, -- must provide either id or name
   @profile_name sysname = NULL,
   @force_delete BIT = 1
AS
   SET NOCOUNT ON
  
   DECLARE @rc int
   DECLARE @profileid int
   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 0, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)

IF(EXISTS (select * from sysmail_unsentitems WHERE 
   sysmail_unsentitems.profile_id = @profileid) AND @force_delete <> 1)
BEGIN
	IF(@profile_name IS NULL)
	BEGIN
		select @profile_name = name from dbo.sysmail_profile WHERE profile_id = @profileid
	END
	RAISERROR(14668, -1, -1, @profile_name)
	RETURN (1)   
END

UPDATE [msdb].[dbo].[sysmail_mailitems]
SET [sent_status] = 2, [sent_date] = getdate()
WHERE profile_id = @profileid AND sent_status <> 1
     
   DELETE FROM msdb.dbo.sysmail_profile 
   WHERE profile_id = @profileid
   RETURN(0)
GO
/******** [sysmail_update_account_sp] ********/

USE [msdb]
GO
/****** Object:  StoredProcedure [dbo].[sysmail_update_account_sp]    Script Date: 06/26/2006 10:45:40 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO

USE [msdb]
GO
/****** Object:  StoredProcedure [dbo].[sp_ExternalMailQueueListener]    Script Date: 08/10/2006 14:31:04 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
-- Processes messages from the external mail queue
--
ALTER PROCEDURE [dbo].[sp_ExternalMailQueueListener]
AS
BEGIN
    DECLARE 
        @idoc               INT,
        @mailitem_id        INT,
        @sent_status        INT,
        @sent_account_id    INT,
        @rc                 INT,
        @processId          INT,
        @sent_date          DATETIME,
        @localmessage       NVARCHAR(max),
        @conv_handle        uniqueidentifier,
       @message_type_name  NVARCHAR(256),
       @xml_message_body   NVARCHAR(max),
        @LogMessage         NVARCHAR(max)

    -- Table to store message information.
    DECLARE @msgs TABLE
    (
        [conversation_handle]   uniqueidentifier,
       [message_type_name]     nvarchar(256),
       [message_body]          varbinary(max)
    )

    --RECEIVE messages from the external queue. 
    --MailItem status messages are sent from the external sql mail process along with other SSB notifications and errors
    ;RECEIVE conversation_handle, message_type_name, message_body FROM InternalMailQueue INTO @msgs
    -- Check if there was some error in reading from queue
    SET @rc = @@ERROR
    IF (@rc <> 0)
    BEGIN
        --Log error and continue. Don't want to block the following messages on the queue
        SET @localmessage = FORMATMESSAGE(@@ERROR)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage

        GOTO ErrorHandler;
    END
   
    -----------------------------------
    --Process sendmail status messages
    SELECT 
        @conv_handle        = conversation_handle,
        @message_type_name  = message_type_name, 
        @xml_message_body   = CAST(message_body AS NVARCHAR(MAX))
    FROM @msgs 
    WHERE [message_type_name] = N'{//www.microsoft.com/databasemail/messages}SendMailStatus'

    IF(@message_type_name IS NOT NULL)
    BEGIN
        --
        --Expecting the xml body to be n the following form:
        --
        --<?xml version="1.0" encoding="utf-16"?>
        --<responses:SendMail xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.microsoft.com/databasemail/responses ResponseTypes.xsd" xmlns:responses="http://schemas.microsoft.com/databasemail/responses">
        --<Information>
        --    <Failure Message="THe error message...."/>
        --</Information>
        --<MailItemId Id="1" />
        --<SentStatus Status="3" />
        --<SentAccountId Id="0" />
        --<SentDate Date="2005-03-30T14:55:13" />
        --<CallingProcess Id="3012" />
        --</responses:SendMail>

        -- Get the handle to the xml document
        EXEC @rc = sp_xml_preparedocument 
                        @idoc OUTPUT, 
                        @xml_message_body, 
                        N'<ns xmlns:responses="http://schemas.microsoft.com/databasemail/responses" />'
        IF(@rc <> 0)
        BEGIN
            --Log error and continue. Don't want to block the following messages on the queue
            SET @localmessage = FORMATMESSAGE(14655, CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
            exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage

            GOTO ErrorHandler;
        END

        -- Execute a SELECT statement that uses the OPENXML rowset provider to get the MailItemId and sent status.
        SELECT @mailitem_id     = MailItemId, 
               @sent_status     = SentStatus,
               @sent_account_id = SentAccountId,
               @sent_date       = SentDate,
               @processId       = CallingProcess,
               @LogMessage      = LogMessage
        FROM OPENXML (@idoc, '/responses:SendMail', 1)
            WITH (MailItemId    INT      './MailItemId/@Id', 
                  SentStatus    INT      './SentStatus/@Status',
                  SentAccountId INT      './SentAccountId/@Id',
                  SentDate      DATETIME './SentDate/@Date', --The date was formated using ISO8601
                  CallingProcess INT     './CallingProcess/@Id', 
                  LogMessage     NVARCHAR(max) './Information/Failure/@Message')

        --Close the handle to the xml document
        EXEC sp_xml_removedocument @idoc

        IF(@mailitem_id IS NULL)
        BEGIN  
            --Log error and continue. Don't want to block the following messages on the queue by rolling back the tran
            SET @localmessage = FORMATMESSAGE(14652, CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
            exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage
        END      
        ELSE
        BEGIN
            -- check sent_status is valid : 0(PendingSend), 1(SendSuccessful), 2(SendFailed), 3(AttemptingSendRetry)
            IF(@sent_status NOT IN (1, 2, 3))
            BEGIN
                SET @localmessage = FORMATMESSAGE(14653, N'SentStatus', CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
                exec msdb.dbo.sysmail_logmailevent_sp @event_type=2, @description=@localmessage

                --Set value to SendFailed
                SET @sent_status = 2
            END

            --Make the @sent_account_id NULL if it is 0. 
            IF(@sent_account_id IS NOT NULL AND @sent_account_id = 0)
                SET @sent_account_id = NULL

            --
            -- Update the mail status if not a retry. Nothing else needs to be done in this case
            UPDATE sysmail_mailitems
            SET sent_status     = CAST (@sent_status as TINYINT),
                sent_account_id = @sent_account_id,
                sent_date       = @sent_date
            WHERE mailitem_id = @mailitem_id
        
            -- Report a failure if no record is found in the sysmail_mailitems table
            IF (@@ROWCOUNT = 0)
            BEGIN
                SET @localmessage = FORMATMESSAGE(14653, N'MailItemId', CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
                exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage
            END

            IF (@LogMessage IS NOT NULL)
            BEGIN
                exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@LogMessage, @process_id=@processId, @mailitem_id=@mailitem_id, @account_id=@sent_account_id
            END
        END
    END

    -------------------------------------------------------
    --Process all other messages by logging to sysmail_log
    SET @conv_handle = NULL;
    
    --Always end the conversion if this message is received
    SELECT @conv_handle = conversation_handle
    FROM @msgs 
    WHERE [message_type_name] = N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'
    
    IF(@conv_handle IS NOT NULL)
    BEGIN
        END CONVERSATION @conv_handle;
    END

    DECLARE @queuemessage nvarchar(max)
    DECLARE queue_messages_cursor CURSOR LOCAL 
    FOR
        SELECT FORMATMESSAGE(14654, CONVERT(NVARCHAR(50), conversation_handle), message_type_name, message_body)
        FROM @msgs 
        WHERE [message_type_name] 
              NOT IN (N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog',
                      N'{//www.microsoft.com/databasemail/messages}SendMailStatus')
  
    OPEN queue_messages_cursor 
    FETCH NEXT FROM queue_messages_cursor INTO @queuemessage
    WHILE (@@fetch_status = 0)
    BEGIN
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=2, @description=@queuemessage
        FETCH NEXT FROM queue_messages_cursor INTO @queuemessage
    END
    CLOSE queue_messages_cursor 
    DEALLOCATE queue_messages_cursor 

    -- All done OK
    goto ExitProc;

    -----------------
    -- Error Handler
    -----------------
ErrorHandler:

    ------------------
    -- Exit Procedure
    ------------------
ExitProc:
    RETURN (@rc)
END

GO

ALTER PROCEDURE [dbo].[sysmail_update_account_sp]
   @account_id int = NULL, -- must provide either id or name
   @account_name sysname = NULL,
   @email_address nvarchar(128) = NULL,
   @display_name nvarchar(128) = NULL,
   @replyto_address nvarchar(128) = NULL,
   @description nvarchar(256) = NULL,
   @mailserver_name sysname = NULL,  
   @mailserver_type sysname = NULL,  
   @port int = NULL,
   @username sysname = NULL,
   @password sysname = NULL,
   @use_default_credentials bit = NULL,
   @enable_ssl bit = NULL
  -- WITH EXECUTE AS OWNER --Allows access to sys.credentials
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @accountid int
   DECLARE @credential_id int
   DECLARE @credential_name sysname

   exec @rc = msdb.dbo.sysmail_verify_account_sp @account_id, @account_name, 0, 1, @accountid OUTPUT
   IF @rc <> 0
      RETURN(1)

   IF(@email_address IS NULL)
   BEGIN
   SELECT @email_address = email_address FROM msdb.dbo.sysmail_account WHERE account_id=@accountid
   END


   IF(@display_name IS NULL)
   BEGIN
   SELECT @display_name = display_name FROM msdb.dbo.sysmail_account WHERE account_id=@accountid
   END

   IF(@replyto_address IS NULL)
   BEGIN
   SELECT @replyto_address = replyto_address FROM msdb.dbo.sysmail_account WHERE account_id=@accountid
   END

   IF(@description IS NULL)
   BEGIN
   SELECT @description = description FROM msdb.dbo.sysmail_account WHERE account_id=@accountid
   END


   IF(@port IS NULL)
   BEGIN
   SELECT @port = port FROM msdb.dbo.sysmail_server WHERE account_id=@accountid
   END

   IF(@use_default_credentials IS NULL)
   BEGIN
   SELECT @use_default_credentials = use_default_credentials FROM msdb.dbo.sysmail_server WHERE account_id=@accountid
   END

   IF(@enable_ssl IS NULL)
   BEGIN
   SELECT @enable_ssl = enable_ssl FROM msdb.dbo.sysmail_server WHERE account_id=@accountid
   END

   IF(@mailserver_type IS NULL)
   BEGIN
   SELECT @mailserver_type = servertype FROM msdb.dbo.sysmail_server WHERE account_id=@accountid
   END


   EXEC @rc = msdb.dbo.sysmail_verify_accountparams_sp 
            @use_default_credentials = @use_default_credentials,
            @mailserver_type = @mailserver_type OUTPUT, -- validates and returns trimmed value
            @username = @username OUTPUT, -- returns trimmed value
            @password = @password OUTPUT  -- returns empty string if @username is given and @password is null 
   IF(@rc <> 0)
      RETURN (1)

   --transact this in case credential updates fail
   BEGIN TRAN
   -- update account table
   IF (@account_name IS NOT NULL)
      IF (@email_address IS NOT NULL)
         UPDATE msdb.dbo.sysmail_account
         SET name=@account_name, description=@description, email_address=@email_address, display_name=@display_name, replyto_address=@replyto_address
         WHERE account_id=@accountid
      ELSE
         UPDATE msdb.dbo.sysmail_account
         SET name=@account_name, description=@description, display_name=@display_name, replyto_address=@replyto_address
         WHERE account_id=@accountid

   ELSE
      IF (@email_address IS NOT NULL)
         UPDATE msdb.dbo.sysmail_account
         SET description=@description, email_address=@email_address, display_name=@display_name, replyto_address=@replyto_address
         WHERE account_id=@accountid
      ELSE
         UPDATE msdb.dbo.sysmail_account
         SET description=@description, display_name=@display_name, replyto_address=@replyto_address
         WHERE account_id=@accountid
      
   -- see if a credential has been stored for this account
   SELECT @credential_name = name,
         @credential_id = c.credential_id
   FROM sys.credentials as c
     JOIN msdb.dbo.sysmail_server as ms
       ON c.credential_id = ms.credential_id
   WHERE account_id = @accountid 
     AND servertype = @mailserver_type

   --update the credential store
   IF(@credential_name IS NOT NULL)
   BEGIN
      --Remove the unneed credential
      IF(@username IS NULL)
      BEGIN
         SET @credential_id = NULL
         EXEC @rc = msdb.dbo.sysmail_drop_user_credential_sp 
                        @credential_name = @credential_name
      END
      -- Update the credential
      ELSE
      BEGIN
         EXEC @rc = msdb.dbo.sysmail_alter_user_credential_sp
                        @credential_name = @credential_name,
                        @username = @username,
                        @password = @password
      END

      IF(@rc <> 0)
      BEGIN 
         ROLLBACK TRAN
         RETURN (1)
      END
   END
   -- create a new credential if one doesn't exist
   ELSE IF(@credential_name IS NULL AND @username IS NOT NULL)
   BEGIN
      EXEC @rc = msdb.dbo.sysmail_create_user_credential_sp
                     @username = @username,
                     @password = @password,
                     @credential_id = @credential_id  OUTPUT
      IF(@rc <> 0)
      BEGIN 
         ROLLBACK TRAN
         RETURN (1)
      END
   END

   -- update server table
   IF (@mailserver_name IS NOT NULL)
      UPDATE msdb.dbo.sysmail_server 
      SET servername=@mailserver_name, port=@port, username=@username, credential_id = @credential_id, use_default_credentials = @use_default_credentials, enable_ssl = @enable_ssl
      WHERE account_id=@accountid AND servertype=@mailserver_type
   
   ELSE
      UPDATE msdb.dbo.sysmail_server 
      SET port=@port, username=@username, credential_id = @credential_id, use_default_credentials = @use_default_credentials, enable_ssl = @enable_ssl
      WHERE account_id=@accountid AND servertype=@mailserver_type
      
   COMMIT TRAN
   RETURN(0)
go

IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='msx_job_id' and id =
        (select id from msdb.dbo.sysobjects where name='sysmaintplan_subplans'))
BEGIN
    PRINT 'Altering table sysmaintplan_subplans...'
    ALTER TABLE sysmaintplan_subplans ADD msx_job_id UNIQUEIDENTIFIER DEFAULT NULL NULL

    ALTER TABLE sysmaintplan_subplans ADD CONSTRAINT FK_subplan_msx_job_id FOREIGN KEY (msx_job_id) REFERENCES sysjobs(job_id)
END

IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='msx_plan' and id =
        (select id from msdb.dbo.sysobjects where name='sysmaintplan_subplans'))
BEGIN
    ALTER TABLE sysmaintplan_subplans ADD msx_plan bit DEFAULT 0 NOT NULL
END

GO

/**************************************************************/
/* sp_maintplan_update_subplan                                */
/**************************************************************/
PRINT ''
PRINT 'Updating procedure sp_maintplan_update_subplan...'
go
ALTER PROCEDURE sp_maintplan_update_subplan
    @subplan_id       UNIQUEIDENTIFIER,
    @plan_id       UNIQUEIDENTIFIER    = NULL,
    @name          sysname             = NULL,
    @description  NVARCHAR(512)       = NULL,
    @job_id        UNIQUEIDENTIFIER    = NULL,
    @schedule_id  INT                 = NULL,
    @allow_create   BIT                 = 0,
    @msx_job_id    UNIQUEIDENTIFIER    = NULL
AS
BEGIN

   SET NOCOUNT ON

   SELECT @name = LTRIM(RTRIM(@name))
   SELECT @description = LTRIM(RTRIM(@description))

   --Are we creating a new entry or updating an existing one?

   IF( NOT EXISTS(SELECT * FROM msdb.dbo.sysmaintplan_subplans WHERE subplan_id = @subplan_id) )
   BEGIN
        -- Only allow creation of a record if user permits it
        IF(@allow_create = 0)
        BEGIN
            DECLARE @subplan_id_as_char VARCHAR(36)
            SELECT @subplan_id_as_char = CONVERT(VARCHAR(36), @subplan_id)
            RAISERROR(14262, -1, -1, '@subplan_id', @subplan_id_as_char)
          RETURN(1)
        END

        --Insert it's a new subplan
      IF (@name IS NULL)
      BEGIN
          RAISERROR(12981, -1, -1, '@name')
         RETURN(1) -- Failure
      END

      IF (@plan_id IS NULL)
      BEGIN
          RAISERROR(12981, -1, -1, '@plan_id')
         RETURN(1) -- Failure
      END

      INSERT INTO msdb.dbo.sysmaintplan_subplans(
            subplan_id,
            plan_id,
            subplan_description,
            subplan_name,
            job_id,
            schedule_id,
            msx_job_id)
      VALUES(
            @subplan_id,
            @plan_id,
            @description,
            @name,
            @job_id,
            @schedule_id,
            @msx_job_id)

   END
   ELSE
   BEGIN --Update the table

      DECLARE @s_subplan_name sysname
      DECLARE @s_job_id UNIQUEIDENTIFIER

      SELECT @s_subplan_name         = subplan_name,
            @s_job_id               = job_id
      FROM msdb.dbo.sysmaintplan_subplans
      WHERE (@subplan_id = subplan_id)

      --Determine if user wants to change these variables
      IF (@name IS NOT NULL)          SELECT @s_subplan_name          = @name
      IF (@job_id IS NOT NULL)        SELECT @s_job_id                = @job_id

      --UPDATE the record

      UPDATE msdb.dbo.sysmaintplan_subplans 
        SET subplan_name        = @s_subplan_name,
            subplan_description = @description,
            job_id              = @s_job_id,
            schedule_id         = @schedule_id,
            msx_job_id          = @msx_job_id
      WHERE (subplan_id = @subplan_id)

   END

    RETURN (@@ERROR)
END
GO

/**************************************************************/
/* sp_maintplan_delete_subplan                                */
/**************************************************************/
PRINT ''
PRINT 'Altering procedure sp_maintplan_delete_subplan...'
go
ALTER PROCEDURE sp_maintplan_delete_subplan
    @subplan_id       UNIQUEIDENTIFIER,
    @delete_jobs BIT                   = 1
AS
BEGIN

    DECLARE @retval     INT
    DECLARE @job        UNIQUEIDENTIFIER
    DECLARE @jobMsx     UNIQUEIDENTIFIER

    SET NOCOUNT ON
    SET @retval = 0

    -- Raise an error if the @subplan_id doesn't exist
    IF( NOT EXISTS(SELECT * FROM sysmaintplan_subplans WHERE subplan_id = @subplan_id))
    BEGIN
        DECLARE @subplan_id_as_char VARCHAR(36)
        SELECT @subplan_id_as_char = CONVERT(VARCHAR(36), @subplan_id)
        RAISERROR(14262, -1, -1, '@subplan_id', @subplan_id_as_char)
        RETURN(1)
    END


    BEGIN TRAN

    --Is there an Agent Job/Schedule associated with this subplan?
    SELECT @job = job_id, @jobMsx = msx_job_id
    FROM msdb.dbo.sysmaintplan_subplans 
    WHERE subplan_id = @subplan_id

    EXEC @retval = msdb.dbo.sp_maintplan_delete_log @subplan_id = @subplan_id
    IF (@retval <> 0)
    BEGIN
        ROLLBACK TRAN
        RETURN @retval
    END

    -- Delete the subplans table entry first since it has a foreign
    -- key constraint on its job_id existing in sysjobs.
    DELETE msdb.dbo.sysmaintplan_subplans 
    WHERE (subplan_id = @subplan_id)

    IF (@delete_jobs = 1)
    BEGIN
        --delete the local job associated with this subplan
        IF (@job IS NOT NULL)
        BEGIN
            EXEC @retval = msdb.dbo.sp_delete_job @job_id = @job, @delete_unused_schedule = 1
            IF (@retval <> 0)
            BEGIN
                ROLLBACK TRAN
                RETURN @retval
            END
        END

        --delete the multi-server job associated with this subplan.
        IF (@jobMsx IS NOT NULL)
        BEGIN 
            EXEC @retval = msdb.dbo.sp_delete_job @job_id = @jobMsx, @delete_unused_schedule = 1
            IF (@retval <> 0)
            BEGIN
                ROLLBACK TRAN
                RETURN @retval
            END
        END
    END

    COMMIT TRAN
    RETURN (0)
END
go

/**************************************************************/
/* SP_MAINTPLAN_UPDATE_SUBPLAN_TSX                            */
/**************************************************************/
PRINT ''
PRINT 'Updating procedure sp_maintplan_update_subplan_tsx...'
IF (OBJECT_ID(N'dbo.sp_maintplan_update_subplan_tsx', 'P') IS NULL)
  execute sp_executesql N'CREATE PROCEDURE sp_maintplan_update_subplan_tsx
    AS
    select 1
'
go

-- This procedure is called when a maintenance plan subplan record
-- needs to be created or updated to match a multi-server Agent job
-- that has arrived from the master server.
ALTER PROCEDURE sp_maintplan_update_subplan_tsx
    @subplan_id    UNIQUEIDENTIFIER,
    @plan_id       UNIQUEIDENTIFIER,
    @name          sysname,
    @description   NVARCHAR(512),
    @job_id        UNIQUEIDENTIFIER
AS
BEGIN
    -- Find out what schedule, if any, is associated with the job.
    declare @schedule_id int
    select @schedule_id = (SELECT TOP(1) schedule_id
                           FROM msdb.dbo.sysjobschedules
                           WHERE (job_id = @job_id) )

    exec sp_maintplan_update_subplan @subplan_id, @plan_id, @name, @description, @job_id, @schedule_id, @allow_create=1

    -- Be sure to mark this subplan as coming from the master, not locally.
    update sysmaintplan_subplans
    set msx_plan = 1
    where subplan_id = @subplan_id
    
END
go

/**************************************************************/
/* SP_MAINTPLAN_SUBPLANS_BY_JOB                               */
/**************************************************************/
PRINT ''
PRINT 'Updating procedure sp_maintplan_subplans_by_job...'
IF (OBJECT_ID(N'dbo.sp_maintplan_subplans_by_job', 'P') IS NULL)
  execute sp_executesql N'CREATE PROCEDURE sp_maintplan_subplans_by_job
    @job_id  UNIQUEIDENTIFIER
    AS
    select 1
'
go
-- If the given job_id is associated with a maintenance plan,
-- then matching entries from sysmaintplan_subplans are returned.
ALTER PROCEDURE sp_maintplan_subplans_by_job
    @job_id  UNIQUEIDENTIFIER
AS
BEGIN
    select plans.name as 'plan_name', plans.id as 'plan_id', subplans.subplan_name, subplans.subplan_id
    from sysmaintplan_plans plans, sysmaintplan_subplans subplans
    where  plans.id = subplans.plan_id
    and (job_id = @job_id
         or msx_job_id = @job_id)
    order by subplans.plan_id, subplans.subplan_id
END
go

-- Allow SQLAgent on target servers to gather information about
-- maintenance plans from the master.
GRANT EXECUTE ON sp_maintplan_subplans_by_job  TO SQLAgentUserRole
GRANT EXECUTE ON sp_maintplan_subplans_by_job  TO TargetServersRole

GO

/**************************************************************/
/* SYSMAINTPLAN_LOG                                           */
/**************************************************************/
-- Alter the sysmaintplan_log table to allow for remote logging
-- of maintenance plan execution.
IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='logged_remotely' and id =
        (select id from msdb.dbo.sysobjects where name='sysmaintplan_log'))
BEGIN
    alter table sysmaintplan_log
        alter column plan_id uniqueidentifier NULL
    alter table sysmaintplan_log
        alter column subplan_id uniqueidentifier NULL
    alter table sysmaintplan_log
    add logged_remotely bit not null default (0),
        source_server_name nvarchar (128) NULL,
        plan_name nvarchar (128) NULL,
        subplan_name nvarchar (128) NULL
END
GO

/**************************************************************/
/* SYSMAINTPLAN_PLANS                                         */
/**************************************************************/

-- Alter the sysmaintplan_plans view to include the from_msx and
-- has_targets columns, which indicate that this maintenance plan is
-- a distributed plan that came from an MSX server, or that it
-- a plan that is currently distributed to other servers.
ALTER VIEW sysmaintplan_plans
AS
   SELECT
   s.name AS [name],
   s.id AS [id],
   s.description AS [description],
   s.createdate AS [create_date],
   suser_sname(s.ownersid) AS [owner],
   s.vermajor AS [version_major],
   s.verminor AS [version_minor],
   s.verbuild AS [version_build],
   s.vercomments AS [version_comments],
   ISNULL((select TOP 1 msx_plan from sysmaintplan_subplans where plan_id = s.id), 0) AS [from_msx],
   CASE WHEN (NOT EXISTS (select TOP 1 msx_job_id 
                          from sysmaintplan_subplans subplans, sysjobservers jobservers
                          where plan_id = s.id 
                          and msx_job_id is not null
                          and subplans.msx_job_id = jobservers.job_id
                          and server_id != 0)) 
        then 0 
        else 1 END AS [has_targets]
   FROM
   msdb.dbo.sysdtspackages90 AS s
   WHERE
   (s.folderid = '08aa12d5-8f98-4dab-a4fc-980b150a5dc8' and s.packagetype = 6)
go

/**************************************************************/
/* SP_DELETE_JOB_REFERENCES                                   */
/**************************************************************/

PRINT ''
PRINT 'Updating procedure sp_delete_job_references...'
go
ALTER PROCEDURE sp_delete_job_references
  @notify_sqlagent BIT = 1
AS
BEGIN
  DECLARE @deleted_job_id  UNIQUEIDENTIFIER
  DECLARE @task_id_as_char VARCHAR(10)
  DECLARE @job_is_cached   INT
  DECLARE @alert_name      sysname
  DECLARE @maintplan_plan_id  UNIQUEIDENTIFIER
  DECLARE @maintplan_subplan_id  UNIQUEIDENTIFIER

  -- Keep SQLServerAgent's cache in-sync and cleanup any 'webtask' cross-references to the deleted job(s)
  -- NOTE: The caller must have created a table called #temp_jobs_to_delete of the format
  --       (job_id UNIQUEIDENTIFIER NOT NULL, job_is_cached INT NOT NULL).

  DECLARE sqlagent_notify CURSOR LOCAL
  FOR
  SELECT job_id, job_is_cached
  FROM #temp_jobs_to_delete

  OPEN sqlagent_notify
  FETCH NEXT FROM sqlagent_notify INTO @deleted_job_id, @job_is_cached

  WHILE (@@fetch_status = 0)
  BEGIN
    -- NOTE: We only notify SQLServerAgent if we know the job has been cached
    IF(@job_is_cached = 1 AND @notify_sqlagent = 1)
      EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                          @job_id      = @deleted_job_id,
                                          @action_type = N'D'

    IF (EXISTS (SELECT *
                FROM master.dbo.sysobjects
                WHERE (name = N'sp_cleanupwebtask')
                  AND (type = 'P')))
    BEGIN
      SELECT @task_id_as_char = CONVERT(VARCHAR(10), task_id)
      FROM msdb.dbo.systaskids
      WHERE (job_id = @deleted_job_id)
      IF (@task_id_as_char IS NOT NULL)
        EXECUTE ('master.dbo.sp_cleanupwebtask @taskid = ' + @task_id_as_char)
    END

    -- Maintenance plan cleanup for SQL 2005.
    -- If this job came from another server and it runs a subplan of a
    -- maintenance plan, then delete the subplan record. If that was
    -- the last subplan still referencing that plan, delete the plan.
    -- This removes a distributed maintenance plan from a target server
    -- once all of jobs from the master server that used that maintenance
    -- plan are deleted.
    SELECT @maintplan_plan_id = plans.plan_id, @maintplan_subplan_id = plans.subplan_id
    FROM sysmaintplan_subplans plans, sysjobs_view sjv
    WHERE plans.job_id = @deleted_job_id
      AND plans.job_id = sjv.job_id
      AND sjv.master_server = 1 -- This means the job came from the master

    IF (@maintplan_subplan_id is not NULL)
    BEGIN
      EXECUTE sp_maintplan_delete_subplan @subplan_id = @maintplan_subplan_id, @delete_jobs = 0
      IF (NOT EXISTS (SELECT *
                      FROM sysmaintplan_subplans
                      where plan_id = @maintplan_plan_id))
      BEGIN
        DECLARE @plan_name sysname

        SELECT @plan_name = name
          FROM sysmaintplan_plans
          WHERE id = @maintplan_plan_id

        EXECUTE sp_dts_deletepackage @name = @plan_name, @folderid = '08aa12d5-8f98-4dab-a4fc-980b150a5dc8' -- this is the guid for 'Maintenance Plans'
      END
    END

    FETCH NEXT FROM sqlagent_notify INTO @deleted_job_id, @job_is_cached
  END
  DEALLOCATE sqlagent_notify

  -- Remove systaskid references (must do this AFTER sp_cleanupwebtask stuff)
  DELETE FROM msdb.dbo.systaskids
  WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

  -- Remove sysdbmaintplan_jobs references (legacy maintenance plans prior to SQL 2005)
  DELETE FROM msdb.dbo.sysdbmaintplan_jobs
  WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

  -- Finally, clean up any dangling references in sysalerts to the deleted job(s)
  DECLARE sysalerts_cleanup CURSOR LOCAL
  FOR
  SELECT name
  FROM msdb.dbo.sysalerts
  WHERE (job_id IN (SELECT job_id FROM #temp_jobs_to_delete))

  OPEN sysalerts_cleanup
  FETCH NEXT FROM sysalerts_cleanup INTO @alert_name
  WHILE (@@fetch_status = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_update_alert @name   = @alert_name,
                                     @job_id = 0x00
    FETCH NEXT FROM sysalerts_cleanup INTO @alert_name
  END
  DEALLOCATE sysalerts_cleanup
END
go


/**************************************************************/
/* SP_DELETE_JOBSERVER                                        */
/**************************************************************/

PRINT ''
PRINT 'Updating procedure sp_delete_jobserver...'
go
ALTER PROCEDURE sp_delete_jobserver
  @job_id      UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name    sysname          = NULL, -- Must provide either this or job_id
  @server_name sysname
AS
BEGIN
  DECLARE @retval             INT
  DECLARE @server_id          INT
  DECLARE @local_machine_name sysname

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @server_name = LTRIM(RTRIM(@server_name))

  IF (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = '(LOCAL)')
    SELECT @server_name = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- First, check if the server is the local server
  EXECUTE @retval = master.dbo.xp_getnetname @local_machine_name OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure
  IF (@local_machine_name IS NOT NULL) AND (UPPER(@server_name) = UPPER(@local_machine_name))
    SELECT @server_name = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

  -- Check server name
  IF (UPPER(@server_name) <> UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))))
  BEGIN
    SELECT @server_id = server_id
    FROM msdb.dbo.systargetservers
    WHERE (UPPER(server_name) = @server_name)
    IF (@server_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@server_name', @server_name)
      RETURN(1) -- Failure
    END
  END
  ELSE
    SELECT @server_id = 0

  -- Check that the job is indeed targeted at the server
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysjobservers
                  WHERE (job_id = @job_id)
                    AND (server_id = @server_id)))
  BEGIN
    RAISERROR(14270, -1, -1, @job_name, @server_name)
    RETURN(1) -- Failure
  END

  -- Instruct the deleted server to purge the job
  -- NOTE: We must do this BEFORE we delete the sysjobservers row
  EXECUTE @retval = sp_post_msx_operation 'DELETE', 'JOB', @job_id, @server_name

  -- Delete the sysjobservers row
  DELETE FROM msdb.dbo.sysjobservers
  WHERE (job_id = @job_id)
    AND (server_id = @server_id)

  -- We used to change the category_id to 0 when removing the last job server
  -- from a job. We no longer do this.
--  IF (NOT EXISTS (SELECT *
--                  FROM msdb.dbo.sysjobservers
--                  WHERE (job_id = @job_id)))
--  BEGIN
--    UPDATE msdb.dbo.sysjobs
--    SET category_id = 0 -- [Uncategorized (Local)]
--    WHERE (job_id = @job_id)
--  END

  -- If the job is local, make sure that SQLServerAgent removes it from cache
  IF (@server_id = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                        @job_id      = @job_id,
                                        @action_type = N'D'
  END

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_GET_COMPOSITE_JOB_INFO                                  */
/**************************************************************/

PRINT ''
PRINT 'Updating procedure sp_get_composite_job_info...'
go
ALTER PROCEDURE sp_get_composite_job_info
  @job_id             UNIQUEIDENTIFIER = NULL,
  @job_type           VARCHAR(12)      = NULL,  -- LOCAL or MULTI-SERVER
  @owner_login_name   sysname          = NULL,
  @subsystem          NVARCHAR(40)     = NULL,
  @category_id        INT              = NULL,
  @enabled            TINYINT          = NULL,
  @execution_status   INT              = NULL,  -- 0 = Not idle or suspended, 1 = Executing, 2 = Waiting For Thread, 3 = Between Retries, 4 = Idle, 5 = Suspended, [6 = WaitingForStepToFinish], 7 = PerformingCompletionActions
  @date_comparator    CHAR(1)          = NULL,  -- >, < or =
  @date_created       DATETIME         = NULL,
  @date_last_modified DATETIME         = NULL,
  @description        NVARCHAR(512)    = NULL,  -- We do a LIKE on this so it can include wildcards
  @schedule_id        INT              = NULL   -- if supplied only return the jobs that use this schedule
AS
BEGIN
  DECLARE @can_see_all_running_jobs INT
  DECLARE @job_owner   sysname

  SET NOCOUNT ON

  -- By 'composite' we mean a combination of sysjobs and xp_sqlagent_enum_jobs data.
  -- This proc should only ever be called by sp_help_job, so we don't verify the
  -- parameters (sp_help_job has already done this).

  -- Step 1: Create intermediate work tables
  DECLARE @job_execution_state TABLE (job_id                  UNIQUEIDENTIFIER NOT NULL,
                                     date_started            INT              NOT NULL,
                                     time_started            INT              NOT NULL,
                                     execution_job_status    INT              NOT NULL,
                                     execution_step_id       INT              NULL,
                                     execution_step_name     sysname          COLLATE database_default NULL,
                                     execution_retry_attempt INT              NOT NULL,
                                     next_run_date           INT              NOT NULL,
                                     next_run_time           INT              NOT NULL,
                                     next_run_schedule_id    INT              NOT NULL)
  DECLARE @filtered_jobs TABLE (job_id                   UNIQUEIDENTIFIER NOT NULL,
                               date_created             DATETIME         NOT NULL,
                               date_last_modified       DATETIME         NOT NULL,
                               current_execution_status INT              NULL,
                               current_execution_step   sysname          COLLATE database_default NULL,
                               current_retry_attempt    INT              NULL,
                               last_run_date            INT              NOT NULL,
                               last_run_time            INT              NOT NULL,
                               last_run_outcome         INT              NOT NULL,
                               next_run_date            INT              NULL,
                               next_run_time            INT              NULL,
                               next_run_schedule_id     INT              NULL,
                               type                     INT              NOT NULL)
  DECLARE @xp_results TABLE (job_id                UNIQUEIDENTIFIER NOT NULL,
                            last_run_date         INT              NOT NULL,
                            last_run_time         INT              NOT NULL,
                            next_run_date         INT              NOT NULL,
                            next_run_time         INT              NOT NULL,
                            next_run_schedule_id  INT              NOT NULL,
                            requested_to_run      INT              NOT NULL, -- BOOL
                            request_source        INT              NOT NULL,
                            request_source_id     sysname          COLLATE database_default NULL,
                            running               INT              NOT NULL, -- BOOL
                            current_step          INT              NOT NULL,
                            current_retry_attempt INT              NOT NULL,
                            job_state             INT              NOT NULL)

  -- Step 2: Capture job execution information (for local jobs only since that's all SQLServerAgent caches)
  SELECT @can_see_all_running_jobs = ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0)
  IF (@can_see_all_running_jobs = 0)
  BEGIN
    SELECT @can_see_all_running_jobs = ISNULL(IS_MEMBER(N'SQLAgentReaderRole'), 0)
  END
  SELECT @job_owner = SUSER_SNAME()

  IF ((@@microsoftversion / 0x01000000) >= 8) -- SQL Server 8.0 or greater
    INSERT INTO @xp_results
    EXECUTE master.dbo.xp_sqlagent_enum_jobs @can_see_all_running_jobs, @job_owner, @job_id
  ELSE
    INSERT INTO @xp_results
    EXECUTE master.dbo.xp_sqlagent_enum_jobs @can_see_all_running_jobs, @job_owner

  INSERT INTO @job_execution_state
  SELECT xpr.job_id,
         xpr.last_run_date,
         xpr.last_run_time,
         xpr.job_state,
         sjs.step_id,
         sjs.step_name,
         xpr.current_retry_attempt,
         xpr.next_run_date,
         xpr.next_run_time,
         xpr.next_run_schedule_id
  FROM @xp_results                          xpr
       LEFT OUTER JOIN msdb.dbo.sysjobsteps sjs ON ((xpr.job_id = sjs.job_id) AND (xpr.current_step = sjs.step_id)),
       msdb.dbo.sysjobs_view                sjv
  WHERE (sjv.job_id = xpr.job_id)

  -- Step 3: Filter on everything but dates and job_type
  IF ((@subsystem        IS NULL) AND
      (@owner_login_name IS NULL) AND
      (@enabled          IS NULL) AND
      (@category_id      IS NULL) AND
      (@execution_status IS NULL) AND
      (@description      IS NULL) AND
      (@job_id           IS NULL))
  BEGIN
    -- Optimize for the frequently used case...
    INSERT INTO @filtered_jobs
    SELECT sjv.job_id,
           sjv.date_created,
           sjv.date_modified,
           ISNULL(jes.execution_job_status, 4), -- Will be NULL if the job is non-local or is not in @job_execution_state (NOTE: 4 = STATE_IDLE)
           CASE ISNULL(jes.execution_step_id, 0)
             WHEN 0 THEN NULL                   -- Will be NULL if the job is non-local or is not in @job_execution_state
             ELSE CONVERT(NVARCHAR, jes.execution_step_id) + N' (' + jes.execution_step_name + N')'
           END,
           jes.execution_retry_attempt,         -- Will be NULL if the job is non-local or is not in @job_execution_state
           0,  -- last_run_date placeholder    (we'll fix it up in step 3.3)
           0,  -- last_run_time placeholder    (we'll fix it up in step 3.3)
           5,  -- last_run_outcome placeholder (we'll fix it up in step 3.3 - NOTE: We use 5 just in case there are no jobservers for the job)
           jes.next_run_date,                   -- Will be NULL if the job is non-local or is not in @job_execution_state
           jes.next_run_time,                   -- Will be NULL if the job is non-local or is not in @job_execution_state
           jes.next_run_schedule_id,            -- Will be NULL if the job is non-local or is not in @job_execution_state
           0   -- type placeholder             (we'll fix it up in step 3.4)
    FROM msdb.dbo.sysjobs_view                sjv
         LEFT OUTER JOIN @job_execution_state jes ON (sjv.job_id = jes.job_id)
    WHERE ((@schedule_id IS NULL)
      OR   (EXISTS(SELECT * 
                 FROM sysjobschedules as js
                 WHERE (sjv.job_id = js.job_id)
                   AND (js.schedule_id = @schedule_id))))
  END
  ELSE
  BEGIN
    INSERT INTO @filtered_jobs
    SELECT DISTINCT
           sjv.job_id,
           sjv.date_created,
           sjv.date_modified,
           ISNULL(jes.execution_job_status, 4), -- Will be NULL if the job is non-local or is not in @job_execution_state (NOTE: 4 = STATE_IDLE)
           CASE ISNULL(jes.execution_step_id, 0)
             WHEN 0 THEN NULL                   -- Will be NULL if the job is non-local or is not in @job_execution_state
             ELSE CONVERT(NVARCHAR, jes.execution_step_id) + N' (' + jes.execution_step_name + N')'
           END,
           jes.execution_retry_attempt,         -- Will be NULL if the job is non-local or is not in @job_execution_state
           0,  -- last_run_date placeholder    (we'll fix it up in step 3.3)
           0,  -- last_run_time placeholder    (we'll fix it up in step 3.3)
           5,  -- last_run_outcome placeholder (we'll fix it up in step 3.3 - NOTE: We use 5 just in case there are no jobservers for the job)
           jes.next_run_date,                   -- Will be NULL if the job is non-local or is not in @job_execution_state
           jes.next_run_time,                   -- Will be NULL if the job is non-local or is not in @job_execution_state
           jes.next_run_schedule_id,            -- Will be NULL if the job is non-local or is not in @job_execution_state
           0   -- type placeholder             (we'll fix it up in step 3.4)
    FROM msdb.dbo.sysjobs_view                sjv
         LEFT OUTER JOIN @job_execution_state jes ON (sjv.job_id = jes.job_id)
         LEFT OUTER JOIN msdb.dbo.sysjobsteps sjs ON (sjv.job_id = sjs.job_id)
    WHERE ((@subsystem        IS NULL) OR (sjs.subsystem            = @subsystem))
      AND ((@owner_login_name IS NULL) 
          OR (sjv.owner_sid            = dbo.SQLAGENT_SUSER_SID(@owner_login_name)))--force case insensitive comparation for NT users
      AND ((@enabled          IS NULL) OR (sjv.enabled              = @enabled))
      AND ((@category_id      IS NULL) OR (sjv.category_id          = @category_id))
      AND ((@execution_status IS NULL) OR ((@execution_status > 0) AND (jes.execution_job_status = @execution_status))
                                       OR ((@execution_status = 0) AND (jes.execution_job_status <> 4) AND (jes.execution_job_status <> 5)))
      AND ((@description      IS NULL) OR (sjv.description       LIKE @description))
      AND ((@job_id           IS NULL) OR (sjv.job_id               = @job_id))
      AND ((@schedule_id IS NULL)
        OR (EXISTS(SELECT * 
                 FROM sysjobschedules as js
                 WHERE (sjv.job_id = js.job_id)
                   AND (js.schedule_id = @schedule_id))))
  END

  -- Step 3.1: Change the execution status of non-local jobs from 'Idle' to 'Unknown'
  UPDATE @filtered_jobs
  SET current_execution_status = NULL
  WHERE (current_execution_status = 4)
    AND (job_id IN (SELECT job_id
                    FROM msdb.dbo.sysjobservers
                    WHERE (server_id <> 0)))

  -- Step 3.2: Check that if the user asked to see idle jobs that we still have some.
  --           If we don't have any then the query should return no rows.
  IF (@execution_status = 4) AND
     (NOT EXISTS (SELECT *
                  FROM @filtered_jobs
                  WHERE (current_execution_status = 4)))
  BEGIN
    DELETE FROM @filtered_jobs
  END

  -- Step 3.3: Populate the last run date/time/outcome [this is a little tricky since for
  --           multi-server jobs there are multiple last run details in sysjobservers, so
  --           we simply choose the most recent].
  IF (EXISTS (SELECT *
              FROM msdb.dbo.systargetservers))
  BEGIN
    UPDATE @filtered_jobs
    SET last_run_date = sjs.last_run_date,
        last_run_time = sjs.last_run_time,
        last_run_outcome = sjs.last_run_outcome
    FROM @filtered_jobs         fj,
         msdb.dbo.sysjobservers sjs
    WHERE (CONVERT(FLOAT, sjs.last_run_date) * 1000000) + sjs.last_run_time =
           (SELECT MAX((CONVERT(FLOAT, last_run_date) * 1000000) + last_run_time)
            FROM msdb.dbo.sysjobservers
            WHERE (job_id = sjs.job_id))
      AND (fj.job_id = sjs.job_id)
  END
  ELSE
  BEGIN
    UPDATE @filtered_jobs
    SET last_run_date = sjs.last_run_date,
        last_run_time = sjs.last_run_time,
        last_run_outcome = sjs.last_run_outcome
    FROM @filtered_jobs         fj,
         msdb.dbo.sysjobservers sjs
    WHERE (fj.job_id = sjs.job_id)
  END

  -- Step 3.4 : Set the type of the job to local (1) or multi-server (2)
  --            NOTE: If the job has no jobservers then it wil have a type of 0 meaning
  --                  unknown.  This is marginally inconsistent with the behaviour of
  --                  defaulting the category of a new job to [Uncategorized (Local)], but
  --                  prevents incompletely defined jobs from erroneously showing up as valid
  --                  local jobs.
  UPDATE @filtered_jobs
  SET type = 1 -- LOCAL
  FROM @filtered_jobs         fj,
       msdb.dbo.sysjobservers sjs
  WHERE (fj.job_id = sjs.job_id)
    AND (server_id = 0)
  UPDATE @filtered_jobs
  SET type = 2 -- MULTI-SERVER
  FROM @filtered_jobs         fj,
       msdb.dbo.sysjobservers sjs
  WHERE (fj.job_id = sjs.job_id)
    AND (server_id <> 0)

  -- Step 4: Filter on job_type
  IF (@job_type IS NOT NULL)
  BEGIN
    IF (UPPER(@job_type collate SQL_Latin1_General_CP1_CS_AS) = 'LOCAL')
      DELETE FROM @filtered_jobs
      WHERE (type <> 1) -- IE. Delete all the non-local jobs
    IF (UPPER(@job_type collate SQL_Latin1_General_CP1_CS_AS) = 'MULTI-SERVER')
      DELETE FROM @filtered_jobs
      WHERE (type <> 2) -- IE. Delete all the non-multi-server jobs
  END

  -- Step 5: Filter on dates
  IF (@date_comparator IS NOT NULL)
  BEGIN
    IF (@date_created IS NOT NULL)
    BEGIN
      IF (@date_comparator = '=')
        DELETE FROM @filtered_jobs WHERE (date_created <> @date_created)
      IF (@date_comparator = '>')
        DELETE FROM @filtered_jobs WHERE (date_created <= @date_created)
      IF (@date_comparator = '<')
        DELETE FROM @filtered_jobs WHERE (date_created >= @date_created)
    END
    IF (@date_last_modified IS NOT NULL)
    BEGIN
      IF (@date_comparator = '=')
        DELETE FROM @filtered_jobs WHERE (date_last_modified <> @date_last_modified)
      IF (@date_comparator = '>')
        DELETE FROM @filtered_jobs WHERE (date_last_modified <= @date_last_modified)
      IF (@date_comparator = '<')
        DELETE FROM @filtered_jobs WHERE (date_last_modified >= @date_last_modified)
    END
  END

  -- Return the result set (NOTE: No filtering occurs here)
  SELECT sjv.job_id,
         originating_server, 
         sjv.name,
         sjv.enabled,
         sjv.description,
         sjv.start_step_id,
         category = ISNULL(sc.name, FORMATMESSAGE(14205)),
         owner = dbo.SQLAGENT_SUSER_SNAME(sjv.owner_sid),
         sjv.notify_level_eventlog,
         sjv.notify_level_email,
         sjv.notify_level_netsend,
         sjv.notify_level_page,
         notify_email_operator   = ISNULL(so1.name, FORMATMESSAGE(14205)),
         notify_netsend_operator = ISNULL(so2.name, FORMATMESSAGE(14205)),
         notify_page_operator    = ISNULL(so3.name, FORMATMESSAGE(14205)),
         sjv.delete_level,
         sjv.date_created,
         sjv.date_modified,
         sjv.version_number,
         fj.last_run_date,
         fj.last_run_time,
         fj.last_run_outcome,
         next_run_date = ISNULL(fj.next_run_date, 0),                                 -- This column will be NULL if the job is non-local
         next_run_time = ISNULL(fj.next_run_time, 0),                                 -- This column will be NULL if the job is non-local
         next_run_schedule_id = ISNULL(fj.next_run_schedule_id, 0),                   -- This column will be NULL if the job is non-local
         current_execution_status = ISNULL(fj.current_execution_status, 0),           -- This column will be NULL if the job is non-local
         current_execution_step = ISNULL(fj.current_execution_step, N'0 ' + FORMATMESSAGE(14205)), -- This column will be NULL if the job is non-local
         current_retry_attempt = ISNULL(fj.current_retry_attempt, 0),                 -- This column will be NULL if the job is non-local
         has_step = (SELECT COUNT(*)
                     FROM msdb.dbo.sysjobsteps sjst
                     WHERE (sjst.job_id = sjv.job_id)),
         has_schedule = (SELECT COUNT(*)
                         FROM msdb.dbo.sysjobschedules sjsch
                         WHERE (sjsch.job_id = sjv.job_id)),
         has_target = (SELECT COUNT(*)
                       FROM msdb.dbo.sysjobservers sjs
                       WHERE (sjs.job_id = sjv.job_id)),
         type = fj.type
  FROM @filtered_jobs                         fj
       LEFT OUTER JOIN msdb.dbo.sysjobs_view  sjv ON (fj.job_id = sjv.job_id)
       LEFT OUTER JOIN msdb.dbo.sysoperators  so1 ON (sjv.notify_email_operator_id = so1.id)
       LEFT OUTER JOIN msdb.dbo.sysoperators  so2 ON (sjv.notify_netsend_operator_id = so2.id)
       LEFT OUTER JOIN msdb.dbo.sysoperators  so3 ON (sjv.notify_page_operator_id = so3.id)
       LEFT OUTER JOIN msdb.dbo.syscategories sc  ON (sjv.category_id = sc.category_id)
  ORDER BY sjv.job_id

END
go


/****************************************************************
* Add new or updated SPs above. A new SP should be added to the list
* of procedures in #sp_table below, and if necessary put into the
* appropriate ODB bucket.

* IMPORTANT NOTE - We CANNOT sign any new procedures added since Yukon
* RTM! This is because a cross-SKU upgrade, say from SQL Express SP2
* to SQL Developer, first installs the RTM version of the new SKU. The
* RTM version of the instmsdb.sql script tries to drop the Agent
* signing certificate in order to recreate it, but since it doesn't
* know about new signed SPs added since RTM it fails the drop. As a
* result that breaks cross-SKU upgrades, so we can never sign new
* procedures with our Agent signing certificate. To handle this, add
* your new procedure to the #sp_table with a value of zero for the
* 'sign' column.
* *****************************************************************/


/**************************************************************/
/* Sign agent sps and add them to Off By Default component    */
/*                                                            */
/* Also sign SPs for other components located in MSDB         */
/**************************************************************/
PRINT 'Signing sps ...'
-- Create certificate to sign Agent sps
--
if exists (select * from sys.certificates where name = '##MS_AgentSigningCertificate##')
   drop certificate [##MS_AgentSigningCertificate##]

declare @certError int
dbcc traceon(4606,-1)
create certificate [##MS_AgentSigningCertificate##] 
   encryption by password = 'Yukon90_'
   with subject = 'MS_AgentSigningCertificate'
select @certError = @@error
dbcc traceoff(4606,-1)

IF (@certError <> 0)
BEGIN
    select 'Objects still signed by ##MS_AgentSigningCertificate##' = object_name(crypts.major_id) 
    from sys.crypt_properties crypts, sys.certificates certs
    where crypts.thumbprint = certs.thumbprint
    and crypts.class = 1
    and certs.name = '##MS_AgentSigningCertificate##'

    RAISERROR('Cannot create ##MS_AgentSigningCertificate## in msdb. SYSDBUPG.SQL terminating.', 20, 127) WITH LOG
END
go

-- List all of the stored procedures we need to sign with the Agent
-- signing certicate. Optionally if your SP belongs in the 'Agent XPs'
-- Off-by-default component, then specify 1 for the 'obdComponent'
-- column; If it belongs in 'DBMail XPs', specify 2.

create table #sp_table (name sysname, sign int, obdComponent int, bNewProc int)
go
insert into #sp_table values(N'sp_sqlagent_is_srvrolemember',              1, 0, 0)
insert into #sp_table values(N'sp_verify_category_identifiers',            1, 0, 0)
insert into #sp_table values(N'sp_verify_proxy_identifiers',               1, 0, 0)
insert into #sp_table values(N'sp_verify_credential_identifiers',          1, 0, 0)
insert into #sp_table values(N'sp_verify_subsystem_identifiers',           1, 0, 0)
insert into #sp_table values(N'sp_verify_login_identifiers',               1, 0, 0)
insert into #sp_table values(N'sp_verify_proxy',                           1, 0, 0)
insert into #sp_table values(N'sp_add_proxy',                              1, 0, 0)
insert into #sp_table values(N'sp_delete_proxy',                           1, 0, 0)
insert into #sp_table values(N'sp_update_proxy',                           1, 0, 0)
insert into #sp_table values(N'sp_sqlagent_is_member',                     1, 0, 0)
insert into #sp_table values(N'sp_verify_proxy_permissions',               1, 0, 0)
insert into #sp_table values(N'sp_help_proxy',                             1, 0, 0)
insert into #sp_table values(N'sp_grant_proxy_to_subsystem',               1, 0, 0)
insert into #sp_table values(N'sp_grant_login_to_proxy',                   1, 0, 0)
insert into #sp_table values(N'sp_revoke_login_from_proxy',                1, 0, 0)
insert into #sp_table values(N'sp_revoke_proxy_from_subsystem',            1, 0, 0)
insert into #sp_table values(N'sp_enum_proxy_for_subsystem',               1, 0, 0)
insert into #sp_table values(N'sp_enum_login_for_proxy',                   1, 0, 0)
insert into #sp_table values(N'sp_sqlagent_get_startup_info',              1, 1, 0)
insert into #sp_table values(N'sp_sqlagent_has_server_access',             1, 1, 0)
insert into #sp_table values(N'sp_sem_add_message',                        1, 0, 0)
insert into #sp_table values(N'sp_sem_drop_message',                       1, 0, 0)
insert into #sp_table values(N'sp_get_message_description',                1, 0, 0)
insert into #sp_table values(N'sp_sqlagent_get_perf_counters',             1, 0, 0)
insert into #sp_table values(N'sp_sqlagent_notify',                        1, 1, 0)
insert into #sp_table values(N'sp_is_sqlagent_starting',                   1, 1, 0)
insert into #sp_table values(N'sp_verify_job_identifiers',                 1, 0, 0)
insert into #sp_table values(N'sp_verify_schedule_identifiers',            1, 0, 0)
insert into #sp_table values(N'sp_verify_jobproc_caller',                  1, 0, 0)
insert into #sp_table values(N'sp_downloaded_row_limiter',                 1, 1, 0)
insert into #sp_table values(N'sp_post_msx_operation',                     1, 1, 0)
insert into #sp_table values(N'sp_verify_performance_condition',           1, 0, 0)
insert into #sp_table values(N'sp_verify_job_date',                        1, 0, 0)
insert into #sp_table values(N'sp_verify_job_time',                        1, 0, 0)
insert into #sp_table values(N'sp_verify_alert',                           1, 1, 0)
insert into #sp_table values(N'sp_update_alert',                           1, 0, 0)
insert into #sp_table values(N'sp_delete_job_references',                  1, 0, 0)
insert into #sp_table values(N'sp_delete_all_msx_jobs',                    1, 0, 0)
insert into #sp_table values(N'sp_generate_target_server_job_assignment_sql', 1, 0, 0)
insert into #sp_table values(N'sp_generate_server_description',            1, 1, 0)
insert into #sp_table values(N'sp_msx_set_account',                        1, 1, 0)
insert into #sp_table values(N'sp_msx_get_account',                        1, 1, 0)
insert into #sp_table values(N'sp_delete_operator',                        1, 0, 0)
insert into #sp_table values(N'sp_msx_defect',                             1, 1, 0)
insert into #sp_table values(N'sp_msx_enlist',                             1, 1, 0)
insert into #sp_table values(N'sp_delete_targetserver',                    1, 0, 0)
insert into #sp_table values(N'sp_get_sqlagent_properties',                1, 1, 0)
insert into #sp_table values(N'sp_set_sqlagent_properties',                1, 1, 0)
insert into #sp_table values(N'sp_add_targetservergroup',                  1, 0, 0)
insert into #sp_table values(N'sp_update_targetservergroup',               1, 0, 0)
insert into #sp_table values(N'sp_delete_targetservergroup',               1, 0, 0)
insert into #sp_table values(N'sp_help_targetservergroup',                 1, 0, 0)
insert into #sp_table values(N'sp_add_targetsvrgrp_member',                1, 0, 0)
insert into #sp_table values(N'sp_delete_targetsvrgrp_member',             1, 0, 0)
insert into #sp_table values(N'sp_verify_category',                        1, 0, 0)
insert into #sp_table values(N'sp_add_category',                           1, 0, 0)
insert into #sp_table values(N'sp_update_category',                        1, 0, 0)
insert into #sp_table values(N'sp_delete_category',                        1, 0, 0)
insert into #sp_table values(N'sp_help_category',                          1, 0, 0)
insert into #sp_table values(N'sp_help_targetserver',                      1, 0, 0)
insert into #sp_table values(N'sp_resync_targetserver',                    1, 0, 0)
insert into #sp_table values(N'sp_purge_jobhistory',                       1, 0, 0)
insert into #sp_table values(N'sp_help_jobhistory',                        1, 0, 0)
insert into #sp_table values(N'sp_help_jobhistory_full',                   0, 0, 0) -- added after Yukon RTM, so cannot sign
insert into #sp_table values(N'sp_help_jobhistory_summary',                0, 0, 0) -- added after Yukon RTM, so cannot sign
insert into #sp_table values(N'sp_help_jobhistory_sem',                    0, 0, 0) -- added after Yukon RTM, so cannot sign
insert into #sp_table values(N'sp_add_jobserver',                          1, 0, 0)
insert into #sp_table values(N'sp_delete_jobserver',                       1, 0, 0)
insert into #sp_table values(N'sp_help_jobserver',                         1, 0, 0)
insert into #sp_table values(N'sp_help_downloadlist',                      1, 0, 0)
insert into #sp_table values(N'sp_enum_sqlagent_subsystems',               1, 0, 0)
insert into #sp_table values(N'sp_enum_sqlagent_subsystems_internal',      1, 0, 0)
insert into #sp_table values(N'sp_verify_subsystem',                       1, 1, 0)
insert into #sp_table values(N'sp_verify_subsystems',                      1, 0, 0)
insert into #sp_table values(N'sp_verify_schedule',                        1, 0, 0)
insert into #sp_table values(N'sp_add_schedule',                           1, 0, 0)
insert into #sp_table values(N'sp_attach_schedule',                        1, 0, 0)
insert into #sp_table values(N'sp_detach_schedule',                        1, 0, 0)
insert into #sp_table values(N'sp_update_schedule',                        1, 0, 0)
insert into #sp_table values(N'sp_delete_schedule',                        1, 0, 0)
insert into #sp_table values(N'sp_get_jobstep_db_username',                1, 0, 0)
insert into #sp_table values(N'sp_verify_jobstep',                         1, 0, 0)
insert into #sp_table values(N'sp_add_jobstep_internal',                   1, 0, 0)
insert into #sp_table values(N'sp_add_jobstep',                            1, 0, 0)
insert into #sp_table values(N'sp_update_jobstep',                         1, 0, 0)
insert into #sp_table values(N'sp_delete_jobstep',                         1, 0, 0)
insert into #sp_table values(N'sp_help_jobstep',                           1, 0, 0)
insert into #sp_table values(N'sp_write_sysjobstep_log',                   1, 0, 0)
insert into #sp_table values(N'sp_help_jobsteplog',                        1, 0, 0)
insert into #sp_table values(N'sp_delete_jobsteplog',                      1, 0, 0)
insert into #sp_table values(N'sp_get_schedule_description',               1, 1, 0)
insert into #sp_table values(N'sp_add_jobschedule',                        1, 0, 0)
insert into #sp_table values(N'sp_update_replication_job_parameter',       1, 0, 0)
insert into #sp_table values(N'sp_update_jobschedule',                     1, 0, 0)
insert into #sp_table values(N'sp_delete_jobschedule',                     1, 0, 0)
insert into #sp_table values(N'sp_help_schedule',                          1, 0, 0)
insert into #sp_table values(N'sp_help_jobschedule',                       1, 0, 0)
insert into #sp_table values(N'sp_verify_job',                             1, 1, 0)
insert into #sp_table values(N'sp_add_job',                                1, 0, 0)
insert into #sp_table values(N'sp_update_job',                             1, 0, 0)
insert into #sp_table values(N'sp_delete_job',                             1, 0, 0)
insert into #sp_table values(N'sp_get_composite_job_info',                 1, 1, 0)
insert into #sp_table values(N'sp_help_job',                               1, 0, 0)
insert into #sp_table values(N'sp_help_jobcount ',                         1, 0, 0)
insert into #sp_table values(N'sp_help_jobs_in_schedule',                  1, 0, 0)
insert into #sp_table values(N'sp_manage_jobs_by_login',                   1, 0, 0)
insert into #sp_table values(N'sp_apply_job_to_targets',                   1, 0, 0)
insert into #sp_table values(N'sp_remove_job_from_targets',                1, 0, 0)
insert into #sp_table values(N'sp_get_job_alerts',                         1, 0, 0)
insert into #sp_table values(N'sp_convert_jobid_to_char',                  1, 0, 0)
insert into #sp_table values(N'sp_start_job',                              1, 0, 0)
insert into #sp_table values(N'sp_stop_job',                               1, 0, 0)
insert into #sp_table values(N'sp_cycle_agent_errorlog',                   1, 0, 0)
insert into #sp_table values(N'sp_get_chunked_jobstep_params',             1, 0, 0)
insert into #sp_table values(N'sp_check_for_owned_jobs',                   1, 0, 0)
insert into #sp_table values(N'sp_check_for_owned_jobsteps',               1, 0, 0)
insert into #sp_table values(N'sp_sqlagent_refresh_job',                   1, 0, 0)
insert into #sp_table values(N'sp_jobhistory_row_limiter',                 1, 1, 0)
insert into #sp_table values(N'sp_sqlagent_log_jobhistory',                1, 0, 0)
insert into #sp_table values(N'sp_sqlagent_check_msx_version',             1, 0, 0)
insert into #sp_table values(N'sp_sqlagent_probe_msx',                     1, 0, 0)
insert into #sp_table values(N'sp_set_local_time',                         1, 1, 0)
insert into #sp_table values(N'sp_multi_server_job_summary',               1, 0, 0)
insert into #sp_table values(N'sp_target_server_summary',                  1, 0, 0)
insert into #sp_table values(N'sp_uniquetaskname',                         1, 0, 0)
insert into #sp_table values(N'sp_addtask',                                1, 0, 0)
insert into #sp_table values(N'sp_droptask',                               1, 0, 0)
insert into #sp_table values(N'sp_add_alert_internal',                     1, 0, 0)
insert into #sp_table values(N'sp_add_alert',                              1, 0, 0)
insert into #sp_table values(N'sp_delete_alert',                           1, 0, 0)
insert into #sp_table values(N'sp_help_alert',                             1, 0, 0)
insert into #sp_table values(N'sp_verify_operator',                        1, 0, 0)
insert into #sp_table values(N'sp_add_operator',                           1, 0, 0)
insert into #sp_table values(N'sp_update_operator',                        1, 1, 0)
insert into #sp_table values(N'sp_help_operator',                          1, 0, 0)
insert into #sp_table values(N'sp_help_operator_jobs',                     1, 0, 0)
insert into #sp_table values(N'sp_verify_operator_identifiers',            1, 0, 0)
insert into #sp_table values(N'sp_notify_operator',                        1, 0, 0)
insert into #sp_table values(N'sp_verify_notification',                    1, 0, 0)
insert into #sp_table values(N'sp_add_notification',                       1, 0, 0)
insert into #sp_table values(N'sp_update_notification',                    1, 0, 0)
insert into #sp_table values(N'sp_delete_notification',                    1, 0, 0)
insert into #sp_table values(N'sp_help_notification',                      1, 0, 0)
insert into #sp_table values(N'sp_help_jobactivity',                       1, 0, 0)
insert into #sp_table values(N'sp_enlist_tsx',                             1, 1, 0)
insert into #sp_table values(N'trig_targetserver_insert',                  1, 0, 0)

-- Database Mail configuration procs
insert into #sp_table values(N'sysmail_verify_accountparams_sp',           1, 0, 0)
insert into #sp_table values(N'sysmail_verify_principal_sp',               1, 0, 0)
insert into #sp_table values(N'sysmail_verify_profile_sp',                 1, 0, 0)
insert into #sp_table values(N'sysmail_verify_account_sp',                 1, 0, 0)
insert into #sp_table values(N'sysmail_add_profile_sp',                    1, 0, 0)
insert into #sp_table values(N'sysmail_update_profile_sp',                 1, 0, 0)
insert into #sp_table values(N'sysmail_delete_profile_sp',                 1, 0, 0)
insert into #sp_table values(N'sysmail_help_profile_sp',                   1, 0, 0)
insert into #sp_table values(N'sysmail_create_user_credential_sp',         1, 0, 0)
insert into #sp_table values(N'sysmail_alter_user_credential_sp',          1, 0, 0)
insert into #sp_table values(N'sysmail_drop_user_credential_sp',           1, 0, 0)
insert into #sp_table values(N'sysmail_add_account_sp',                    1, 0, 0)
insert into #sp_table values(N'sysmail_update_account_sp',                 1, 0, 0)
insert into #sp_table values(N'sysmail_delete_account_sp',                 1, 0, 0)
insert into #sp_table values(N'sysmail_help_account_sp',                   1, 0, 0)
insert into #sp_table values(N'sysmail_help_admin_account_sp',             1, 0, 0)
insert into #sp_table values(N'sysmail_add_profileaccount_sp',             1, 0, 0)
insert into #sp_table values(N'sysmail_update_profileaccount_sp',          1, 0, 0)
insert into #sp_table values(N'sysmail_delete_profileaccount_sp',          1, 0, 0)
insert into #sp_table values(N'sysmail_help_profileaccount_sp',            1, 0, 0)
insert into #sp_table values(N'sysmail_configure_sp',                      1, 0, 0)
insert into #sp_table values(N'sysmail_help_configure_sp',                 1, 0, 0)
insert into #sp_table values(N'sysmail_help_configure_value_sp',           1, 0, 0)
insert into #sp_table values(N'sysmail_add_principalprofile_sp',           1, 0, 0)
insert into #sp_table values(N'sysmail_update_principalprofile_sp',        1, 0, 0)
insert into #sp_table values(N'sysmail_delete_principalprofile_sp',        1, 0, 0)
insert into #sp_table values(N'sysmail_help_principalprofile_sp',          1, 0, 0)

-- Database Mail: mail host database specific procs
insert into #sp_table values(N'sysmail_start_sp',                          1, 2, 0)
insert into #sp_table values(N'sysmail_stop_sp',                           1, 2, 0)
insert into #sp_table values(N'sysmail_logmailevent_sp',                   1, 0, 0)
insert into #sp_table values(N'sp_SendMailMessage',                        1, 0, 0)
insert into #sp_table values(N'sp_isprohibited',                           1, 0, 0)
insert into #sp_table values(N'sp_SendMailQueues',                         1, 0, 0)
insert into #sp_table values(N'sp_ProcessResponse',                        1, 0, 0)
insert into #sp_table values(N'sp_MailItemResultSets',                     1, 0, 0)
insert into #sp_table values(N'sp_process_DialogTimer',                    1, 0, 0)
insert into #sp_table values(N'sp_readrequest',                            1, 0, 0)
insert into #sp_table values(N'sp_GetAttachmentData',                      1, 0, 0)
insert into #sp_table values(N'sp_RunMailQuery',                           1, 0, 0)
insert into #sp_table values(N'sysmail_help_queue_sp',                     1, 0, 0)
insert into #sp_table values(N'sysmail_help_status_sp',                    1, 2, 0)
insert into #sp_table values(N'sysmail_delete_mailitems_sp',               1, 0, 0)
insert into #sp_table values(N'sysmail_delete_log_sp',                     1, 0, 0)
insert into #sp_table values(N'sp_send_dbmail',                            1, 2, 0)
insert into #sp_table values(N'sp_validate_user',                          1, 2, 0)
insert into #sp_table values(N'sp_ExternalMailQueueListener',              1, 0, 0)
insert into #sp_table values(N'sp_sysmail_activate',                       1, 0, 0)

-- Maintenance Plans
insert into #sp_table values(N'sp_maintplan_delete_log',                   1, 0, 0)
insert into #sp_table values(N'sp_maintplan_delete_subplan',               1, 0, 0)
insert into #sp_table values(N'sp_maintplan_open_logentry',                1, 0, 0)
insert into #sp_table values(N'sp_maintplan_close_logentry',               1, 0, 0)
insert into #sp_table values(N'sp_maintplan_update_log',                   1, 0, 0)
insert into #sp_table values(N'sp_maintplan_update_subplan',               1, 0, 0)
insert into #sp_table values(N'sp_maintplan_delete_plan',                  1, 0, 0)
insert into #sp_table values(N'sp_maintplan_start',                        1, 0, 0)
insert into #sp_table values(N'sp_clear_dbmaintplan_by_db',                1, 0, 0)
insert into #sp_table values(N'sp_add_maintenance_plan',                   1, 0, 0)
insert into #sp_table values(N'sp_delete_maintenance_plan',                1, 0, 0)
insert into #sp_table values(N'sp_add_maintenance_plan_db',                1, 0, 0)
insert into #sp_table values(N'sp_delete_maintenance_plan_db',             1, 0, 0)
insert into #sp_table values(N'sp_add_maintenance_plan_job',               1, 1, 0)
insert into #sp_table values(N'sp_delete_maintenance_plan_job',            1, 0, 0)
insert into #sp_table values(N'sp_help_maintenance_plan',                  1, 0, 0)
insert into #sp_table values(N'sp_maintplan_update_subplan_tsx',           0, 0, 0) -- added after Yukon RTM, so cannot sign
insert into #sp_table values(N'sp_maintplan_subplans_by_job',              0, 0, 0) -- added after Yukon RTM, so cannot sign

-- Log Shipping
insert into #sp_table values(N'sp_add_log_shipping_monitor_jobs',          1, 0, 0)
insert into #sp_table values(N'sp_add_log_shipping_primary',               1, 0, 0)
insert into #sp_table values(N'sp_add_log_shipping_secondary',             1, 0, 0)
insert into #sp_table values(N'sp_delete_log_shipping_monitor_jobs',       1, 0, 0)
insert into #sp_table values(N'sp_delete_log_shipping_primary',            1, 0, 0)
insert into #sp_table values(N'sp_delete_log_shipping_secondary ',         1, 0, 0)
insert into #sp_table values(N'sp_log_shipping_in_sync',                   1, 0, 0)
insert into #sp_table values(N'sp_log_shipping_get_date_from_file ',       1, 0, 0)
insert into #sp_table values(N'sp_get_log_shipping_monitor_info',          1, 0, 0)
insert into #sp_table values(N'sp_update_log_shipping_monitor_info',       1, 0, 0)
insert into #sp_table values(N'sp_delete_log_shipping_monitor_info',       1, 0, 0)
insert into #sp_table values(N'sp_remove_log_shipping_monitor_account',    1, 0, 0)
insert into #sp_table values(N'sp_log_shipping_monitor_backup',            1, 0, 0)
insert into #sp_table values(N'sp_log_shipping_monitor_restore',           1, 0, 0)
insert into #sp_table values(N'sp_change_monitor_role',                    1, 0, 0)
insert into #sp_table values(N'sp_create_log_shipping_monitor_account',    1, 0, 0)

-- DTS
insert into #sp_table values(N'sp_get_dtsversion',                         1, 0, 0)
insert into #sp_table values(N'sp_make_dtspackagename',                    1, 0, 0)
insert into #sp_table values(N'sp_add_dtspackage',                         1, 0, 0)
insert into #sp_table values(N'sp_drop_dtspackage',                        1, 0, 0)
insert into #sp_table values(N'sp_reassign_dtspackageowner',               1, 0, 0)
insert into #sp_table values(N'sp_get_dtspackage',                         1, 0, 0)
insert into #sp_table values(N'sp_reassign_dtspackagecategory',            1, 0, 0)
insert into #sp_table values(N'sp_enum_dtspackages',                       1, 0, 0)
insert into #sp_table values(N'sp_add_dtscategory',                        1, 0, 0)
insert into #sp_table values(N'sp_drop_dtscategory',                       1, 0, 0)
insert into #sp_table values(N'sp_modify_dtscategory',                     1, 0, 0)
insert into #sp_table values(N'sp_enum_dtscategories',                     1, 0, 0)
insert into #sp_table values(N'sp_log_dtspackage_begin',                   1, 0, 0)
insert into #sp_table values(N'sp_log_dtspackage_end',                     1, 0, 0)
insert into #sp_table values(N'sp_log_dtsstep_begin',                      1, 0, 0)
insert into #sp_table values(N'sp_log_dtsstep_end',                        1, 0, 0)
insert into #sp_table values(N'sp_log_dtstask',                            1, 0, 0)
insert into #sp_table values(N'sp_enum_dtspackagelog',                     1, 0, 0)
insert into #sp_table values(N'sp_enum_dtssteplog',                        1, 0, 0)
insert into #sp_table values(N'sp_enum_dtstasklog',                        1, 0, 0)
insert into #sp_table values(N'sp_dump_dtslog_all',                        1, 0, 0)
insert into #sp_table values(N'sp_dump_dtspackagelog',                     1, 0, 0)
insert into #sp_table values(N'sp_dump_dtssteplog',                        1, 0, 0)
insert into #sp_table values(N'sp_dump_dtstasklog',                        1, 0, 0)
insert into #sp_table values(N'sp_dts_addlogentry',                        1, 0, 0)
insert into #sp_table values(N'sp_dts_listpackages',                       1, 0, 0)
insert into #sp_table values(N'sp_dts_listfolders',                        1, 0, 0)
insert into #sp_table values(N'sp_dts_deletepackage',                      1, 0, 0)
insert into #sp_table values(N'sp_dts_deletefolder',                       1, 0, 0)
insert into #sp_table values(N'sp_dts_getpackage',                         1, 0, 0)
insert into #sp_table values(N'sp_dts_getfolder',                          1, 0, 0)
insert into #sp_table values(N'sp_dts_putpackage',                         1, 0, 0)
insert into #sp_table values(N'sp_dts_addfolder',                          1, 0, 0)
insert into #sp_table values(N'sp_dts_renamefolder',                       1, 0, 0)
insert into #sp_table values(N'sp_dts_setpackageroles',                    1, 0, 0)
insert into #sp_table values(N'sp_dts_getpackageroles',                    1, 0, 0)
go

/**************************************************************/
/* Mark system objects                                        */
/**************************************************************/
declare  @start datetime
        ,@name  sysname
select @start = start from #sysdbupg
declare newsysobjs cursor for select name from sys.objects where schema_id = 1 and create_date >= @start
open newsysobjs
fetch next from newsysobjs into @name
while @@fetch_status = 0
begin
   Exec sp_MS_marksystemobject @name
   update #sp_table set bNewProc = 1 where name = @name
   fetch next from newsysobjs into @name
end
deallocate newsysobjs
drop table #sysdbupg
go


BEGIN TRANSACTION
declare @sp sysname
declare @exec_str nvarchar(1024)
declare @sign int
declare @obdComponent int
declare @bNewProc int
declare ms_crs_sps cursor global for select name, sign, obdComponent, bNewProc from #sp_table 
open ms_crs_sps
fetch next from ms_crs_sps into @sp, @sign, @obdComponent, @bNewProc
while @@fetch_status = 0
begin
   if exists(select * from sys.objects where name = @sp)
   begin
      print 'processing sp: ' + @sp

      if (@sign = 1)
      begin
         set @exec_str = N'add signature to ' + @sp + N' by certificate [##MS_AgentSigningCertificate##] with password = ''Yukon90_'''
         Execute(@exec_str)
         if (@@error <> 0)
         begin
            declare @err_str nvarchar(1024)
            set @err_str = 'Cannot sign stored procedure ' + @sp + '. Terminating.'
            RAISERROR(@err_str, 20, 127) WITH LOG
            ROLLBACK TRANSACTION
            return
         end
      end

      -- If there is a new procedure that goes in a component, put it there
      if (@obdComponent > 0 and @bNewProc > 0)
      begin
         if (@obdComponent = 1) -- SQLAgent
             set @exec_str = N'exec sp_AddFunctionalUnitToComponent N''Agent XPs'', N''' + @sp + N''''

         else if (@obdComponent = 2) -- DbMail
             set @exec_str = N'exec sp_AddFunctionalUnitToComponent N''Database Mail XPs'', N''' + @sp + N''''
 
         Execute(@exec_str)
         if (@@error <> 0)
         begin
            RAISERROR('Cannot add stored procedure to component. SYSDBUPG.SQL terminating.', 20, 127) WITH LOG
            ROLLBACK TRANSACTION
            return
         end
      end

   end
   fetch next from ms_crs_sps into @sp, @sign, @obdComponent, @bNewProc
end
close ms_crs_sps
deallocate ms_crs_sps
COMMIT TRANSACTION
go
drop table #sp_table
go

-- drop certificate private key
alter certificate [##MS_AgentSigningCertificate##] remove private key

IF (@@error <> 0)
   RAISERROR('Cannot alter ##MS_AgentSigningCertificate## in msdb. SYSDBUPG.SQL terminating.', 20, 127) WITH LOG
go


--create a temporary database in order to get the path to the 'Data' folder
--because upon upgrade existing database are in temporary folder
IF (EXISTS (SELECT name
                FROM master.dbo.sysdatabases
                WHERE (name = N'temp_MS_AgentSigningCertificate_database')))
BEGIN
  DROP DATABASE temp_MS_AgentSigningCertificate_database
END
go
CREATE DATABASE temp_MS_AgentSigningCertificate_database
go
-- export certificate to master
-- use current directory to persist the file
--
DECLARE @certificate_name NVARCHAR(520)
DECLARE @certificate_nameQuoted NVARCHAR(1042) -- twice the length + 2, in case every character was a quote

SELECT @certificate_name = SUBSTRING(filename, 1, CHARINDEX(N'temp_MS_AgentSigningCertificate_database.mdf', filename) - 1) + 
                           CONVERT(NVARCHAR(520), NEWID()) + N'.cer'
  FROM master.dbo.sysaltfiles
  WHERE (name = N'temp_MS_AgentSigningCertificate_database')
  
-- Handle single quotes in the directory name (can't use QUOTENAME in case name over 128 chars)
SELECT @certificate_nameQuoted = '''' + REPLACE(@certificate_name, '''', '''''') + ''''

EXECUTE(N'backup certificate [##MS_AgentSigningCertificate##] to file = ' + @certificate_nameQuoted)

IF (@@error <> 0)
   RAISERROR('Cannot backup ##MS_AgentSigningCertificate##. SYSDBUPG.SQL terminating.', 20, 127) WITH LOG

use master

if exists (select * from sys.database_principals where name = '##MS_AgentSigningCertificate##')
   drop user [##MS_AgentSigningCertificate##]

if exists (select * from sys.server_principals where name = '##MS_AgentSigningCertificate##')
   drop login [##MS_AgentSigningCertificate##]

if exists (select * from sys.certificates where name = '##MS_AgentSigningCertificate##')
   drop certificate [##MS_AgentSigningCertificate##]

execute(N'create certificate [##MS_AgentSigningCertificate##] from file = ' + @certificate_nameQuoted)
IF (@@error <> 0)
   RAISERROR('Cannot create ##MS_AgentSigningCertificate## certificate in master. SYSDBUPG.SQL terminating.', 20, 127) WITH LOG

-- create login
--
create login [##MS_AgentSigningCertificate##] from certificate [##MS_AgentSigningCertificate##]
IF (@@error <> 0)
   RAISERROR('Cannot create ##MS_AgentSigningCertificate## login. SYSDBUPG.SQL terminating.', 20, 127) WITH LOG

-- create certificate based user for execution granting
--
create user [##MS_AgentSigningCertificate##] for certificate [##MS_AgentSigningCertificate##]
IF (@@error <> 0)
   RAISERROR('Cannot create ##MS_AgentSigningCertificate## user. SYSDBUPG.SQL terminating.', 20, 127) WITH LOG

-- enable certificate for OBD
--
exec sys.sp_SetOBDCertificate N'##MS_AgentSigningCertificate##',N'ON'

grant execute to [##MS_AgentSigningCertificate##]

use msdb
go

-- Refresh Subsystem list now that sp_verify_subsystems has been
-- signed and our special ##MS_AgentSigningCertificate## exists. We
-- have to do this after the certificate exists because
-- sp_verify_subsystems makes calls to xp_regread and xp_fileexist,
-- which are extended procedures that, if disabled, are only available
-- to procedures signed by special certificates like ours. If SSIS is
-- not installed but SSMS is installed this call will make SSIS
-- subsystem available to Agent jobs.
exec sp_verify_subsystems 1
go

-- drop temporary database
IF (EXISTS (SELECT name
                FROM master.dbo.sysdatabases
                WHERE (name = N'temp_MS_AgentSigningCertificate_database')))
BEGIN
  DROP DATABASE temp_MS_AgentSigningCertificate_database
END
go

PRINT 'Succesfully signed sps'
--
-- End of signing sps
go

USE msdb
go

/**************************************************************/
/* Drop auxilary procedure to enable OBD component          */
/**************************************************************/
DROP PROCEDURE #sp_enable_component 
go
DROP PROCEDURE #sp_restore_component_state 
go

--------------------------------------------------------------------------------
--	REPL_MASTER.SQL
--------------------------------------------------------------------------------
/****************************************************************************
//		Copyright (c) Microsoft Corporation.
//
// @File: provisionexpress.sql
// @Owner: kaloianm
//
// Purpose:
//		Script to grant login privileges to BUILTIN\Users for EXPRESS/RANU.
//
// Notes:
//		run by the SQL script upgrade framework.
//
//
// @EndHeader@
*****************************************************************************/

use master
go

PRINT '----------------------------------------'
PRINT 'Starting provisionexpress.sql ...';
PRINT '----------------------------------------'
go

-- Obtain the name of BUILTIN\Users from a well-known SID 
-- (as it might be on an non-ENU installation)
--
DECLARE @builtinUsersSID AS NVARCHAR(85)
DECLARE @builtinUsersSIDBinary AS VARBINARY(85)
DECLARE @builtinUsers AS NVARCHAR(85);
SET @builtinUsersSID = N'S-1-5-32-545';
-- convert textual SID to name
--
SELECT @builtinUsersSIDBinary = sid_binary(@builtinUsersSID);
SELECT @builtinUsers = suser_sname(@builtinUsersSIDBinary);

IF (@builtinUsers IS NOT NULL)
BEGIN
	IF (NOT EXISTS (SELECT * FROM sys.server_principals WHERE NAME = @builtinUsers))
	BEGIN
		PRINT 'Adding login for ' + @builtinUsers
		DECLARE @strExec NVARCHAR (MAX)
		SET @strExec = 'CREATE LOGIN ' + QUOTENAME(@builtinUsers) + ' FROM WINDOWS'
		EXEC (@strExec)
	END
END
go

/**********************************************************************/
/* PRE_MSDB.SQL                                                       */
/*                                                                    */
/* Prepares MSDB for upgrade.                                         */
/*                                                                    */
/*
** Copyright (c) Microsoft Corporation
** All Rights Reserved.
*/
/**********************************************************************/

PRINT '----------------------------------'
PRINT 'Starting execution of PRE_MSDB.SQL'
PRINT '----------------------------------'

use msdb
go


-- If the user sets implicit_transactions on, some specprocs and DBCC commands will fail
-- Need to save the state and disable implicit_transactions
DECLARE @implicit_transactions_flag INT
DECLARE @is_implicit_transactions_set BIT

SELECT @is_implicit_transactions_set = CONVERT(BIT, value)
FROM fn_listextendedproperty(N'IMPLICIT_TRANSACTIONS', default, default, default, default, default, default);

IF (@is_implicit_transactions_set IS NOT NULL)
BEGIN
	EXEC sp_dropextendedproperty N'IMPLICIT_TRANSACTIONS'
END

SET @implicit_transactions_flag = 2
SET @is_implicit_transactions_set = @@options & @implicit_transactions_flag

EXEC sp_addextendedproperty 
@name = N'IMPLICIT_TRANSACTIONS', @value = @is_implicit_transactions_set

SET IMPLICIT_TRANSACTIONS OFF

--set compatibily level to 100
EXEC sp_dbcmptlevel @dbname = 'msdb',  @new_cmptlevel = '100'

GO
/**********************************************************************/
/* PRE_SQLAGENT100.SQL                                                */
/*                                                                    */
/* Upgrades 7.x, 8.x and 9.0 to 10.0 and drops all obsolete 8.x       */
/*                                                                    */
/*
** Copyright (c) Microsoft Corporation
** All Rights Reserved.
*/
/**********************************************************************/

PRINT '-----------------------------------------'
PRINT 'Starting execution of PRE_SQLAGENT100.SQL'
PRINT '-----------------------------------------'

use msdb
go
-- Check that we're in msdb
IF (DB_NAME() <> N'msdb')
  RAISERROR('A problem was encountered accessing msdb. upgrade script terminating.', 20, 127) WITH LOG
go

CHECKPOINT
go

--set compatibility level for msdb database to 150
ALTER DATABASE msdb
  SET COMPATIBILITY_LEVEL = 150
GO

-- Allow updates to system catalogs so that we can fully manipulate our system objects
EXECUTE master.dbo.sp_configure N'allow updates', 1
go
RECONFIGURE WITH OVERRIDE
go

/**************************************************************/
/* Record time of start of creates                            */
/**************************************************************/
SELECT start = getdate() INTO #InstMsdb
go

--preserve existing object permnission during upgrade
--create perms table
IF (NOT OBJECT_ID(N'dbo.upgrade_perms', 'U') IS NULL)
BEGIN
  DROP TABLE dbo.upgrade_perms
END
-- upgrade_perms is filled with current permission of objects in MSDB
-- the structure of table is:
-- state_desc = GRANT|DENY
-- permission_name = SELECT|EXECUTE|UPDATE ...
-- object_name = grantor name
-- grantee_name = grantee name

CREATE TABLE dbo.upgrade_perms(state_desc nvarchar(60), permission_name sysname, object_name sysname, grantee_name sysname)
CREATE INDEX indnc ON dbo.upgrade_perms(object_name)
DECLARE @state_desc			  nvarchar(60)
DECLARE @permission_name	sysname
DECLARE @object_name		  sysname
DECLARE @grantee_name		  sysname 

DECLARE perms_cursor CURSOR LOCAL FOR 
	SELECT state_desc, permission_name, OBJECT_NAME(major_id), USER_NAME(grantee_principal_id) from msdb.sys.database_permissions 
  WHERE state_desc IS NOT NULL AND 
  permission_name IS NOT NULL AND 
  OBJECT_NAME(major_id) IS NOT NULL AND 
  USER_NAME(grantee_principal_id) IS NOT NULL 	

OPEN perms_cursor
   FETCH NEXT FROM perms_cursor INTO @state_desc, @permission_name, @object_name, @grantee_name
   WHILE (@@fetch_status = 0)
   BEGIN
      INSERT dbo.upgrade_perms(state_desc, permission_name, object_name, grantee_name)
      VALUES(@state_desc, @permission_name, @object_name, @grantee_name) 
      FETCH NEXT FROM perms_cursor INTO @state_desc, @permission_name, @object_name, @grantee_name
   END
DEALLOCATE perms_cursor
go


------------------------VIEWS UPGRADE---------------------------------------


------------------------TABLE UPGRADE---------------------------------------
--create an populate sysoriginatingservers
use msdb
go

IF (NOT EXISTS (SELECT *   --just a safe belt, this table shouldn't be in 8.x
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysoriginatingservers')
              AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysoriginatingservers...'
  CREATE TABLE dbo.sysoriginatingservers
  (
    -- There is only a single MSX server record in this table (originating_server_id = 1)
    -- 0 is generated by sysoriginatingservers_view and indicates the local server
    originating_server_id	INT 		CONSTRAINT CK_originating_server_id_MustBe_1 CHECK (originating_server_id = 1) 
									    DEFAULT (1) UNIQUE CLUSTERED,				
    originating_server    sysname		NOT NULL UNIQUE NONCLUSTERED,
    --Mark this record as a MSX server entry
    master_server         bit			CONSTRAINT CK_master_server_MustBe_1 CHECK (master_server = 1) 
									    DEFAULT (1)	
  )
END
go

IF (NOT EXISTS (SELECT t.name FROM msdb.sys.all_columns c JOIN msdb.sys.all_objects t 
                ON c.object_id = t.object_id 
                WHERE c.name = 'originating_server_id' and t.name = 'sysjobs' and t.type = 'U'))
BEGIN                
  PRINT ''
  PRINT 'Adding column originating_server_id to table sysjobs...'
  --add new column 9.0 originating_server_id
  ALTER TABLE sysjobs WITH NOCHECK 
  ADD originating_server_id INT NULL
END
go

DECLARE @MSXServerName        sysname
DECLARE @LocalServerName      sysname
DECLARE @UpdateOrgServerTSQL  nvarchar(MAX)

SELECT @LocalServerName = UPPER(CONVERT(sysname, SERVERPROPERTY('servername')))

EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                        N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                        N'MSXServerName',
                                        @MSXServerName OUTPUT,
                                        N'no_output'

SELECT @MSXServerName = LTRIM(RTRIM(UPPER(@MSXServerName)))
IF (@MSXServerName = '') SELECT @MSXServerName = NULL

IF (@MSXServerName IS NOT NULL)
BEGIN
  IF (NOT EXISTS( SELECT * FROM dbo.sysoriginatingservers 
                  WHERE originating_server_id = 1 AND originating_server = @MSXServerName
                  AND master_server = 1))
    BEGIN
      PRINT ''
      PRINT 'Populate table sysoriginatingservers...'
      INSERT INTO sysoriginatingservers( originating_server_id, originating_server,  master_server) 
      VALUES(1, @MSXServerName, 1)
    END  
END

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysjobs')
              AND (type = 'U')))
BEGIN
  IF (EXISTS (SELECT t.name FROM msdb.sys.all_columns c JOIN msdb.sys.all_objects t 
                  ON c.object_id = t.object_id 
                  WHERE c.name = 'originating_server' and t.name = 'sysjobs' and t.type = 'U'))
  BEGIN                

    PRINT ''
    PRINT 'Populate new column originating_server_id of table sysjobs...'
    --set this column based on the value of 8.0 only column originating_server 
	  --if MSX server is NULL we come up with server name that cannot exit, a generated GUID
    SELECT @UpdateOrgServerTSQL = 
    '
    UPDATE sysjobs SET originating_server_id = 
    CASE UPPER(originating_server) 
      WHEN ''' + @LocalServerName + ''' THEN 0 --local_server_id 
      WHEN ''' + ISNULL(@MSXServerName, CONVERT(sysname, NEWID()))   + ''' THEN 1 --msx_server_id 
      ELSE 0 --7.0 (local) or bad data 
    END
    '
    EXECUTE( @UpdateOrgServerTSQL)
    
    PRINT ''
    PRINT 'Drop column originating_server of table sysjobs...'
    --drop 8.0 column originating_server
    DROP INDEX sysjobs.nc2
    ALTER TABLE sysjobs DROP COLUMN originating_server
  END
END  
go

--normalize 8.0 sysjobschedules into 9.0 sysschedules and sysjobschedules
IF NOT EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysschedules')
              AND (type = 'U'))
BEGIN
--create first sysschedules table  
	PRINT ''
	PRINT 'Creating table sysschedules...'

	CREATE TABLE dbo.sysschedules
	(
	schedule_id            INT IDENTITY     PRIMARY KEY CLUSTERED,
	schedule_uid           UNIQUEIDENTIFIER NOT NULL,
	originating_server_id  INT              NOT NULL, 
	name                   sysname          NOT NULL,
	owner_sid				       varbinary(85)	  NOT NULL, 
	enabled                INT              NOT NULL,
	freq_type              INT              NOT NULL,
	freq_interval          INT              NOT NULL,
	freq_subday_type       INT              NOT NULL,
	freq_subday_interval   INT              NOT NULL,
	freq_relative_interval INT              NOT NULL,
	freq_recurrence_factor INT              NOT NULL,
	active_start_date      INT              NOT NULL,
	active_end_date        INT              NOT NULL,
	active_start_time      INT              NOT NULL,
	active_end_time        INT              NOT NULL,
	date_created           DATETIME         NOT NULL  DEFAULT (GETDATE()),
	date_modified          DATETIME         NOT NULL  DEFAULT (GETDATE()),
	version_number         INT              NOT NULL  DEFAULT (1)
	)
-- CREATE UNIQUE CLUSTERED INDEX clust ON sysschedules(job_id, name)
-- CREATE UNIQUE NONCLUSTERED INDEX nc1 ON sysschedules(schedule_id)
END
go
--a system object cannot be renamed, turn off marking system object trace to alloe renaming of temp_sysjobschedules
dbcc traceoff(1717, -1) 

go
-- create temp cross 9.0 table temp_sysjobschedules
IF EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'temp_sysjobschedules')
              AND (type = 'U'))
BEGIN
  DROP TABLE dbo.temp_sysjobschedules
END
go

PRINT ''
PRINT 'Creating table temp_sysjobschedules'	
CREATE TABLE dbo.temp_sysjobschedules
	(
	schedule_id		         INT					    REFERENCES dbo.sysschedules(schedule_id),
	job_id                 UNIQUEIDENTIFIER REFERENCES dbo.sysjobs(job_id),
	next_run_date          INT              NOT NULL   DEFAULT 0,
	next_run_time          INT              NOT NULL   DEFAULT 0
	)
go

DECLARE @dynamicSQL nvarchar(4000)
IF (EXISTS (SELECT t.name FROM msdb.sys.all_columns c JOIN msdb.sys.all_objects t 
                ON c.object_id = t.object_id 
                WHERE c.name = 'name' and t.name = 'sysjobschedules' and t.type = 'U'))
BEGIN	
  PRINT ''
  PRINT 'Moving schedule data ...'	
	SET IDENTITY_INSERT dbo.sysschedules ON
  SELECT @dynamicSQL =
  '
	INSERT INTO dbo.sysschedules
	(
		schedule_id            ,
		schedule_uid           ,
		originating_server_id  ,
		name                   ,
		owner_sid				       ,
		enabled                ,
		freq_type              ,
		freq_interval          ,
		freq_subday_type       ,
		freq_subday_interval   ,
		freq_relative_interval ,
		freq_recurrence_factor ,
		active_start_date      ,
		active_end_date        ,
		active_start_time      ,
		active_end_time        ,
		date_created           
	)
	SELECT
		js.schedule_id            ,
		NEWID()                   ,
		0                         , --local server. TO DO make sure local server = 0
		js.name                   ,      
		j.owner_sid               ,      
		js.enabled                   ,
		js.freq_type              ,
		js.freq_interval          ,
		js.freq_subday_type       ,
		js.freq_subday_interval   ,
		js.freq_relative_interval ,
		js.freq_recurrence_factor ,
		js.active_start_date      ,
		js.active_end_date        ,
		js.active_start_time      ,
		js.active_end_time        ,
		js.date_created           
	FROM
  dbo.sysjobs j JOIN dbo.sysjobschedules js ON j.job_id = js.job_id
	  
	INSERT INTO dbo.temp_sysjobschedules
	(
		schedule_id		        ,
		job_id                ,
		next_run_date         ,
		next_run_time         
	)
	SELECT
		js.schedule_id          ,
		js.job_id               ,
		js.next_run_date        ,
		js.next_run_time          
	FROM dbo.sysjobs j JOIN dbo.sysjobschedules js ON j.job_id = js.job_id
'
  EXECUTE (@dynamicSQL)

	SET IDENTITY_INSERT dbo.sysschedules OFF

	IF (EXISTS (SELECT *
				FROM msdb.dbo.syscolumns
				WHERE (id = OBJECT_ID(N'sysjobschedules'))
				AND (name = N'date_created')
				AND (cdefault <> 0)))
	  EXECUTE sp_unbindefault N'sysjobschedules.date_created'

	DROP TABLE dbo.sysjobschedules
	EXECUTE sp_rename 'temp_sysjobschedules', 'sysjobschedules'
	EXECUTE (N'CREATE UNIQUE CLUSTERED INDEX clust ON dbo.sysjobschedules(job_id, schedule_id)')
	PRINT ''
	PRINT 'Updating schedules done'
END	
go

--just a safe belt
IF EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'temp_sysjobschedules')
              AND (type = 'U'))
BEGIN
  DROP TABLE dbo.temp_sysjobschedules
END
go

--alter only if command column is not already nvarchar(max)
IF (NOT EXISTS (SELECT c.* FROM msdb.sys.all_columns c JOIN msdb.sys.all_objects t 
                ON c.object_id = t.object_id 
                WHERE (c.name = 'proxy_id' OR c.name = 'step_uid') AND t.name = 'sysjobsteps' AND t.type = 'U'))
BEGIN
  PRINT ''
  PRINT 'Adding proxy_id, step_uid  columns to sysjobsteps table'  
  ALTER TABLE sysjobsteps ADD
        proxy_id INT NULL,
        step_uid UNIQUEIDENTIFIER NULL
END
go

--rename DTS subsystem to SSIS
IF (OBJECT_ID('dbo.sysjobsteps', 'U') IS NOT NULL)
BEGIN
    UPDATE dbo.sysjobsteps SET subsystem = N'SSIS' WHERE UPPER(subsystem collate SQL_Latin1_General_CP1_CS_AS) = N'DTS'
END
go

--to be safer populate sysjobsteps with guids, otherwise step table logs are not possible
EXECUTE (N'UPDATE sysjobsteps SET step_uid = NEWID() WHERE step_uid IS NULL')
go

--if there is no index for step_uid, create it, so step table logs can reference it
IF NOT EXISTS (SELECT name FROM sys.indexes WHERE name = N'nc2' and object_id = object_id(N'[dbo].[sysjobsteps]') )
BEGIN
  EXECUTE (N'CREATE UNIQUE NONCLUSTERED INDEX nc2 ON sysjobsteps(step_uid)')
END
go


--alter sysdownloadlist table
PRINT ''
PRINT 'Alter table sysdownloadlist...'
ALTER TABLE sysdownloadlist ALTER COLUMN source_server sysname
ALTER TABLE sysdownloadlist ALTER COLUMN target_server sysname
go

--alter sysjobhistory  table
PRINT ''
PRINT 'Alter table sysjobhistory...'
ALTER TABLE sysjobhistory ALTER COLUMN server sysname
go

IF EXISTS (SELECT * FROM msdb.dbo.sysobjects WHERE name = N'sysjobhistory' and type = 'U')
BEGIN
  UPDATE  msdb.dbo.sysjobhistory
  SET     server = CONVERT(sysname, SERVERPROPERTY('servername'))
  WHERE   UPPER(server) = '(LOCAL)'
END
go

--alter systargetservers table
PRINT ''
PRINT 'Alter table systargetservers...'
ALTER TABLE systargetservers  ALTER COLUMN server_name sysname
go

--alter sysjobsteps table
PRINT ''
PRINT 'Alter table sysjobsteps...'
ALTER TABLE sysjobsteps  ALTER COLUMN additional_parameters NVARCHAR(max)
go

--drop syssubsystems table if it exists( to support update from IDW(n) to RTM)
--it will recreated later with the updated schema
IF (OBJECT_ID('dbo.syssubsystems', 'U') IS NOT NULL)
BEGIN
  DROP TABLE dbo.syssubsystems
END

--drop column logshipping from sysdbmaintplans table
--alter only if command column is not already nvarchar(max)
IF (EXISTS (SELECT c.* FROM msdb.sys.all_columns c JOIN msdb.sys.all_objects t 
                ON c.object_id = t.object_id 
                WHERE (c.name = 'logshipping') AND t.name = 'sysdbmaintplans' AND t.type = 'U'))
BEGIN
  ALTER TABLE sysdbmaintplans DROP COLUMN logshipping
END
go

--make sure 
--it will recreated later with the updated schema
IF (OBJECT_ID('dbo.sysjobstepslogs', 'U') IS NOT NULL)
BEGIN
  ALTER TABLE dbo.sysjobstepslogs  ALTER COLUMN log_size bigint
END

-- sysproxylogin upgrade
-- sysproxylogin upgrade
BEGIN TRY
	IF EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='principal_id' and id =
			(SELECT OBJECT_ID(N'dbo.sysproxylogin', 'U')))
	BEGIN
		-- convert data from principal_id to sid
		exec sp_executesql N'
		DECLARE @sid varbinary(85)
		DECLARE @principal_id int 

		DECLARE principal_sid_cursor CURSOR LOCAL 
		FOR
		SELECT distinct principal_id
		FROM dbo.sysproxylogin WHERE (sid IS NULL) AND (flags = 2)

		OPEN principal_sid_cursor 
		FETCH NEXT FROM principal_sid_cursor INTO @principal_id
		WHILE (@@fetch_status = 0)
		BEGIN
		    SELECT @sid=sid FROM msdb.sys.database_principals WHERE principal_id=@principal_id

			IF @sid IS NOT NULL -- principal_id is valid
			BEGIN
				UPDATE dbo.sysproxylogin
				SET sid = @sid
				WHERE principal_id = @principal_id
			END
			ELSE
			BEGIN
				DELETE FROM dbo.sysproxylogin
				WHERE principal_id = @principal_id
			END
			FETCH NEXT FROM principal_sid_cursor INTO @principal_id
		END
		CLOSE principal_sid_cursor 
		DEALLOCATE principal_sid_cursor
		'

		-- remove obsolete column
		DROP INDEX sysproxylogin.clust
		ALTER TABLE dbo.sysproxylogin DROP COLUMN principal_id
        CREATE UNIQUE CLUSTERED    INDEX clust ON sysproxylogin(proxy_id, sid, flags)
	END
END TRY
BEGIN CATCH
	print 'There was a problem upgrading sysproxylogin table.'
	print 'Unable to map existing principal_id values to sid column'
	print 'Error State: ' + convert(varchar(10),ERROR_STATE() )
END CATCH

GO

/**************************************************************/
/* Mark system objects                                        */
/**************************************************************/
declare  @start datetime
		,@name  sysname
select @start = start from #InstMsdb
declare newsysobjs cursor for select name from sys.objects where schema_id = 1 and create_date >= @start
open newsysobjs
fetch next from newsysobjs into @name
while @@fetch_status = 0
begin
	Exec sp_MS_marksystemobject @name
	fetch next from newsysobjs into @name
end
deallocate newsysobjs
drop table #InstMsdb
go

EXECUTE master.dbo.sp_configure N'allow updates', 0
go
RECONFIGURE WITH OVERRIDE
go


PRINT ''
PRINT '-----------------------------------------'
PRINT 'Execution of PRE_SQLAGENT100.SQL complete'
PRINT '-----------------------------------------'
go

/**************************************************************/
/* DMF Pre-upgrade steps                                      */
/**************************************************************/

PRINT 'DMF pre-upgrade steps...'
--
-- >>> CTP5 -> CTP6 Upgrade
--
-- The check is based on presense of ObjectSet tables
--  We also check if principal DMF objects is there
--  if it's not we either upgrade from Yukon
--  or there is nothing to upgrade anyway
IF (OBJECT_ID('[dbo].[syspolicy_policies_internal]', 'U') IS NOT NULL)
	AND (OBJECT_ID('[dbo].[syspolicy_object_sets_internal]', 'U') IS NULL)
BEGIN
	BEGIN TRY
		-- Open transaction - we don't want to delete tables if we don't have a copy of data
		BEGIN TRANSACTION
		-- Create upgrade marker
		CREATE TABLE dbo.dmf_upgrade (id int)
	
		-- STORE DATA
		SELECT * INTO msdb.dbo.tmp_syspolicy_target_sets_internal FROM syspolicy_target_sets_internal 
		SELECT * INTO msdb.dbo.tmp_syspolicy_target_set_levels_internal FROM syspolicy_target_set_levels_internal 
		SELECT * INTO msdb.dbo.tmp_syspolicy_policies_internal FROM syspolicy_policies_internal 
		SELECT * INTO msdb.dbo.tmp_syspolicy_system_health_state_internal FROM syspolicy_system_health_state_internal 
		SELECT * INTO msdb.dbo.tmp_syspolicy_policy_execution_history_internal FROM syspolicy_policy_execution_history_internal 
		SELECT * INTO msdb.dbo.tmp_syspolicy_policy_execution_history_details_internal FROM syspolicy_policy_execution_history_details_internal 
	
		-- Delete policies to invoke the trigger that removes dependent objects (jobs, if any).
		DELETE [dbo].[syspolicy_policies_internal]
		-- T-SQL Policy jobs are now gone, we must nullify job_id to not violate the foreign key constraint
		UPDATE msdb.dbo.tmp_syspolicy_policies_internal SET job_id = NULL
		
		-- DROP TABLES (WITH DEPENDENCIES)	
		IF (OBJECT_ID('[dbo].[syspolicy_target_set_levels_internal]', 'U') IS NOT NULL) DROP TABLE [dbo].[syspolicy_target_set_levels_internal]
		IF (OBJECT_ID('[dbo].[syspolicy_target_sets_internal]', 'U') IS NOT NULL) DROP TABLE [dbo].[syspolicy_target_sets_internal]
		IF (OBJECT_ID('[dbo].[syspolicy_policy_execution_history_details_internal]', 'U') IS NOT NULL) DROP TABLE [dbo].[syspolicy_policy_execution_history_details_internal]
		IF (OBJECT_ID('[dbo].[syspolicy_policy_execution_history_internal]', 'U') IS NOT NULL) DROP TABLE [dbo].[syspolicy_policy_execution_history_internal]
		IF (OBJECT_ID('[dbo].[syspolicy_system_health_state_internal]', 'U') IS NOT NULL) DROP TABLE [dbo].[syspolicy_system_health_state_internal]
		IF (OBJECT_ID('[dbo].[syspolicy_policies]', 'V') IS NOT NULL) DROP VIEW [dbo].[syspolicy_policies]
		IF (OBJECT_ID('[dbo].[syspolicy_policies_internal]', 'U') IS NOT NULL) DROP TABLE [dbo].[syspolicy_policies_internal]
	
		COMMIT TRANSACTION
	END TRY
	BEGIN CATCH
            IF (XACT_STATE() <> 0)
            BEGIN
                ROLLBACK TRANSACTION;
            END

            DECLARE @ErrorMessage   NVARCHAR(4000);
            DECLARE @ErrorSeverity  INT;
            DECLARE @ErrorState     INT;
            DECLARE @ErrorNumber    INT;
            DECLARE @ErrorLine      INT;
            DECLARE @ErrorProcedure NVARCHAR(200);
            SELECT @ErrorLine = ERROR_LINE(),
                   @ErrorSeverity = ERROR_SEVERITY(),
                   @ErrorState = ERROR_STATE(),
                   @ErrorNumber = ERROR_NUMBER(),
                   @ErrorMessage = ERROR_MESSAGE(),
                   @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
            PRINT 'ERROR: DMF CTP5 to CTP6 upgrade tasks failed' 
            RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
	END CATCH;
END
GO
--
-- <<< CTP5 - CTP6 Upgrade
--

--
-- >>> CTP6 - RTM Upgrade
--
IF (OBJECT_ID('[dbo].[syspolicy_policies_internal]', 'U') IS NOT NULL)
BEGIN
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policies_internal]') AND name='object_set_id' AND is_nullable=0)
		BEGIN
		ALTER TABLE [dbo].[syspolicy_policies_internal] ALTER COLUMN object_set_id int NULL
		END
END
GO
IF (OBJECT_ID('[dbo].[syspolicy_system_health_state_internal]', 'U') IS NOT NULL)
BEGIN
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_system_health_state_internal]') AND name='target_query_expression_with_id' AND is_nullable=1)
		BEGIN
		IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'IX_syspolicy_system_health_state_internal_target_query_expression_with_id')
			DROP INDEX IX_syspolicy_system_health_state_internal_target_query_expression_with_id ON [dbo].[syspolicy_system_health_state_internal]
		ALTER TABLE [dbo].[syspolicy_system_health_state_internal] ALTER COLUMN target_query_expression_with_id nvarchar(400) NOT NULL
		CREATE INDEX IX_syspolicy_system_health_state_internal_target_query_expression_with_id ON
			[dbo].[syspolicy_system_health_state_internal](target_query_expression_with_id)
		END
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_system_health_state_internal]') AND name='target_query_expression' AND is_nullable=1)
		BEGIN
		ALTER TABLE [dbo].[syspolicy_system_health_state_internal] ALTER COLUMN target_query_expression nvarchar(max) NOT NULL
		END
END
GO
IF (OBJECT_ID('[dbo].[syspolicy_policy_execution_history_internal]', 'U') IS NOT NULL)
BEGIN
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policy_execution_history_internal]') AND name='policy_id' AND is_nullable=1)
		  BEGIN
		  IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'IX_syspolicy_policy_execution_history_internal_end_date_policy_id')
	  		DROP INDEX IX_syspolicy_policy_execution_history_internal_end_date_policy_id ON [dbo].[syspolicy_policy_execution_history_internal]
		  IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'IX_syspolicy_policy_execution_history_internal_policy_id')
			DROP INDEX IX_syspolicy_policy_execution_history_internal_policy_id ON [dbo].[syspolicy_policy_execution_history_internal]

		  DECLARE @fk_name sysname 
		  SELECT @fk_name = k.name from sys.foreign_keys k
			join sys.foreign_key_columns kc on k.object_id = kc.constraint_object_id
			join sys.columns c on k.parent_object_id = c.object_id AND kc.parent_column_id = c.column_id
			where k.parent_object_id=OBJECT_ID('syspolicy_policy_execution_history_internal')
			and c.name = 'policy_id'	  
		  IF @fk_name IS NOT NULL
			BEGIN
			DECLARE @drop_cmd nvarchar(512)
			SELECT @drop_cmd = 'ALTER TABLE [dbo].[syspolicy_policy_execution_history_internal] DROP CONSTRAINT ' + QUOTENAME(@fk_name) 
			EXECUTE (@drop_cmd)
			END
	      END
END
GO
IF (OBJECT_ID('[dbo].[syspolicy_policy_execution_history_internal]', 'U') IS NOT NULL)
BEGIN
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policy_execution_history_internal]') AND name='policy_id' AND is_nullable=1)
		BEGIN
		ALTER TABLE [dbo].[syspolicy_policy_execution_history_internal] ALTER COLUMN policy_id int NOT NULL
		CREATE INDEX IX_syspolicy_policy_execution_history_internal_end_date_policy_id 
			ON [dbo].[syspolicy_policy_execution_history_internal](policy_id, end_date);
		CREATE INDEX IX_syspolicy_policy_execution_history_internal_policy_id 
			ON [dbo].[syspolicy_policy_execution_history_internal](policy_id);
		END
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policy_execution_history_internal]') AND name='start_date' AND is_nullable=1)
		BEGIN
		ALTER TABLE [dbo].[syspolicy_policy_execution_history_internal] ALTER COLUMN [start_date] datetime NOT NULL
		END
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policy_execution_history_internal]') AND name='result' AND is_nullable=1)
		BEGIN
		ALTER TABLE [dbo].[syspolicy_policy_execution_history_internal] ALTER COLUMN result bit NOT NULL
		END
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policy_execution_history_internal]') AND name='is_full_run' AND is_nullable=1)
		BEGIN
		ALTER TABLE [dbo].[syspolicy_policy_execution_history_internal] ALTER COLUMN is_full_run bit NOT NULL
		END
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policy_execution_history_internal]') AND name='exception_message' AND is_nullable=0)
		BEGIN
		ALTER TABLE [dbo].[syspolicy_policy_execution_history_internal] ALTER COLUMN exception_message nvarchar(max) NULL
		END
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policy_execution_history_internal]') AND name='exception' AND is_nullable=0)
		BEGIN
		ALTER TABLE [dbo].[syspolicy_policy_execution_history_internal] ALTER COLUMN exception nvarchar(max) NULL
		END
END
GO
IF (OBJECT_ID('[dbo].[syspolicy_policy_execution_history_internal]', 'U') IS NOT NULL)
BEGIN
	  IF NOT EXISTS (SELECT * from sys.foreign_keys k
			join sys.foreign_key_columns kc on k.object_id = kc.constraint_object_id
			join sys.columns c on k.parent_object_id = c.object_id AND kc.parent_column_id = c.column_id
			where k.parent_object_id=OBJECT_ID('syspolicy_policy_execution_history_internal')
			and c.name = 'policy_id')	  
	  BEGIN
	  ALTER TABLE [dbo].[syspolicy_policy_execution_history_internal] ADD FOREIGN KEY (policy_id) REFERENCES [syspolicy_policies_internal]
	  END
END
GO
IF (OBJECT_ID('[dbo].[syspolicy_policy_execution_history_details_internal]', 'U') IS NOT NULL)
BEGIN
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policy_execution_history_details_internal]') AND name='history_id' AND is_nullable=1)
		BEGIN
		ALTER TABLE [dbo].[syspolicy_policy_execution_history_details_internal] ALTER COLUMN history_id bigint NOT NULL
		END
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policy_execution_history_details_internal]') AND name='target_query_expression_with_id' AND is_nullable=1)
		BEGIN
		ALTER TABLE [dbo].[syspolicy_policy_execution_history_details_internal] ALTER COLUMN target_query_expression_with_id nvarchar(4000) NOT NULL
		END
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policy_execution_history_details_internal]') AND name='target_query_expression' AND is_nullable=1)
		BEGIN
		ALTER TABLE [dbo].[syspolicy_policy_execution_history_details_internal] ALTER COLUMN target_query_expression nvarchar(4000) NOT NULL
		END
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policy_execution_history_details_internal]') AND name='execution_date' AND is_nullable=1)
		BEGIN
		ALTER TABLE [dbo].[syspolicy_policy_execution_history_details_internal] ALTER COLUMN execution_date datetime NOT NULL
		END
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policy_execution_history_details_internal]') AND name='result' AND is_nullable=1)
		BEGIN
		IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'IX_syspolicy_policy_execution_history_details_internal_result')
		  DROP INDEX IX_syspolicy_policy_execution_history_details_internal_result ON [dbo].[syspolicy_policy_execution_history_details_internal] 
		ALTER TABLE [dbo].[syspolicy_policy_execution_history_details_internal] ALTER COLUMN result bit NOT NULL
		END
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policy_execution_history_details_internal]') AND name='result_detail' AND is_nullable=0)
		BEGIN
		ALTER TABLE [dbo].[syspolicy_policy_execution_history_details_internal] ALTER COLUMN result_detail nvarchar(max) NULL
		END
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policy_execution_history_details_internal]') AND name='exception_message' AND is_nullable=0)
		BEGIN
		ALTER TABLE [dbo].[syspolicy_policy_execution_history_details_internal] ALTER COLUMN exception_message nvarchar(max) NULL
		END
	  IF EXISTS (SELECT * FROM sys.columns WHERE object_id=OBJECT_ID('[dbo].[syspolicy_policy_execution_history_details_internal]') AND name='exception' AND is_nullable=0)
		BEGIN
		ALTER TABLE [dbo].[syspolicy_policy_execution_history_details_internal] ALTER COLUMN exception nvarchar(max) NULL
		END
END
GO
--
-- <<< CTP6 - RTM Upgrade
--

--
-- >>> Katmai RTM - KJ CTP2 Upgrade
--
-- If there is no 'is_system' column in the following tables, add it
--
-- Need to take care of Shiloh/Yukon upgrade - no DMF objects exist
-- Only check for policies, assuming we either have all DMF tables or none
-- If installation is corrupted (there is policies table, but no conditions table) upgrade fails
--
IF (OBJECT_ID('[dbo].[syspolicy_policies_internal]', 'U') IS NOT NULL)
BEGIN
	IF NOT EXISTS (SELECT * FROM sys.columns WHERE name = 'is_system' AND object_id = OBJECT_ID('[dbo].[syspolicy_policies_internal]', 'U'))
	  ALTER TABLE syspolicy_policies_internal ADD is_system bit NOT NULL DEFAULT (0)
	IF NOT EXISTS (SELECT * FROM sys.columns WHERE name = 'is_system' AND object_id = OBJECT_ID('[dbo].[syspolicy_conditions_internal]', 'U'))
	  ALTER TABLE syspolicy_conditions_internal ADD is_system bit NOT NULL DEFAULT (0)
	IF NOT EXISTS (SELECT * FROM sys.columns WHERE name = 'is_system' AND object_id = OBJECT_ID('[dbo].[syspolicy_object_sets_internal]', 'U'))
	  ALTER TABLE syspolicy_object_sets_internal ADD is_system bit NOT NULL DEFAULT (0)
END
GO

-- Fix indexes on syspolicy_policy_execution_history_details_internal
--
IF (OBJECT_ID('[dbo].[syspolicy_policies_internal]', 'U') IS NOT NULL)
BEGIN
	IF EXISTS (SELECT * FROM sys.indexes WHERE name = N'IX_syspolicy_policy_execution_history_details_internal_result')
	  DROP INDEX IX_syspolicy_policy_execution_history_details_internal_result ON [dbo].[syspolicy_policy_execution_history_details_internal] 
	  
	IF NOT EXISTS (SELECT * FROM sys.key_constraints WHERE name = N'PK_syspolicy_policy_execution_history_details_id')
	  BEGIN
	  DECLARE @ix_name sysname 
	  SELECT @ix_name = name FROM sys.key_constraints 
		WHERE parent_object_id = OBJECT_ID('[dbo].[syspolicy_policy_execution_history_details_internal]', 'U') AND type = 'PK' AND is_system_named = 1
	  IF @ix_name IS NOT NULL
		BEGIN
		DECLARE @drop_cmd nvarchar(512)
		SELECT @drop_cmd = 'ALTER TABLE [dbo].[syspolicy_policy_execution_history_details_internal] DROP CONSTRAINT ' + QUOTENAME(@ix_name) 
		EXECUTE (@drop_cmd)
		END
	  ALTER TABLE [dbo].[syspolicy_policy_execution_history_details_internal] 
		ADD CONSTRAINT [PK_syspolicy_policy_execution_history_details_id] PRIMARY KEY CLUSTERED(history_id, detail_id)
	  END
END
GO
--
-- <<< Katmai RTM - KJ CTP2 Upgrade
--


/**************************************************************/
/* End of DMF Pre-upgrade steps                               */
/**************************************************************/


/**********************************************************************/
/* DC Pre-upgrade steps                                               */
/**********************************************************************/
GO
PRINT 'DC pre-upgrade steps...';

-- Capture current state of DataCollector in temp table
-- and re-enable collector after script upgrade
-- #304027 - Data Collection is disabled when upgrading SQL Server 2008 
--           service pack upgrade or hotfix upgrade

PRINT 'Check if Data collector config table exists...'
IF (OBJECT_ID(N'[dbo].[syscollector_config_store_internal]', 'U') IS NOT NULL)
BEGIN

    IF (OBJECT_ID('tempdb..#data_collector_status', 'U') IS NOT NULL)
    BEGIN
        PRINT 'Dropping existing temp table tempdb..#data_collector_status...'
	DROP TABLE #data_collector_status
    END

    DECLARE @collector_enabled int;

    SELECT @collector_enabled = ISNULL(CONVERT(int, parameter_value),0)
    FROM [dbo].[syscollector_config_store_internal]
    WHERE parameter_name = 'CollectorEnabled'

    PRINT 'Data Collector state before upgrade: ' + CONVERT(varchar, @collector_enabled)

    SELECT @collector_enabled AS data_collector_old_status
    INTO #data_collector_status
END

-- Capture Collection set status before upgrade
PRINT 'pre_dc100::Check if syscollector_collection_sets_internal table exists...'
IF (OBJECT_ID(N'[dbo].[syscollector_collection_sets_internal]', 'U') IS NOT NULL)
BEGIN
    -- Capture Collection set status in temp table
    IF (OBJECT_ID('tempdb..#data_collector_collectionset_status', 'U') IS NOT NULL)
    BEGIN
        PRINT 'pre_dc100::Dropping existing temp table tempdb..#data_collector_collectionset_status...'
        DROP TABLE #data_collector_collectionset_status
    END

    PRINT 'pre_dc100::Capturing Collection set status in temp table...'
    SELECT collection_set_uid, name, is_running 
    INTO #data_collector_collectionset_status
    FROM [dbo].[syscollector_collection_sets_internal]
END
GO

--
-- CTP6->CTP6 Refresh
--

-- Drop the parent_log_id->log_id self reference
IF ((OBJECT_ID ('dbo.syscollector_execution_log_internal') IS NOT NULL) 
	AND (OBJECT_ID('dbo.FK_syscollector_execution_log_parent_log_id', 'F') IS NOT NULL))
BEGIN
    PRINT 'Dropping [FK_syscollector_execution_log_parent_log_id]';
    ALTER TABLE [dbo].[syscollector_execution_log_internal] DROP CONSTRAINT [FK_syscollector_execution_log_parent_log_id];
END

--
-- >>> CTP5 -> CTP6 Upgrade
--
-- Change int log_id columns to bigint 
IF (OBJECT_ID ('dbo.syscollector_execution_log_internal') IS NOT NULL) AND EXISTS (
    SELECT * 
    FROM sys.columns AS c
    INNER JOIN sys.types AS t ON c.system_type_id = t.system_type_id
    WHERE [object_id] = OBJECT_ID ('dbo.syscollector_execution_log_internal')
        AND c.name = 'log_id' AND t.name = 'int'
    )
BEGIN
    BEGIN TRY
        PRINT 'Starting log_id int -> bigint conversion'
        BEGIN TRANSACTION PreInstMsdb100_DCUpgrade
        -- Drop PK/FK constaints referencing log_id columns so we can change the data types of these columns
        PRINT 'Dropping [FK_syscollector_execution_stats_log_id]';
        ALTER TABLE [dbo].[syscollector_execution_stats_internal] DROP CONSTRAINT [FK_syscollector_execution_stats_log_id];
        PRINT 'Dropping [PK_syscollector_execution_stats]';
        ALTER TABLE [dbo].[syscollector_execution_stats_internal] DROP CONSTRAINT [PK_syscollector_execution_stats]; 
        PRINT 'Dropping [PK_syscollector_execution_log]';
        ALTER TABLE [dbo].[syscollector_execution_log_internal] DROP CONSTRAINT [PK_syscollector_execution_log];
        
        -- Truncate the DC log table to avoid causing unnecessary delays during CTP upgrade
        PRINT 'Truncating [syscollector_execution_log_internal]...';
        TRUNCATE TABLE [dbo].[syscollector_execution_log_internal];
        -- log_id values stored in syscollector_execution_stats will no longer be valid after we have truncated the log table
        PRINT 'Truncating [syscollector_execution_stats_internal]...';
        TRUNCATE TABLE [dbo].[syscollector_execution_stats_internal];
        
        -- Change the data type of all log_id columns
        PRINT 'Changing log_id column datatypes...';
        PRINT '   syscollector_execution_stats_internal.log_id';
        ALTER TABLE [dbo].[syscollector_execution_stats_internal] ALTER COLUMN [log_id] bigint NOT NULL;
        PRINT '   syscollector_execution_log_internal.log_id';
        ALTER TABLE [dbo].[syscollector_execution_log_internal] ALTER COLUMN [log_id] bigint NOT NULL; 
        PRINT '   syscollector_execution_log_internal.parent_log_id';
        ALTER TABLE [dbo].[syscollector_execution_log_internal] ALTER COLUMN [parent_log_id] bigint NULL;
    END TRY
    BEGIN CATCH
        IF (XACT_STATE() <> 0)
        BEGIN
            ROLLBACK TRANSACTION;
        END

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
        PRINT 'ERROR: DC CTP5 to CTP6 upgrade tasks failed' 
        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
    END CATCH;
END
GO

-- Re-create the PK/FK constraints that we dropped in order to modify column datatypes
IF (OBJECT_ID ('dbo.syscollector_execution_log_internal') IS NOT NULL) AND NOT EXISTS (SELECT * FROM sys.objects WHERE name = 'PK_syscollector_execution_log' AND [schema_id] = SCHEMA_ID ('dbo'))
BEGIN
    BEGIN TRY
        PRINT 'Creating PK_syscollector_execution_log...';
        ALTER TABLE [dbo].[syscollector_execution_log_internal] ADD CONSTRAINT [PK_syscollector_execution_log] 
        PRIMARY KEY CLUSTERED (log_id ASC);
    END TRY
    BEGIN CATCH
        -- If we're still in the transaction started by the prior batch, roll it back so that it's 
        -- as if we never dropped any constraints
        PRINT 'ERROR: DC CTP5 to CTP6 upgrade tasks failed to create [PK_syscollector_execution_log] ' 
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
    END CATCH;
END
GO
IF (OBJECT_ID ('dbo.syscollector_execution_stats_internal') IS NOT NULL) AND NOT EXISTS (SELECT * FROM sys.objects WHERE name = 'PK_syscollector_execution_stats' AND [schema_id] = SCHEMA_ID ('dbo'))
BEGIN
    BEGIN TRY
        PRINT 'Creating PK_syscollector_execution_stats...';
        ALTER TABLE [dbo].[syscollector_execution_stats_internal] ADD CONSTRAINT [PK_syscollector_execution_stats] 
        PRIMARY KEY CLUSTERED (log_id ASC, task_name ASC, log_time DESC);
    END TRY
    BEGIN CATCH
        PRINT 'ERROR: DC CTP5 to CTP6 upgrade tasks failed to create [PK_syscollector_execution_stats] ' 
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
    END CATCH;
END
GO
IF (OBJECT_ID ('dbo.syscollector_execution_stats_internal') IS NOT NULL) AND NOT EXISTS (SELECT * FROM sys.objects WHERE name = 'FK_syscollector_execution_stats_log_id' AND [schema_id] = SCHEMA_ID ('dbo'))
BEGIN
    BEGIN TRY
        PRINT 'Creating FK_syscollector_execution_stats_log_id...';
        ALTER TABLE [dbo].[syscollector_execution_stats_internal] ADD CONSTRAINT [FK_syscollector_execution_stats_log_id] 
        FOREIGN KEY (log_id) REFERENCES [dbo].[syscollector_execution_log_internal] (log_id)
            ON DELETE CASCADE;
    END TRY
    BEGIN CATCH
        PRINT 'ERROR: DC CTP5 to CTP6 upgrade tasks failed to create [FK_syscollector_execution_stats_log_id] ' 
        IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION;
    END CATCH;
END
GO

IF (@@TRANCOUNT > 0) COMMIT TRANSACTION;
GO
IF (OBJECT_ID ('dbo.syscollector_execution_log_internal') IS NOT NULL)
BEGIN
    EXEC sp_refreshview 'dbo.syscollector_execution_log'
    EXEC sp_refreshview 'dbo.syscollector_execution_stats'
END
GO
--
-- <<< CTP5 - CTP6 Upgrade
--

--
-- >>> Delete auto-generated T-SQL packages stored in msdb. 
--

IF OBJECT_ID ('dbo.syscollector_tsql_query_collector') IS NOT NULL
BEGIN
    RAISERROR ('Deleting cached auto-generated T-SQL Data Collection packages from msdb...', 0, 1) WITH NOWAIT

    /*
    ** We have a row in [syscollector_tsql_query_collector] for each T-SQL collection item, and 
    ** each item has a collection and an upload package. There's a DELETE trigger on this table 
    ** that will take care of deleting the packages from sysssispackages for us.  The packages 
    ** will be re-generated automatically the next time their parent collection set's collection 
    ** package runs. 
    */ 
    DELETE FROM syscollector_tsql_query_collector
END
--
-- <<< Delete auto-generated T-SQL packages stored in msdb. 
--
GO

PRINT 'End of DC pre-upgrade steps.';

/**********************************************************************/
/* End of DC Pre-upgrade steps                                        */
/**********************************************************************/
GO




/**********************************************************************/
/* DAC Pre-upgrade steps                                               */
/**********************************************************************/
PRINT 'DAC pre-upgrade steps...';
GO


RAISERROR('Starting DAC pre-upgrade steps ...', 0, 1) WITH NOWAIT;

--
-- SQL Server 2008 R2 : CTP2->CTP3 upgrade 
-- 1. sysdac_history table has two extra columns that we added in CTP3 (required, comments).
--    It has been decided not to preserve CTP2 history data  i.e. on an upgrade from CTP2->CTP3, deploy/uninstall logs are cleared. 
--
-- 2. {sysdac_packages_internal, sysdac_packages, sysdac_parts_internal, sysdac_parts} are deleted. They are replaced with new artifacts in CTP3.
--

IF (OBJECT_ID('[dbo].[sysdac_history_internal]', 'U') IS NOT NULL)
BEGIN
    IF NOT EXISTS(SELECT 1
	            FROM sys.columns 
	            WHERE (name = 'comments' OR name = 'required') AND  
	                    object_id = OBJECT_ID('[dbo].[sysdac_history_internal]', 'U')
	                    )						
    BEGIN
        DROP TABLE [dbo].[sysdac_history_internal]
    END
END
	
IF (OBJECT_ID('[dbo].[sysdac_parts]', 'V') IS NOT NULL)
    DROP VIEW [dbo].[sysdac_parts]	

IF (OBJECT_ID('[dbo].[sysdac_packages]', 'V') IS NOT NULL)
    DROP VIEW [dbo].[sysdac_packages]

IF (OBJECT_ID('[dbo].[sysdac_parts_internal]', 'U') IS NOT NULL)
    DROP TABLE [dbo].[sysdac_parts_internal]

IF (OBJECT_ID('[dbo].[sysdac_packages_internal]', 'U') IS NOT NULL)
    DROP TABLE [dbo].[sysdac_packages_internal]


PRINT 'End of DAC pre-upgrade steps.';
GO
/**********************************************************************/
/* End of DAC Pre-upgrade steps                                        */
/**********************************************************************/



-------------------------------------------------------------------------
--
/**************************************************************/
/* Utility pre-upgrade steps                                 */
/**************************************************************/


/**************************************************************/
/* End of Utility pre-upgrade steps                          */
/**************************************************************/


/**********************************************************************/
/* MSDB.SQL                                                           */
/*                                                                    */
/* Creates the MSDB database and some utility SPs.                    */
/*                                                                    */
/*                                                                    */
/* Copyright (c) Microsoft Corporation                                */
/* All Rights Reserved.                                               */
/*                                                                    */
/**********************************************************************/

PRINT '----------------------------------'
PRINT 'Starting execution of MSDB.SQL'
PRINT '----------------------------------'
go


--this version of instmsdb should be executed only against 15.0 and later servers
IF (@@microsoftversion / 0x01000000) < 15
BEGIN
      RAISERROR('This version of instmsdb.sql should only be executed against 15.0 and later servers.', 20, 127) WITH LOG 
END
go

-- disable the event collection for policies while running this script
IF EXISTS (SELECT * FROM sys.server_triggers WHERE name = N'syspolicy_server_trigger')
    DISABLE TRIGGER [syspolicy_server_trigger] ON ALL SERVER 
GO

IF EXISTS (SELECT * FROM sys.service_queues where name = N'syspolicy_event_queue')
    ALTER QUEUE [syspolicy_event_queue] WITH ACTIVATION (STATUS = OFF)
GO

/*********************************************************************/
/* Create auxilary procedure to enable OBD (Off By Default component */
/*********************************************************************/
-- In case msdb script hasn't completed successfully, the procedure may have not be dropped, and the create will fail.
-- Execute CREATE OR ALTER instead, to make sure it succeeds.
CREATE OR ALTER PROCEDURE #sp_enable_component     
   @comp_name     sysname, 
   @advopt_old_value    INT OUT, 
   @comp_old_value   INT OUT 
AS
   BEGIN
   SELECT @advopt_old_value=cast(value_in_use as int) from sys.configurations where name = 'show advanced options';
   SELECT @comp_old_value=cast(value_in_use as int) from sys.configurations where name = @comp_name; 
   EXEC sp_configure 'show advanced options',1;
   RECONFIGURE WITH OVERRIDE;
   EXEC sp_configure @comp_name, 1; 
   RECONFIGURE WITH OVERRIDE;
   END
go

-- In case msdb script hasn't completed successfully, the procedure may have not be dropped, and the create will fail.
-- Execute CREATE OR ALTER instead, to make sure it succeeds.
CREATE OR ALTER PROCEDURE #sp_restore_component_state 
   @comp_name     sysname, 
   @advopt_old_value    INT, 
   @comp_old_value   INT 
AS
   BEGIN
   EXEC sp_configure @comp_name, @comp_old_value; 
   RECONFIGURE WITH OVERRIDE;
   EXEC sp_configure 'show advanced options',@advopt_old_value;
   RECONFIGURE WITH OVERRIDE;
   END
go

-- Explicitly set the options that the server stores with the object in sysobjects.status
-- so that it doesn't matter if the script is run using a DBLib or ODBC based client.
SET QUOTED_IDENTIFIER OFF -- We don't use quoted identifiers
SET ANSI_NULLS ON         -- We don't want (NULL = NULL) == TRUE
go
SET ANSI_PADDING ON       -- Set so that trailing zeros aren't trimmed off sysjobs.owner_login_sid
go

-- Allow updates to system catalogs so that all our SP's inherit full DML capability on
-- system objects and so that we can exercise full DDL control on our system objects
-- Note: This option is still present, but has no effect and will be removed in a future
-- version of SQL Server.
-- On Managed Instances, changes to configuration option allow updates are not supported.
IF (SERVERPROPERTY('EngineEdition') <> 8)
  EXECUTE master.dbo.sp_configure N'allow updates', 1
go
RECONFIGURE WITH OVERRIDE
go

/**************************************************************/
/*                                                            */
/*      D  A  T  A  B  A  S  E    C  R  E  A  T  I  O  N      */
/*                                                            */
/**************************************************************/

IF (NOT EXISTS (SELECT name
                FROM master.dbo.sysdatabases
                WHERE (name = N'msdb')))
BEGIN
  PRINT 'Creating the msdb database...'
END
go

USE master
go

SET NOCOUNT ON

-- NOTE: It is important that this script can be re-run WITHOUT causing loss of data, hence
--       we only create the database if it missing (if the database already exists we test
--       that it has enough free space and if not we expand both the device and the database).
DECLARE @model_db_size    INT
DECLARE @msdb_db_size     INT
DECLARE @sz_msdb_db_size  VARCHAR(10)
DECLARE @device_directory NVARCHAR(260)
DECLARE @page_size        INT
DECLARE @size             INT
DECLARE @free_db_space    FLOAT

SELECT @page_size = 8

IF (NOT EXISTS (SELECT name
                FROM master.dbo.sysdatabases
                WHERE (name = N'msdb')))
BEGIN
  -- Make sure that we create [the data portion of] MSDB to be at least as large as
  -- the MODEL database
  SELECT @model_db_size = (SUM(size) * @page_size)
  FROM model.dbo.sysfiles

  IF (@model_db_size > 3072) -- 3 is the minimum required size for MSDB (in megabytes)
    SELECT @msdb_db_size = @model_db_size
  ELSE
    SELECT @msdb_db_size = 3072

  SELECT @device_directory = REVERSE(SUBSTRING(REVERSE(physical_name), CHARINDEX(CAST(SERVERPROPERTY('pathseparator') as nvarchar(2)), REVERSE(physical_name)), LEN(physical_name))) 
  FROM sys.master_files 
  WHERE [database_id] = 1
  AND [file_id] = 1
  AND [data_space_id] = 1

  -- Drop any existing MSDBData / MSDBLog file(s)
  DECLARE   @advopt_old_value    INT 
  DECLARE   @comp_old_value   INT
  EXECUTE(N'EXECUTE master.sys.xp_delete_files N''' + @device_directory + N'MSDBData.mdf'', N''' + @device_directory + N'MSDBLog.ldf''')

  -- Create the database
  PRINT ''
  PRINT 'Creating MSDB database...'
  SELECT @sz_msdb_db_size = RTRIM(LTRIM(CONVERT(VARCHAR, @msdb_db_size)))
  EXECUTE (N'CREATE DATABASE msdb ON (NAME = N''MSDBData'', FILENAME = N''' + @device_directory + N'MSDBData.mdf'', SIZE = ' + @sz_msdb_db_size + N'KB, MAXSIZE = UNLIMITED, FILEGROWTH = 10%)
                         LOG ON (NAME = N''MSDBLog'',  FILENAME = N''' + @device_directory + N'MSDBLog.ldf'',  SIZE = 512KB, MAXSIZE = UNLIMITED, FILEGROWTH = 10%)')
  EXECUTE (N'ALTER DATABASE msdb SET DB_CHAINING ON')
  PRINT ''
END
ELSE
BEGIN
  PRINT 'Checking the size of MSDB...'

  DBCC UPDATEUSAGE(N'msdb') WITH NO_INFOMSGS

  -- Make sure that MSDBLog has unlimited growth
  -- On Managed Instance, msdb log file name is 'log'.
  IF (SERVERPROPERTY('EngineEdition') <> 8)
    ALTER DATABASE msdb MODIFY FILE (NAME = N'MSDBLog', MAXSIZE = 2TB)
  ELSE
    ALTER DATABASE msdb MODIFY FILE (NAME = N'log', MAXSIZE = 2TB)

  -- Determine amount of free space in msdb. We need at least 2MB free.
  SELECT @free_db_space = ((((SELECT SUM(size)
                              FROM msdb.dbo.sysfiles
                              WHERE status & 0x8040 = 0) -
                             (SELECT SUM(reserved)
                              FROM msdb.dbo.sysindexes
                              WHERE indid IN (0, 1, 255))) * @page_size) / 1024.0)

  IF (@free_db_space < 2)
  BEGIN
    DECLARE @logical_file_name sysname
    DECLARE @os_file_name      NVARCHAR(255)
    DECLARE @size_as_char      VARCHAR(10)
   
    -- Slightly different query for Managed instance, since FILENAME is not supported.
    IF (SERVERPROPERTY('EngineEdition') <> 8)
    BEGIN
      SELECT @logical_file_name = name,
             @os_file_name = filename,
             @size_as_char = CONVERT(VARCHAR(10), size*8 + 2048) -- size column in sysaltfiles is in number of 8KB pages
      FROM master.dbo.sysaltfiles
      WHERE (name = N'MSDBData')
      set @os_file_name = QUOTENAME(@os_file_name,'''')
      PRINT 'Attempting to expand the msdb database...'
      EXECUTE(N'ALTER DATABASE msdb MODIFY FILE (NAME = N''' + @logical_file_name + N''',
                                                 FILENAME = N' + @os_file_name + N',
                                                 SIZE =' + @size_as_char + N'KB)')    
    END
    ELSE
    BEGIN
      SELECT @logical_file_name = name,
             @os_file_name = filename,
             @size_as_char = CONVERT(VARCHAR(10), size*8 + 2048) -- size column in sysaltfiles is in number of 8KB pages
      FROM master.dbo.sysaltfiles
      WHERE (filename like N'%msdb.mdf')
      set @os_file_name = QUOTENAME(@os_file_name,'''')
      PRINT 'Attempting to expand the msdb database...'
      EXECUTE(N'ALTER DATABASE msdb MODIFY FILE (NAME = N''' + @logical_file_name + N''',
                                                 SIZE =' + @size_as_char + N'KB)')    
    END
    IF (@@error <> 0)
      RAISERROR('Unable to expand the msdb database. MSDB.SQL terminating.', 20, 127) WITH LOG
  END
  PRINT ''
END

EXECUTE (N'ALTER DATABASE msdb SET TRUSTWORTHY ON')

go

-- truncate log on checkpoint
-- Simple recovery mode not supported on Managed Instances and System AG.
IF (SERVERPROPERTY('EngineEdition') <> 8 AND (select group_database_id from sys.databases where name = 'msdb') is null)
ALTER DATABASE msdb 
SET RECOVERY SIMPLE
go

USE msdb
go

-- Check that we're in msdb
IF (DB_NAME() <> N'msdb')
  RAISERROR('A problem was encountered accessing msdb. MSDB.SQL terminating.', 20, 127) WITH LOG
go

-- Add the guest user
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysusers
                WHERE (name = N'guest')
                  AND (hasdbaccess = 1)))
BEGIN
  PRINT ''
  EXECUTE sys.sp_adduser N'guest'
END
go

CHECKPOINT
go

/**************************************************************/
/* Record time of start of creates                            */
/**************************************************************/
--DROP IF EXISTS, to ensure that the the SELECT below doesn't fail.
DROP TABLE IF EXISTS #InstMsdb
GO
SELECT start = getdate() INTO #InstMsdb
go

/**************************************************************/
/*                                                            */
/*              T  A  B  L  E     D  R  O  P  S               */
/*                                                            */
/**************************************************************/
SET NOCOUNT ON

DECLARE @build_number   INT
SELECT @build_number = @@microsoftversion & 0xffff

-- Do any necessary changes based on build number here


/**************************************************************/
/*                                                            */
/*      F I N I S H E D    T  A  B  L  E     D  R  O  P  S    */
/*                                                            */
/**************************************************************/

PRINT '----------------------------------'
PRINT 'Finished execution of MSDB.SQL'
PRINT '----------------------------------'
go









































PRINT '-----------------------------------------'
PRINT 'Starting execution of MSDB_VERSIONING.SQL'
PRINT '-----------------------------------------'
go


-- This table is used by SqlScriptUpgrade to detect the Sql11 version (the code is in <>\sql\mpu\sqlagent\scripts\msdb_upgrade_discovery.sql)
-- Even though the version numbers are taken from sql_version.h so they match the server version,
--  the intent here is different. We only use the major version (for now).
IF (OBJECT_ID(N'dbo.msdb_version', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table msdb_version...'
   CREATE TABLE dbo.msdb_version
   (
      id                 INT    IDENTITY,
      version_string		 NVARCHAR(255)    NOT NULL,
      version_major      INT NOT NULL,
      version_minor      INT NOT NULL,
      version_build      INT NOT NULL,
      version_revision   INT NOT NULL
   )
END
GO
IF NOT EXISTS (SELECT 1 FROM sys.columns 
   WHERE name = N'script_hash'
   AND object_id = OBJECT_ID(N'dbo.msdb_version', 'U'))
BEGIN
   ALTER TABLE dbo.msdb_version ADD script_hash UNIQUEIDENTIFIER
END
GO
IF NOT EXISTS (SELECT 1 FROM msdb.sys.columns 
   WHERE name = N'upgrade_script_completed'
   AND object_id = OBJECT_ID(N'msdb.dbo.msdb_version', 'U'))
BEGIN
   ALTER TABLE msdb.dbo.msdb_version ADD upgrade_script_completed BIT NOT NULL DEFAULT 1
END
GO
DECLARE @vsmaj INT
DECLARE @vsmin INT
DECLARE @vsbld INT
DECLARE @vsrev INT
DECLARE @vsstr NVARCHAR(255)

DECLARE @script_version_str NVARCHAR(50)
DECLARE @script_version UNIQUEIDENTIFIER

-- The variables below will be initialized during preprocessing.
-- The right side of the assignment will be replaced with values from sql\version\sqlversion.h
-- Actually MSDB version number defines MSDB own version that is not related to SQL product version.
-- We use the same SQL version from sqlversion.h just for an automatic build process. Thus MSDB
-- version happens being always updated in every build even though there might be no changes in MSDB.
SET @vsmaj = 15;
SET @vsmin = 0;
SET @vsbld = 2130;
SET @vsrev = 3;

SET @vsstr = CAST (@vsmaj AS NVARCHAR(10)) + N'.' + 
			 CAST (@vsmin AS NVARCHAR(10)) + N'.' + 
			 CAST (@vsbld AS NVARCHAR(10)) + N'.' +  
			 CAST (@vsrev AS NVARCHAR(10));

SET @script_version_str = REPLACE('8340fb94e5332e44a3e779f6bafe000b', ' ', '');
-- We get script version hash in the form of 16 space-separated byte-size values.
-- Here we convert it to unique identifier form.
SET @script_version = CAST(
        SUBSTRING(@script_version_str, 1, 8) + '-' + SUBSTRING(@script_version_str, 9, 4) + '-' +
        SUBSTRING(@script_version_str, 13, 4) + '-' + SUBSTRING(@script_version_str, 17, 4) + '-' +
        SUBSTRING(@script_version_str, 21, 12)
        AS UNIQUEIDENTIFIER);

DECLARE @curr_version UNIQUEIDENTIFIER;
DECLARE @script_completed BIT;
SELECT @curr_version = script_hash, @script_completed = upgrade_script_completed FROM msdb.dbo.msdb_version

-- Run the rest of the script only if the script version is different, or if the previous script execution never completed.
--
IF (SERVERPROPERTY('EngineEdition') = 8 and @curr_version = @script_version and @script_completed = 1)
BEGIN
 -- These settings are necessary for parsing the rest of the script when NOEXEC is ON.
 SET ANSI_WARNINGS ON
 SET CONCAT_NULL_YIELDS_NULL ON
 PRINT 'Msdb is at the latest version, skipping the rest of the script.'
 SET NOEXEC ON
END

-- Even if the table exists already, update the version.
TRUNCATE TABLE dbo.msdb_version

INSERT INTO msdb.dbo.msdb_version (version_string, version_major, version_minor, version_build, version_revision, script_hash, upgrade_script_completed)
    VALUES (@vsstr, @vsmaj, @vsmin, @vsbld, @vsrev, @script_version, 0)
GO

PRINT '-----------------------------------------'
PRINT 'Finished execution of MSDB_VERSIONING.SQL'
PRINT '-----------------------------------------'
GO
---------------------------------------------------------------
-- Replication Datatype mapping tables
---------------------------------------------------------------

-- REVIEW - Is this needed? What does this do?

-- Removing the drop procedure. This procedure has issues during the upgrade scenario. 
-- If new datatype mappings were added before upgrade, dropping and recreating 
-- datatypemappings, deletes all the newly added mappings- which is not the desired behavior.

--exec sys.sp_MSrepl_dropdatatypemappings
--go

--CHECKPOINT
--go

---------------------------------------------------------------
-- Replication Datatype mapping tables
---------------------------------------------------------------

exec sys.sp_MSrepl_createdatatypemappings
go


CHECKPOINT
go

/**********************************************************************/
/* SQLAGENT.SQL                                                       */
/*                                                                    */
/* Installs the tables, triggers and stored procedures necessary for  */
/* supporting local (and multi-server) jobs, alerts, operators, and   */
/* backup history.  These objects are used by SQL SMO, SQL Management */
/* Studio and SQLServerAgent                                          */
/*                                                                    */
/* All Rights Reserved.                                               */
/*                                                                    */
/**********************************************************************/

/**************************************************************/
/* drop certificate signature from Agent signed sps           */
/**************************************************************/

BEGIN TRANSACTION
DECLARE @qualified_spname SYSNAME
DECLARE @schema_name SYSNAME
DECLARE @sp SYSNAME
DECLARE @exec_str NVARCHAR(1024)
DECLARE ms_crs_sps CURSOR GLOBAL FOR
   SELECT SCHEMA_NAME(so.[schema_id]),
    OBJECT_NAME(crypts.major_id)
   FROM sys.crypt_properties crypts, sys.certificates certs, sys.objects  so
   WHERE crypts.thumbprint = certs.thumbprint
   AND crypts.major_id = so.[object_id]
   AND crypts.class = 1
   AND certs.name = '##MS_AgentSigningCertificate##'

OPEN ms_crs_sps
FETCH NEXT FROM ms_crs_sps into @schema_name, @sp

WHILE @@fetch_status = 0
BEGIN
   IF EXISTS(SELECT * FROM sys.objects
                WHERE
                name = @sp AND
                SCHEMA_NAME(schema_id) = @schema_name
                )
   BEGIN
      SET @qualified_spname = QUOTENAME(@schema_name) + '.'  + QUOTENAME(@sp)

      PRINT 'Dropping signature from: ' + @qualified_spname

      SET @exec_str = N'DROP SIGNATURE FROM ' + @qualified_spname + N' BY CERTIFICATE [##MS_AgentSigningCertificate##]'

      EXECUTE(@exec_str)

      IF (@@ERROR <> 0)
      BEGIN
         DECLARE @err_str nvarchar(1024)
         SET @err_str = 'Cannot drop signature from ' + @qualified_spname + '. Terminating.'

         CLOSE ms_crs_sps
         DEALLOCATE ms_crs_sps
         ROLLBACK TRANSACTION
         RAISERROR(@err_str, 20, 127) WITH LOG
         RETURN
      END
   END

   FETCH NEXT FROM ms_crs_sps into @schema_name, @sp
END
CLOSE ms_crs_sps
DEALLOCATE ms_crs_sps
COMMIT TRANSACTION
go

/**************************************************************/
/*                                                            */
/*                     D  E  F  A  U  L  T  S                 */
/*                                                            */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'default_sdl_error_message')
                  AND (type = 'D')))
  EXECUTE('CREATE DEFAULT default_sdl_error_message AS NULL')
go
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'default_current_date')
                  AND (type = 'D')))
  EXECUTE('CREATE DEFAULT default_current_date AS GETDATE()')
go
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'default_zero')
                  AND (type = 'D')))
  EXECUTE('CREATE DEFAULT default_zero AS 0')
go
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'default_one')
                  AND (type = 'D')))
  EXECUTE('CREATE DEFAULT default_one AS 1')
go

PRINT ''
PRINT 'Creating procedure sp_agent_add_job...'
IF (NOT OBJECT_ID(N'dbo.sp_agent_add_job', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_agent_add_job
GO

CREATE PROCEDURE dbo.sp_agent_add_job
    @job_name               SYSNAME,
    @enabled                TINYINT = 1, -- 0 = Disabled, 1 = Enabled
    @description            NVARCHAR(512) = NULL,
    @start_step_id          INT = 1,
    @notify_level_eventlog  INT = 2,    -- 0 = Never, 1 = On Success, 2 = On Failure, 3 = Always
    @delete_level           INT = 0,    -- 0 = Never, 1 = On Success, 2 = On Failure, 3 = Always
    @job_id                 UNIQUEIDENTIFIER = NULL OUTPUT
AS
BEGIN
  -- If this is Azure SQL Database - Managed Instance disable eventlog
  IF (SERVERPROPERTY('EngineEdition') = 8)
    SET @notify_level_eventlog = 0

    DECLARE @retval INT

    EXEC @retval = sys.sp_sqlagent_add_job @job_name = @job_name,
        @enabled = @enabled,
        @description = @description,
        @start_step_id = @start_step_id,
        @notify_level_eventlog = @notify_level_eventlog,
        @delete_level = @delete_level,
        @job_id = @job_id OUTPUT

     RETURN(@retval) -- 0 means success
END

GO

/**************************************************************/
/* sp_agent_delete_job                                       */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_agent_delete_job...'
IF (NOT OBJECT_ID(N'dbo.sp_agent_delete_job', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_agent_delete_job
GO

CREATE PROCEDURE dbo.sp_agent_delete_job
    @job_id                UNIQUEIDENTIFIER,
    @is_system             TINYINT = 0
AS
BEGIN
    DECLARE @retval INT

    IF(@is_system = 1)
    BEGIN
        -- Delete system job
        EXEC @retval = sys.sp_sqlagent_delete_job
            @job_id
    END
    ELSE
    BEGIN
        -- delete user job
        EXEC msdb.dbo.sp_delete_job @job_id = @job_id
        SELECT @retval = @@error
    END

    RETURN(@retval) -- 0 means success
END
GO

/**************************************************************/
/* sp_agent_add_jobstep                                       */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_agent_add_jobstep...'
IF (NOT OBJECT_ID(N'dbo.sp_agent_add_jobstep', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_agent_add_jobstep
GO

CREATE PROCEDURE dbo.sp_agent_add_jobstep
    @job_id                UNIQUEIDENTIFIER = NULL,   -- Must provide either this or job_name
    @job_name              SYSNAME          = NULL,   -- Must provide either this or job_id
    @step_id               INT              = NULL,
    @step_name             SYSNAME,
    @subsystem             NVARCHAR(40)     = N'TSQL',
    @command               NVARCHAR(max)    = NULL,
    @additional_parameters NVARCHAR(max)    = NULL,
    @cmdexec_success_code  INT              = 0,
    @on_success_action     TINYINT          = 1,      -- 1 = Quit With Success, 2 = Quit With Failure, 3 = Goto Next Step, 4 = Goto Step
    @on_success_step_id    INT              = 0,
    @on_fail_action        TINYINT          = 2,      -- 1 = Quit With Success, 2 = Quit With Failure, 3 = Goto Next Step, 4 = Goto Step
    @on_fail_step_id       INT              = 0,
    @server                SYSNAME          = NULL,
    @database_name         SYSNAME          = NULL,
    @database_user_name    SYSNAME          = NULL,
    @retry_attempts        INT              = 0,
    @retry_interval        INT              = 0,
    @os_run_priority       INT              = 0,
    @output_file_name      NVARCHAR(200)    = NULL,
    @flags                 INT              = 128,     -- 128  - System jobstep flag
    @step_uid UNIQUEIDENTIFIER              = NULL OUTPUT
AS
BEGIN
    DECLARE @retval INT

    EXEC @retval = sys.sp_sqlagent_add_jobstep   @job_id = @job_id,
        @job_name = @job_name,
        @step_id = @step_id,
        @step_name = @step_name,
        @subsystem = @subsystem,
        @command = @command,
        @flags = @flags,
        @additional_parameters = @additional_parameters,
        @cmdexec_success_code = @cmdexec_success_code,
        @on_success_action = @on_success_action,
        @on_success_step_id = @on_success_step_id,
        @on_fail_action = @on_fail_action,
        @on_fail_step_id = @on_fail_step_id,
        @server = @server,
        @database_name = @database_name,
        @database_user_name = @database_user_name,
        @retry_attempts = @retry_attempts,
        @retry_interval = @retry_interval,
        @os_run_priority = @os_run_priority,
        @output_file_name = @output_file_name,
        @step_uid = @step_uid OUTPUT

    RETURN(@retval) -- 0 means success
END
GO


PRINT ''
PRINT 'Creating procedure sp_agent_start_job...'
IF (NOT OBJECT_ID(N'dbo.sp_agent_start_job', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_agent_start_job
GO

CREATE PROCEDURE dbo.sp_agent_start_job
  @job_id      UNIQUEIDENTIFIER
AS
BEGIN
    DECLARE @retval INT

    EXEC @retval = sys.sp_sqlagent_start_job @job_id

    RETURN(@retval) -- 0 means success
END
GO

PRINT ''
PRINT 'Creating procedure sp_agent_get_jobstep...'
IF (NOT OBJECT_ID(N'dbo.sp_agent_get_jobstep', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_agent_get_jobstep
GO

CREATE PROCEDURE dbo.sp_agent_get_jobstep
  @job_id                UNIQUEIDENTIFIER,
  @is_system             TINYINT = 0
AS
BEGIN
    -- user jobs
    IF(@is_system = 1)
     BEGIN
        EXEC sys.sp_sqlagent_help_jobstep  @job_id = @job_id
    END
    ELSE
    BEGIN
        EXECUTE msdb.dbo.sp_help_jobstep @job_id = @job_id
    END
END
GO

/**************************************************************/
/* SP_AGENT_LOG_JOB_HISTORY                                   */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_agent_log_job_history...'
IF (NOT OBJECT_ID(N'dbo.sp_agent_log_job_history', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_agent_log_job_history
GO
CREATE PROCEDURE sp_agent_log_job_history
    @job_id               UNIQUEIDENTIFIER,
    @is_system            TINYINT = 0,
    @step_id              INT,
    @sql_message_id       INT = 0,
    @sql_severity         INT = 0,
    @message              NVARCHAR(4000) = NULL,
    @run_status           INT, -- SQLAGENT_EXEC_X code
    @run_date             INT,
    @run_time             INT,
    @run_duration         INT,
    @operator_id_emailed  INT = 0,
    @operator_id_netsent  INT = 0,
    @operator_id_paged    INT = 0,
    @retries_attempted    INT,
    @server               sysname = NULL,
    @session_id           INT = 0
AS
BEGIN
    IF(@is_system = 1)
    BEGIN
        EXEC sys.sp_sqlagent_log_job_history  @job_id = @job_id,
                @step_id = @step_id,
                @sql_message_id = @sql_message_id,
                @sql_severity = @sql_severity,
                @message = @message,
                @run_status = @run_status,
                @run_date = @run_date,
                @run_time = @run_time,
                @run_duration = @run_duration,
                @operator_id_emailed = @operator_id_emailed,
                @operator_id_paged = @operator_id_paged,
                @retries_attempted = @retries_attempted
    END
    ELSE
    BEGIN
        -- Update history for user jobs
        EXEC sp_sqlagent_log_jobhistory @job_id,
            @step_id,
            @sql_message_id,
            @sql_severity,
            @message,
            @run_status,
            @run_date,
            @run_time,
            @run_duration,
            @operator_id_emailed,
            @operator_id_netsent,
            @operator_id_paged,
            @retries_attempted,
            @server,
            @session_id
    END
END
GO

/**************************************************************/
/* SP_AGENT_WRITE_SYSJOBSTEP_LOG                              */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_agent_write_sysjobstep_log...'
IF (NOT OBJECT_ID(N'dbo.sp_agent_write_sysjobstep_log', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_agent_write_sysjobstep_log
GO

CREATE PROCEDURE sp_agent_write_sysjobstep_log
    @job_id    UNIQUEIDENTIFIER,
    @is_system TINYINT = 0,
    @step_id   INT,
    @log_text  NVARCHAR(MAX),
    @append_to_last INT = 0
AS
BEGIN
    IF(@is_system = 1)
    BEGIN
        EXEC sys.sp_sqlagent_write_jobstep_log @job_id = @job_id,
            @step_id = @step_id,
            @log_text = @log_text
    RETURN
    END
    ELSE
    BEGIN
        EXEC sp_write_sysjobstep_log @job_id = @job_id,
            @step_id = @step_id,
            @log_text = @log_text,
            @append_to_last = @append_to_last
    END
END
GO



/**************************************************************/
/*                                                            */
/*                       T  A  B  L  E  S                     */
/*                                                            */
/**************************************************************/

/**************************************************************/
/* SYSPROXIES                                              */
/**************************************************************/
IF (OBJECT_ID(N'dbo.sysproxies', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysproxies...'
   CREATE TABLE dbo.sysproxies
   (
      proxy_id             INT               IDENTITY,   --used to identify a proxy
      name                  sysname       NOT NULL,   --friendly name of a proxy
    credential_id         INT           NOT NULL,
      enabled                TINYINT       NOT NULL,
      description           NVARCHAR(512) NULL,     --nvarchar(512)
    user_sid              VARBINARY(85) NOT NULL,  --sid of proxy NT user
    credential_date_created  DATETIME   NOT NULL   --the date the associated credential has been created
   )
   CREATE UNIQUE CLUSTERED    INDEX clust ON sysproxies(proxy_id)
   CREATE UNIQUE NONCLUSTERED INDEX nc1   ON sysproxies(name)
END
go

IF (OBJECT_ID(N'dbo.syssubsystems', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table syssubsystems...'
   CREATE TABLE dbo.syssubsystems
   (
      subsystem_id       INT         NOT NULL,                       -- used to identify the subsystem
      subsystem          NVARCHAR(40)  COLLATE database_default NOT NULL,    -- Name of subsystem
      description_id     INT         NULL,                           -- Message number of description string. These messages are stored in sysmessages table for localization purposes. Same story as for Shiloh
      subsystem_dll      NVARCHAR(255) COLLATE database_default NULL,        -- Store full path of the name of subsystem dll  subsystem are always installed in the binn folder of an instance
      agent_exe          NVARCHAR(255) COLLATE database_default NULL,        -- Full path to the executable that use the subsystem
      start_entry_point  NVARCHAR(30)  COLLATE database_default NULL,        -- Function called when initializing subsystem
      event_entry_point  NVARCHAR(30)  COLLATE database_default NULL,        -- Function called when executing a subsystem step
      stop_entry_point   NVARCHAR(30)  COLLATE database_default NULL,        -- Function called when unloading a subsystem
      max_worker_threads INT           NULL                                  -- Number of maximum concurrent steps for a subsystem
   )
   CREATE UNIQUE CLUSTERED    INDEX clust ON syssubsystems(subsystem_id)
   CREATE UNIQUE NONCLUSTERED INDEX nc1   ON syssubsystems(subsystem)
END
go

IF (OBJECT_ID(N'dbo.sysproxysubsystem', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysproxysubsystem...'

   CREATE TABLE dbo.sysproxysubsystem
   (
      subsystem_id INT           NOT NULL,  --  used to identify the subsystem
      proxy_id     INT           NOT NULL,  --  used to identify the proxy
   )
   CREATE UNIQUE CLUSTERED    INDEX clust ON sysproxysubsystem(subsystem_id, proxy_id)
END
go

IF (OBJECT_ID(N'dbo.sysproxylogin', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysproxylogin...'

   CREATE TABLE dbo.sysproxylogin
   (
      proxy_id     INT           NOT NULL,  --used to identify the proxy
    sid          VARBINARY(85) NULL,       --keep logins, fixed server roles or msdb database roles
      flags        INT           DEFAULT 0 NOT NULL   -- tells is member_id is login = 0, server fixed role, msdb role.
   )
   CREATE UNIQUE CLUSTERED    INDEX clust ON sysproxylogin(proxy_id, sid, flags)
END
go

PRINT ''
PRINT 'Creating view sysproxyloginsubsystem_view...'
go
IF (NOT OBJECT_ID(N'dbo.sysproxyloginsubsystem_view', 'V') IS NULL)
  DROP VIEW sysproxyloginsubsystem_view
go
CREATE VIEW sysproxyloginsubsystem_view
AS
SELECT ps.subsystem_id AS subsystem_id, pl.proxy_id AS proxy_id, pl.sid AS sid, pl.flags AS flags
FROM sysproxylogin pl JOIN sysproxysubsystem ps ON pl.proxy_id = ps.proxy_id
go

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sqlagent_info')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sqlagent_info...'

  CREATE TABLE sqlagent_info
  (
  attribute sysname       NOT NULL,
  value     NVARCHAR(512) NOT NULL
  )
END
go

/**************************************************************/
/* SYSDOWNLOADLIST                                            */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysdownloadlist')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysdownloadlist...'

  CREATE TABLE sysdownloadlist
  (
  instance_id         INT IDENTITY     NOT NULL,
  source_server       sysname          NOT NULL,
  operation_code      TINYINT          NOT NULL,
  object_type         TINYINT          NOT NULL,
  object_id           UNIQUEIDENTIFIER NOT NULL,
  target_server       sysname        NOT NULL,
  error_message       NVARCHAR(1024)   NULL,
  date_posted         DATETIME         NOT NULL,
  date_downloaded     DATETIME         NULL,
  status              TINYINT          NOT NULL,
  deleted_object_name sysname          NULL
  )

  EXECUTE sys.sp_bindefault default_sdl_error_message, N'sysdownloadlist.error_message'
  EXECUTE sys.sp_bindefault default_current_date,      N'sysdownloadlist.date_posted'
  EXECUTE sys.sp_bindefault default_zero,              N'sysdownloadlist.status'

  CREATE UNIQUE CLUSTERED INDEX clust ON sysdownloadlist(instance_id)
  CREATE NONCLUSTERED     INDEX nc1   ON sysdownloadlist(target_server)
  CREATE NONCLUSTERED     INDEX nc2   ON sysdownloadlist(object_id)
END
go

/**************************************************************/
/* SYSJOBHISTORY                                              */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysjobhistory')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysjobhistory...'

  CREATE TABLE sysjobhistory
  (
  instance_id          INT IDENTITY     NOT NULL,
  job_id               UNIQUEIDENTIFIER NOT NULL,
  step_id              INT              NOT NULL,
  step_name            sysname          NOT NULL,
  sql_message_id       INT              NOT NULL,
  sql_severity         INT              NOT NULL,
  message              NVARCHAR(4000)   NULL,
  run_status           INT              NOT NULL,
  run_date             INT              NOT NULL,
  run_time             INT              NOT NULL,
  run_duration         INT              NOT NULL,
  operator_id_emailed  INT              NOT NULL,
  operator_id_netsent  INT              NOT NULL,
  operator_id_paged    INT              NOT NULL,
  retries_attempted    INT              NOT NULL,
  server               sysname          NOT NULL
  )

  CREATE UNIQUE CLUSTERED INDEX clust ON sysjobhistory(instance_id)
  CREATE NONCLUSTERED     INDEX nc1   ON sysjobhistory(job_id)
END
ELSE
BEGIN
  ALTER TABLE sysjobhistory ALTER COLUMN
    message NVARCHAR(4000)   NULL
END
go


/**************************************************************/
/* sysoriginatingservers                                      */
/**************************************************************/
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysoriginatingservers')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysoriginatingservers...'

CREATE TABLE dbo.sysoriginatingservers
(
  -- There is only a single MSX server record in this table (originating_server_id = 1)
  -- 0 is generated by sysoriginatingservers_view and indicates the local server
  originating_server_id INT      CONSTRAINT CK_originating_server_id_MustBe_1 CHECK (originating_server_id = 1)
                             DEFAULT (1) UNIQUE CLUSTERED,
  originating_server    sysname     NOT NULL UNIQUE NONCLUSTERED,
  --Mark this record as a MSX server entry
  master_server         bit         CONSTRAINT CK_master_server_MustBe_1 CHECK (master_server = 1)
                             DEFAULT (1)
)

END
go


/**************************************************************/
/* trig_sysoriginatingservers_delete                          */
/**************************************************************/
PRINT ''
PRINT 'Creating trigger trig_sysoriginatingservers_delete...'

IF NOT OBJECT_ID('dbo.trig_sysoriginatingservers_delete', 'TR') IS NULL
    DROP TRIGGER dbo.trig_sysoriginatingservers_delete
GO
CREATE TRIGGER dbo.trig_sysoriginatingservers_delete
ON dbo.sysoriginatingservers
FOR DELETE
AS
BEGIN
  SET NOCOUNT ON
  -- Only a single MSX server entry can exist in this table. ie. originating_server_id = 1 and master_server = 1.
  IF((EXISTS (SELECT *
           FROM deleted AS d
                JOIN dbo.sysjobs AS j ON d.originating_server_id = j.originating_server_id)) OR
    (EXISTS (SELECT *
           FROM deleted AS d
                JOIN dbo.sysschedules AS s ON d.originating_server_id = s.originating_server_id)))
  BEGIN
    RAISERROR(14380, -1, -1)
   ROLLBACK TRANSACTION
    RETURN
  END
END
go


/**************************************************************/
/* sysoriginatingservers_view                                 */
/**************************************************************/
PRINT ''
PRINT 'Creating view sysoriginatingservers_view...'
GO
IF (NOT OBJECT_ID(N'dbo.sysoriginatingservers_view', 'V') IS NULL)
  DROP VIEW sysoriginatingservers_view
GO
CREATE VIEW dbo.sysoriginatingservers_view(originating_server_id, originating_server, master_server)
AS
   SELECT
      0 AS originating_server_id,
      UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))) AS originating_server,
      0 AS master_server
   UNION
   SELECT
      originating_server_id,
      originating_server,
      master_server
   FROM
      dbo.sysoriginatingservers
GO

/**************************************************************/
/* SYSJOBS                                                    */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysjobs')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysjobs...'

  CREATE TABLE sysjobs
  (
  job_id                     UNIQUEIDENTIFIER NOT NULL,
  originating_server_id      INT              NOT NULL, -- REFERENCE enforced by trig_sysjobs_insert_update
  name                       sysname          NOT NULL,
  enabled                    TINYINT          NOT NULL,
  description                NVARCHAR(512)    NULL,
  start_step_id              INT              NOT NULL,
  category_id                INT              NOT NULL,
  owner_sid                  VARBINARY(85)    NOT NULL,
  notify_level_eventlog      INT              NOT NULL,
  notify_level_email         INT              NOT NULL,
  notify_level_netsend       INT              NOT NULL,
  notify_level_page          INT              NOT NULL,
  notify_email_operator_id   INT              NOT NULL,
  notify_netsend_operator_id INT              NOT NULL,
  notify_page_operator_id    INT              NOT NULL,
  delete_level               INT              NOT NULL,
  date_created               DATETIME         NOT NULL,
  date_modified              DATETIME         NOT NULL,
  version_number             INT              NOT NULL
  )

  CREATE UNIQUE CLUSTERED INDEX clust ON sysjobs(job_id)
  CREATE NONCLUSTERED     INDEX nc1   ON sysjobs(name) -- NOTE: This is deliberately non-unique
  CREATE NONCLUSTERED     INDEX nc3   ON sysjobs(category_id)
  CREATE NONCLUSTERED     INDEX nc4   ON sysjobs(owner_sid)
END
go

/**************************************************************/
/* trig_sysjobs_insert_update                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating trigger trig_sysjobs_insert_update...'

IF NOT OBJECT_ID('dbo.trig_sysjobs_insert_update', 'TR') IS NULL
    DROP TRIGGER dbo.trig_sysjobs_insert_update
GO
CREATE TRIGGER dbo.trig_sysjobs_insert_update
ON dbo.sysjobs
FOR INSERT, UPDATE
AS
BEGIN
  SET NOCOUNT ON
  -- Disallow the insert or update if the originating_server_id isn't in sysoriginatingservers_view.
  IF (EXISTS (SELECT *
            FROM inserted
           WHERE inserted.originating_server_id NOT IN
                    (SELECT v.originating_server_id
                     FROM sysoriginatingservers_view AS v)))
  BEGIN
   RAISERROR(14379, -1, -1, 'dbo.sysjobs')
   ROLLBACK TRANSACTION
    RETURN
  END
END
go


/**************************************************************/
/* SYSJOBSERVERS                                              */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysjobservers')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysjobservers...'

  CREATE TABLE sysjobservers
  (
  job_id               UNIQUEIDENTIFIER NOT NULL,
  server_id            INT              NOT NULL,
  last_run_outcome     TINYINT          NOT NULL,
  last_outcome_message NVARCHAR(4000)   NULL,
  last_run_date        INT              NOT NULL,
  last_run_time        INT              NOT NULL,
  last_run_duration    INT              NOT NULL
  )

  CREATE CLUSTERED    INDEX clust ON sysjobservers(job_id)
  CREATE NONCLUSTERED INDEX nc1   ON sysjobservers(server_id)
END
ELSE
BEGIN
  ALTER TABLE sysjobservers ALTER COLUMN
    last_outcome_message NVARCHAR(4000)   NULL
END
go

/**************************************************************/
/* SYSJOBS_VIEW                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating view sysjobs_view...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysjobs_view')
              AND (type = 'V')))
  DROP VIEW sysjobs_view
go
CREATE VIEW sysjobs_view
AS
SELECT jobs.job_id,
       svr.originating_server,
       jobs.name,
       jobs.enabled,
       jobs.description,
       jobs.start_step_id,
       jobs.category_id,
       jobs.owner_sid,
       jobs.notify_level_eventlog,
       jobs.notify_level_email,
       jobs.notify_level_netsend,
       jobs.notify_level_page,
       jobs.notify_email_operator_id,
       jobs.notify_netsend_operator_id,
       jobs.notify_page_operator_id,
       jobs.delete_level,
       jobs.date_created,
       jobs.date_modified,
       jobs.version_number,
       jobs.originating_server_id,
       svr.master_server
FROM msdb.dbo.sysjobs as jobs
  JOIN msdb.dbo.sysoriginatingservers_view as svr
    ON jobs.originating_server_id = svr.originating_server_id
  --LEFT JOIN msdb.dbo.sysjobservers js ON jobs.job_id = js.job_id
WHERE (owner_sid = SUSER_SID())
   OR (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
   OR (ISNULL(IS_MEMBER(N'SQLAgentReaderRole'), 0) = 1)
   OR ( (ISNULL(IS_MEMBER(N'TargetServersRole'), 0) = 1) AND
        (EXISTS(SELECT * FROM msdb.dbo.sysjobservers js
         WHERE js.server_id <> 0 AND js.job_id = jobs.job_id))) -- filter out local jobs
go



IF (OBJECT_ID(N'dbo.syssessions', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table syssessions...'

   CREATE TABLE dbo.syssessions
   (
      session_id                INT  IDENTITY PRIMARY KEY,
    agent_start_date          DATETIME NOT NULL
   )
   CREATE UNIQUE NONCLUSTERED INDEX nonclust ON syssessions(agent_start_date)
END
go

IF (OBJECT_ID(N'dbo.sysjobactivity', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysjobactivity...'

   CREATE TABLE dbo.sysjobactivity
   (
      session_id                INT              NOT NULL REFERENCES syssessions(session_id),
      job_id                    UNIQUEIDENTIFIER NOT NULL REFERENCES  sysjobs(job_id) ON DELETE CASCADE,
    run_requested_date        DATETIME         NULL,
    run_requested_source      sysname          NULL,
    queued_date               DATETIME         NULL,
    start_execution_date      DATETIME         NULL,
    last_executed_step_id     INT              NULL,
    last_executed_step_date   DATETIME         NULL,
    stop_execution_date       DATETIME         NULL,
    job_history_id            INT              NULL,      --keeps a reference to the record in sysjobhistory for detailed job information
    next_scheduled_run_date   DATETIME         NULL
   )
   CREATE UNIQUE CLUSTERED    INDEX clust ON sysjobactivity(session_id, job_id)
END
go


/**************************************************************/
/* SYSJOBSTEPS                                                */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysjobsteps')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysjobsteps...'

  CREATE TABLE sysjobsteps
  (
  job_id                UNIQUEIDENTIFIER NOT NULL,
  step_id               INT              NOT NULL,
  step_name             sysname          NOT NULL,
  subsystem             NVARCHAR(40)    NOT NULL,
  command               NVARCHAR(max)    NULL,
  flags                 INT              NOT NULL,
  additional_parameters NVARCHAR(max)    NULL,
  cmdexec_success_code  INT              NOT NULL,
  on_success_action     TINYINT          NOT NULL,
  on_success_step_id    INT              NOT NULL,
  on_fail_action        TINYINT          NOT NULL,
  on_fail_step_id       INT              NOT NULL,
  server                sysname          NULL,      -- Used only by replication and OLAP
  database_name         sysname          NULL,
  database_user_name    sysname          NULL,
  retry_attempts        INT              NOT NULL,
  retry_interval        INT              NOT NULL,
  os_run_priority       INT              NOT NULL,  -- NOTE: Cannot use TINYINT because we need a signed number
  output_file_name      NVARCHAR(200)    NULL,
  last_run_outcome      INT              NOT NULL,
  last_run_duration     INT              NOT NULL,
  last_run_retries      INT              NOT NULL,
  last_run_date         INT              NOT NULL,
  last_run_time         INT              NOT NULL,
  proxy_id              INT              NULL,
  step_uid              UNIQUEIDENTIFIER NULL
  )

  CREATE UNIQUE CLUSTERED INDEX clust ON sysjobsteps(job_id, step_id)
  CREATE UNIQUE NONCLUSTERED INDEX nc1 ON sysjobsteps(job_id, step_name)
  CREATE UNIQUE NONCLUSTERED INDEX nc2 ON sysjobsteps(step_uid)

END
go


/**************************************************************/
/* SYSJOBSTEPSLOGS                                                */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysjobstepslogs')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysjobstepslogs...'

  CREATE TABLE sysjobstepslogs
  (
  log_id                INT IDENTITY (1,1) PRIMARY KEY NOT NULL,
  log                   NVARCHAR(max)    NOT NULL,
  date_created          DATETIME         NOT NULL DEFAULT getdate(),
  date_modified         DATETIME         NOT NULL DEFAULT getdate(),
  log_size              bigint           NOT NULL ,
  step_uid              UNIQUEIDENTIFIER NOT NULL REFERENCES  sysjobsteps(step_uid) ON DELETE CASCADE
  )
END
go


/**************************************************************/
/* SYSSCHEDULES                                               */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysschedules')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysschedules...'

  CREATE TABLE sysschedules
  (
  schedule_id            INT IDENTITY     PRIMARY KEY CLUSTERED,
  schedule_uid           UNIQUEIDENTIFIER NOT NULL,
  originating_server_id  INT              NOT NULL, -- REFERENCE enforced by trig_sysschedules_insert_update
  name                   sysname          NOT NULL,
  owner_sid              varbinary(85)    NOT NULL,
  enabled                INT              NOT NULL,
  freq_type              INT              NOT NULL,
  freq_interval          INT              NOT NULL,
  freq_subday_type       INT              NOT NULL,
  freq_subday_interval   INT              NOT NULL,
  freq_relative_interval INT              NOT NULL,
  freq_recurrence_factor INT              NOT NULL,
  active_start_date      INT              NOT NULL,
  active_end_date        INT              NOT NULL,
  active_start_time      INT              NOT NULL,
  active_end_time        INT              NOT NULL,
  date_created           DATETIME         NOT NULL  DEFAULT (GETDATE()),
  date_modified          DATETIME         NOT NULL  DEFAULT (GETDATE()),
  version_number         INT              NOT NULL  DEFAULT (1)
  )

  -- CREATE UNIQUE CLUSTERED INDEX clust ON sysschedules(job_id, name)
END
go

/**************************************************************/
/* trig_sysschedules_insert_update                            */
/**************************************************************/

PRINT ''
PRINT 'Creating trigger trig_sysschedules_insert_update...'

IF NOT OBJECT_ID('dbo.trig_sysschedules_insert_update', 'TR') IS NULL
    DROP TRIGGER dbo.trig_sysschedules_insert_update
GO
CREATE TRIGGER dbo.trig_sysschedules_insert_update
ON dbo.sysschedules
FOR INSERT, UPDATE
AS
BEGIN
  SET NOCOUNT ON
  -- Disallow the insert or update if the originating_server_id isn't in sysoriginatingservers_view.
  IF (EXISTS (SELECT *
            FROM inserted
           WHERE inserted.originating_server_id NOT IN
                    (SELECT v.originating_server_id
                     FROM sysoriginatingservers_view AS v)))
  BEGIN
   RAISERROR(14379, -1, -1, 'dbo.sysschedules')
   ROLLBACK TRANSACTION
    RETURN
  END
END
go

/**************************************************************/
/* SYSSCHEDULES_LOCALSERVER_VIEW                              */
/**************************************************************/

PRINT ''
PRINT 'Creating view sysschedules_localserver_view...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysschedules_localserver_view')
              AND (type = 'V')))
  DROP VIEW sysschedules_localserver_view
go
CREATE VIEW sysschedules_localserver_view
AS
SELECT sched.schedule_id,
       sched.schedule_uid,
       sched.originating_server_id,
       sched.name,
       sched.owner_sid,
       sched.enabled,
       sched.freq_type,
       sched.freq_interval,
       sched.freq_subday_type,
       sched.freq_subday_interval,
       sched.freq_relative_interval,
       sched.freq_recurrence_factor,
       sched.active_start_date,
       sched.active_end_date,
       sched.active_start_time,
       sched.active_end_time,
       sched.date_created,
       sched.date_modified,
       sched.version_number,
       svr.originating_server,
       svr.master_server
FROM msdb.dbo.sysschedules as sched
    JOIN msdb.dbo.sysoriginatingservers_view as svr
    ON sched.originating_server_id = svr.originating_server_id
WHERE (svr.master_server = 0)
  AND ( (sched.owner_sid = SUSER_SID())
        OR (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
      OR (ISNULL(IS_MEMBER(N'SQLAgentReaderRole'), 0) = 1)
      )
go


/**************************************************************/
/* SYSJOBSCHEDULES                                            */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysjobschedules')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysjobschedules...'

  CREATE TABLE sysjobschedules
  (
  schedule_id            INT              REFERENCES sysschedules(schedule_id),
  job_id                 UNIQUEIDENTIFIER REFERENCES sysjobs(job_id),
  next_run_date          INT              NOT NULL DEFAULT 0,
  next_run_time          INT              NOT NULL DEFAULT 0
  )

  CREATE UNIQUE CLUSTERED INDEX clust ON sysjobschedules(job_id, schedule_id)
  CREATE NONCLUSTERED INDEX [NC_sysjobschedules_schedule_id] ON sysjobschedules(schedule_id)
END
go


/**************************************************************/
/* SYSCATEGORIES                                              */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'syscategories')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table syscategories...'

  CREATE TABLE syscategories
  (
  category_id    INT IDENTITY NOT NULL,
  category_class INT          NOT NULL, -- 1 = Job, 2 = Alert, 3 = Operator
  category_type  TINYINT      NOT NULL, -- 1 = Local, 2 = Multi-Server [Only relevant if class is 1; otherwise, 3 (None)]
  name           sysname      NOT NULL
  )

  CREATE UNIQUE CLUSTERED INDEX clust ON syscategories(name, category_class)
END
go

-- Install standard [permanent] categories (reserved ID range is 0 - 99)
SET IDENTITY_INSERT msdb.dbo.syscategories ON

DELETE FROM msdb.dbo.syscategories
WHERE (category_id < 100)

-- Core categories
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 0, 1, 1, N'[Uncategorized (Local)]')        -- Local default
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 1, 1, 1, N'Jobs from MSX')                  -- All jobs downloaded from the MSX are placed in this category
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 2, 1, 2, N'[Uncategorized (Multi-Server)]') -- Multi-server default
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 3, 1, 1, N'Database Maintenance')           -- Default for all jobs created by the Maintenance Plan Wizard
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 5, 1, 1, N'Full-Text')                      -- Default for all jobs created by the Index Server
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 6, 1, 1, N'Log Shipping')                   -- Default for Log Shipping jobs
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 7, 1, 1, N'Database Engine Tuning Advisor') -- Default for DTA jobs
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES ( 8, 1, 1, N'Data Collector')                   -- Default for all jobs created by the Data Collector
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (98, 2, 3, N'[Uncategorized]')                -- Alert default
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (99, 3, 3, N'[Uncategorized]')                -- Operator default

-- Replication categories
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (10, 1, 1, N'REPL-Distribution')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (11, 1, 1, N'REPL-Distribution Cleanup')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (12, 1, 1, N'REPL-History Cleanup')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (13, 1, 1, N'REPL-LogReader')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (14, 1, 1, N'REPL-Merge')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (15, 1, 1, N'REPL-Snapshot')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (16, 1, 1, N'REPL-Checkup')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (17, 1, 1, N'REPL-Subscription Cleanup')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (18, 1, 1, N'REPL-Alert Response')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (19, 1, 1, N'REPL-QueueReader')
INSERT INTO msdb.dbo.syscategories (category_id, category_class, category_type, name) VALUES (20, 2, 3, N'Replication')

SET IDENTITY_INSERT msdb.dbo.syscategories OFF
go

/**************************************************************/
/* SYSTARGETSERVERS                                           */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'systargetservers')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table systargetservers...'

  CREATE TABLE systargetservers
  (
  server_id               INT IDENTITY  NOT NULL,
  server_name             sysname       NOT NULL,
  location                NVARCHAR(200) NULL,
  time_zone_adjustment    INT           NOT NULL,  -- The offset from GMT in minutes (set by sp_msx_enlist)
  enlist_date             DATETIME      NOT NULL,
  last_poll_date          DATETIME      NOT NULL,
  status                  INT           NOT NULL,  -- 1 = Normal, 2 = Offline, 4 = Blocked
  local_time_at_last_poll DATETIME      NOT NULL,  -- The local time at the target server as-of the last time it polled the MSX
  enlisted_by_nt_user     NVARCHAR(100) NOT NULL,
  poll_interval           INT           NOT NULL   -- The MSX polling interval (in seconds)
  )

  EXECUTE sys.sp_bindefault default_current_date, N'systargetservers.enlist_date'
  EXECUTE sys.sp_bindefault default_current_date, N'systargetservers.last_poll_date'
  EXECUTE sys.sp_bindefault default_one,          N'systargetservers.status'

  CREATE UNIQUE CLUSTERED    INDEX clust ON systargetservers(server_id)
  CREATE UNIQUE NONCLUSTERED INDEX nc1   ON systargetservers(server_name)
END
go

/**************************************************************/
/* SYSTARGETSERVERS_VIEW                                      */
/**************************************************************/

PRINT ''
PRINT 'Creating view systargetservers_view...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'systargetservers_view')
              AND (type = 'V')))
  DROP VIEW systargetservers_view
go
CREATE VIEW systargetservers_view
AS
SELECT server_id,
       server_name,
       enlist_date,
       last_poll_date
FROM msdb.dbo.systargetservers
UNION
SELECT 0,
       CONVERT(sysname, SERVERPROPERTY('ServerName')),
       CONVERT(DATETIME, N'19981113', 112),
       CONVERT(DATETIME, N'19981113', 112)
go

/**************************************************************/
/* SYSTARGETSERVERGROUPS                                      */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'systargetservergroups')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table systargetservergroups...'

  CREATE TABLE systargetservergroups
  (
  servergroup_id INT IDENTITY NOT NULL,
  name           sysname      NOT NULL
  )

  CREATE UNIQUE CLUSTERED INDEX clust ON systargetservergroups(name)
END
go

/**************************************************************/
/* SYSTARGETSERVERGROUPMEMBERS                                */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'systargetservergroupmembers')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table systargetservergroupmembers...'

  CREATE TABLE systargetservergroupmembers
  (
  servergroup_id INT NOT NULL,
  server_id      INT NOT NULL
  )

  CREATE UNIQUE CLUSTERED INDEX clust ON systargetservergroupmembers(servergroup_id, server_id)
  CREATE NONCLUSTERED     INDEX nc1   ON systargetservergroupmembers(server_id)
END
go

/**************************************************************/
/* SYSALERTS                                                  */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysalerts')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysalerts...'

  CREATE TABLE sysalerts
  (
  id                        INT IDENTITY     NOT NULL,
  name                      sysname          NOT NULL, -- Was length 60 in 6.x
  event_source              NVARCHAR(100)    NOT NULL,
  event_category_id         INT              NULL,
  event_id                  INT              NULL,
  message_id                INT              NOT NULL, -- Was NULL in 6.x
  severity                  INT              NOT NULL, -- Was NULL in 6.x
  enabled                   TINYINT          NOT NULL,
  delay_between_responses   INT              NOT NULL,
  last_occurrence_date      INT              NOT NULL, -- Was NULL in 6.x
  last_occurrence_time      INT              NOT NULL, -- Was NULL in 6.x
  last_response_date        INT              NOT NULL, -- Was NULL in 6.x
  last_response_time        INT              NOT NULL, -- Was NULL in 6.x
  notification_message      NVARCHAR(512)    NULL,
  include_event_description TINYINT          NOT NULL,
  database_name             NVARCHAR(512)    NULL,
  event_description_keyword NVARCHAR(100)    NULL,
  occurrence_count          INT              NOT NULL,
  count_reset_date          INT              NOT NULL, -- Was NULL in 6.x
  count_reset_time          INT              NOT NULL, -- Was NULL in 6.x
  job_id                    UNIQUEIDENTIFIER NOT NULL, -- Was NULL in 6.x
  has_notification          INT              NOT NULL, -- New for 7.0
  flags                     INT              NOT NULL, -- Was NULL in 6.x
  performance_condition     NVARCHAR(512)    NULL,
  category_id               INT              NOT NULL  -- New for 7.0
  )

  CREATE UNIQUE CLUSTERED INDEX ByName ON sysalerts(name)
  CREATE UNIQUE INDEX ByID ON sysalerts(id)
END
go

/**************************************************************/
/* sysalerts_performance_counters_view                        */
/**************************************************************/
PRINT ''
PRINT 'Creating view sysalerts_performance_counters_view...'
go
IF (NOT OBJECT_ID(N'dbo.sysalerts_performance_counters_view', 'V') IS NULL)
  DROP VIEW sysalerts_performance_counters_view
go

CREATE VIEW sysalerts_performance_counters_view
AS
    -- Parse object_name 'SQLServer:Buffer Manager', exclude instance specific info; return as 'Buffer Manager'
    SELECT RTRIM(SUBSTRING(pc.object_name, CHARINDEX(':', pc.object_name)+1, DATALENGTH(pc.object_name))) AS 'object_name',
            RTRIM(pc.counter_name) AS 'counter_name',
            CASE WHEN pc.instance_name IS NULL
                THEN NULL
                ELSE RTRIM(pc.instance_name)
            END AS 'instance_name',
            pc.cntr_value,
            pc.cntr_type,
            SERVERPROPERTY('ServerName') AS 'server_name'
    FROM sys.dm_os_performance_counters pc
GO

/**************************************************************/
/* SYSOPERATORS                                               */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysoperators')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysoperators...'

  CREATE TABLE sysoperators
  (
  id                        INT IDENTITY  NOT NULL,
  name                      sysname       NOT NULL, -- Was length 50 in 6.x
  enabled                   TINYINT       NOT NULL,
  email_address             NVARCHAR(100) NULL,
  last_email_date           INT           NOT NULL, -- Was NULL in 6.x
  last_email_time           INT           NOT NULL, -- Was NULL in 6.x
  pager_address             NVARCHAR(100) NULL,
  last_pager_date           INT           NOT NULL, -- Was NULL in 6.x
  last_pager_time           INT           NOT NULL, -- Was NULL in 6.x
  weekday_pager_start_time  INT           NOT NULL,
  weekday_pager_end_time    INT           NOT NULL,
  saturday_pager_start_time INT           NOT NULL,
  saturday_pager_end_time   INT           NOT NULL,
  sunday_pager_start_time   INT           NOT NULL,
  sunday_pager_end_time     INT           NOT NULL,
  pager_days                TINYINT       NOT NULL,
  netsend_address           NVARCHAR(100) NULL,     -- New for 7.0
  last_netsend_date         INT           NOT NULL, -- New for 7.0
  last_netsend_time         INT           NOT NULL, -- New for 7.0
  category_id               INT           NOT NULL  -- New for 7.0
  )

  CREATE UNIQUE CLUSTERED INDEX ByName ON sysoperators(name)
  CREATE UNIQUE INDEX ByID ON sysoperators(id)
END
go

/**************************************************************/
/* SYSNOTIFICATIONS                                           */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysnotifications')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysnotifications...'

  CREATE TABLE sysnotifications
  (
  alert_id             INT      NOT NULL,
  operator_id          INT      NOT NULL,
  notification_method  TINYINT  NOT NULL
  )

  CREATE UNIQUE CLUSTERED INDEX ByAlertIDAndOperatorID ON sysnotifications(alert_id, operator_id)
END
go

/**************************************************************/
/* SYSTASKIDS                                                 */
/*                                                            */
/* This table provides a mapping between new GUID job ID's    */
/* and 6.x INT task ID's.                                     */
/* Entries are made in this table for all existing 6.x tasks  */
/* and for all new tasks added using the 7.0 version of       */
/* sp_addtask.                                                */
/* Callers of the 7.0 version of sp_helptask will ONLY see    */
/* tasks [jobs] that have a corresponding entry in this table */
/* [IE. Jobs created with sp_add_job will not be returned].   */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'systaskids')
                  AND (type = 'U')))
BEGIN
  CREATE TABLE systaskids
  (
  task_id INT IDENTITY     NOT NULL,
  job_id  UNIQUEIDENTIFIER NOT NULL
  )

  CREATE CLUSTERED INDEX clust ON systaskids(job_id)
END
go

/**************************************************************/
/* SYSCACHEDCREDENTIALS                                       */
/*                                                            */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'syscachedcredentials')
                  AND (type = 'U')))
BEGIN
  CREATE TABLE syscachedcredentials
  (
  login_name          sysname      COLLATE database_default NOT NULL PRIMARY KEY,
  has_server_access   BIT          NOT NULL DEFAULT 0,
  is_sysadmin_member  BIT          NOT NULL DEFAULT 0,
  cachedate           DATETIME     NOT NULL DEFAULT getdate()
  )
END
go


/**************************************************************/
/*                                                            */
/*        C  O  R  E     P  R  O  C  E  D  U  R  E  S         */
/*                                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating function SQLAGENT_SUSER_SNAME ...'
IF (NOT OBJECT_ID(N'dbo.SQLAGENT_SUSER_SNAME', 'FN') IS NULL)
  DROP FUNCTION dbo.SQLAGENT_SUSER_SNAME
go

CREATE FUNCTION dbo.SQLAGENT_SUSER_SNAME(@user_sid VARBINARY(85)) RETURNS sysname
AS
BEGIN
  DECLARE @ret sysname
  IF @user_sid = 0xFFFFFFFF
    SELECT @ret = N'$(SQLAgentAccount)'
  ELSE
    SELECT @ret = SUSER_SNAME(@user_sid)
  RETURN @ret
END
go

PRINT ''
PRINT 'Creating function SQLAGENT_SUSER_SID ...'
IF (NOT OBJECT_ID(N'dbo.SQLAGENT_SUSER_SID', 'FN') IS NULL)
  DROP FUNCTION dbo.SQLAGENT_SUSER_SID
go

CREATE FUNCTION dbo.SQLAGENT_SUSER_SID(@user_name sysname) RETURNS VARBINARY(85)
AS
BEGIN
  DECLARE @ret VARBINARY(85)
  IF @user_name = N'$(SQLAgentAccount)'
    SELECT @ret = 0xFFFFFFFF
  ELSE
    SELECT @ret = SUSER_SID(@user_name, 0)
  RETURN @ret
END
go

-----------------------------------------------------------
-- get_principal_id : retrieves principal_id for a given sid
--
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.get_principal_id', 'FN') IS NULL
    DROP FUNCTION dbo.get_principal_id
GO

CREATE FUNCTION dbo.get_principal_id(@principal_sid varbinary(85))
RETURNS int
AS
BEGIN
    DECLARE @principal_id int
    SELECT @principal_id=principal_id FROM msdb.sys.database_principals WHERE sid=@principal_sid
    RETURN @principal_id
END
GO

-----------------------------------------------------------
-- get_principal_sid : retrieves principal sid from principal_id
--
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.get_principal_sid', 'FN') IS NULL
    DROP FUNCTION dbo.get_principal_sid
GO

CREATE FUNCTION dbo.get_principal_sid(@principal_id int)
RETURNS varbinary(85)
AS
BEGIN
    DECLARE @principal_sid varbinary(85)
    SELECT @principal_sid=sid FROM msdb.sys.database_principals WHERE principal_id=@principal_id
    RETURN @principal_sid
END
GO
/**************************************************************/
/* SP_SQLAGENT_IS_SRVROLEMEMBER                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure SP_SQLAGENT_IS_SRVROLEMEMBER...'
IF (NOT OBJECT_ID(N'dbo.sp_sqlagent_is_srvrolemember', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_sqlagent_is_srvrolemember
go

CREATE PROCEDURE sp_sqlagent_is_srvrolemember
   @role_name sysname, @login_name sysname
AS
BEGIN
  DECLARE @is_member        INT
  SET NOCOUNT ON

  IF @role_name IS NULL OR @login_name IS NULL
    RETURN(0)

  SELECT @is_member = 0
  --IS_SRVROLEMEMBER works only if the login to be tested is provisioned with sqlserver
  if( @login_name = SUSER_SNAME())
    SELECT @is_member = IS_SRVROLEMEMBER(@role_name)
  else
    SELECT @is_member = IS_SRVROLEMEMBER(@role_name, @login_name)


  --try to impersonate. A try catch is used because we can have @name as NT groups also
  IF @is_member IS NULL
  BEGIN
    BEGIN TRY
      if( is_srvrolemember('sysadmin') = 1)
      begin
      EXECUTE AS LOGIN = @login_name -- impersonate
        SELECT @is_member = IS_SRVROLEMEMBER(@role_name)  -- check role membership
      REVERT -- revert back
      end
    END TRY
    BEGIN CATCH
      SELECT @is_member = 0
    END CATCH
  END

  RETURN ISNULL(@is_member,0)
END
go

/**************************************************************/
/* sp_get_traceflag_status_internal - For a given trace flag, */
/* returns if trace flag was enabled or disabled              */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_get_traceflag_status_internal...'
IF (NOT OBJECT_ID(N'dbo.sp_get_traceflag_status_internal', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_get_traceflag_status_internal
go

CREATE PROCEDURE sp_get_traceflag_status_internal
   @traceflag INT,
   @status INT OUTPUT
AS
BEGIN
    SET @status = NULL

    IF(@traceflag IS NOT NULL)
    BEGIN
        DECLARE @traceStatus TABLE
        (
            TraceFlag int,
            [Status] int,
            [Global] int,
            [Session] int
        )
        INSERT INTO @traceStatus
        EXEC ('DBCC TRACESTATUS (-1) WITH NO_INFOMSGS')

        SELECT @status = [Status]
        FROM @traceStatus
        WHERE TraceFlag = @traceflag
    END
END
GO

/**************************************************************/
/* SP_VERIFY_CATEGORY_IDENTIFIERS                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_category_identifiers...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_category_identifiers', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_category_identifiers
go

CREATE PROCEDURE sp_verify_category_identifiers
   @name_of_name_parameter [varchar](60),
   @name_of_id_parameter [varchar](60),
   @category_name [sysname] OUTPUT,
   @category_id [INT] OUTPUT
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @category_id_as_char NVARCHAR(36)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name_of_name_parameter = LTRIM(RTRIM(@name_of_name_parameter))
  SELECT @name_of_id_parameter   = LTRIM(RTRIM(@name_of_id_parameter))
  SELECT @category_name          = LTRIM(RTRIM(@category_name))

  IF (@category_name = N'') SELECT @category_name = NULL

  IF ((@category_name IS NOT NULL) AND (@category_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, @name_of_id_parameter, @name_of_name_parameter)
    RETURN(1) -- Failure
  END

  -- Check category id
  IF (@category_id IS NOT NULL)
  BEGIN
    SELECT @category_name = name
    FROM msdb.dbo.syscategories
    WHERE (category_id = @category_id)
    IF (@category_name IS NULL)
    BEGIN
     SELECT @category_id_as_char = CONVERT(nvarchar(36), @category_id)
      RAISERROR(14262, -1, -1, '@category_id', @category_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check category name
  IF (@category_name IS NOT NULL)
  BEGIN
    -- The name is not ambiguous, so get the corresponding category_id (if the job exists)
    SELECT @category_id = category_id
    FROM msdb.dbo.syscategories
    WHERE (name = @category_name)
    IF (@category_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@category_name', @category_name)
      RETURN(1) -- Failure
    END
  END

  RETURN(0) -- Success
END
go

PRINT ''
PRINT 'Creating function agent_datetime...'
IF (NOT OBJECT_ID(N'dbo.agent_datetime', 'FN') IS NULL)
  DROP FUNCTION dbo.agent_datetime
go

CREATE FUNCTION agent_datetime(@date int, @time int)
RETURNS DATETIME
AS
BEGIN
 RETURN
  (
    CONVERT(DATETIME,
          CONVERT(NVARCHAR(4),@date / 10000) + N'-' +
          CONVERT(NVARCHAR(2),(@date % 10000)/100)  + N'-' +
          CONVERT(NVARCHAR(2),@date % 100) + N' ' +
          CONVERT(NVARCHAR(2),@time / 10000) + N':' +
          CONVERT(NVARCHAR(2),(@time % 10000)/100) + N':' +
          CONVERT(NVARCHAR(2),@time % 100),
    120)
  )
END
go

/**************************************************************/
/* SP_VERIFY_PROXY_IDENTIFIERS                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_proxy_identifiers...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_proxy_identifiers', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_proxy_identifiers
go

CREATE PROCEDURE sp_verify_proxy_identifiers
   @name_of_name_parameter [varchar](60),
   @name_of_id_parameter [varchar](60),
   @proxy_name [sysname] OUTPUT,
   @proxy_id [INT] OUTPUT
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @proxy_id_as_char NVARCHAR(36)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name_of_name_parameter = LTRIM(RTRIM(@name_of_name_parameter))
  SELECT @name_of_id_parameter   = LTRIM(RTRIM(@name_of_id_parameter))
  SELECT @proxy_name             = LTRIM(RTRIM(@proxy_name))

  IF (@proxy_name = N'') SELECT @proxy_name = NULL

  IF ((@proxy_name IS NULL)     AND (@proxy_id IS NULL)) OR
     ((@proxy_name IS NOT NULL) AND (@proxy_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, @name_of_id_parameter, @name_of_name_parameter)
    RETURN(1) -- Failure
  END

  -- Check proxy id
  IF (@proxy_id IS NOT NULL)
  BEGIN
    SELECT @proxy_name = name
    FROM msdb.dbo.sysproxies
    WHERE (proxy_id = @proxy_id)
    IF (@proxy_name IS NULL)
    BEGIN
     SELECT @proxy_id_as_char = CONVERT(nvarchar(36), @proxy_id)
      RAISERROR(14262, -1, -1, @name_of_id_parameter, @proxy_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check proxy name
  IF (@proxy_name IS NOT NULL)
  BEGIN
    -- The name is not ambiguous, so get the corresponding proxy_id (if the job exists)
    SELECT @proxy_id = proxy_id
    FROM msdb.dbo.sysproxies
    WHERE (name = @proxy_name)
    IF (@proxy_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, @name_of_name_parameter, @proxy_name)
      RETURN(1) -- Failure
    END
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_VERIFY_CREDENTIAL_IDENTIFIERS                           */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_verify_credential_identifiers...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_credential_identifiers', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_credential_identifiers
go

CREATE PROCEDURE sp_verify_credential_identifiers
   @name_of_name_parameter [varchar](60),
   @name_of_id_parameter [varchar](60),
   @credential_name [sysname] OUTPUT,
   @credential_id [INT] OUTPUT,
   @allow_only_windows_credential bit = NULL
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @credential_id_as_char NVARCHAR(36)
  DECLARE @credential_identity NVARCHAR(4000)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name_of_name_parameter = LTRIM(RTRIM(@name_of_name_parameter))
  SELECT @name_of_id_parameter   = LTRIM(RTRIM(@name_of_id_parameter))
  SELECT @credential_name        = LTRIM(RTRIM(@credential_name))

  IF (@credential_name = N'') SELECT @credential_name = NULL

  IF ((@credential_name IS NULL)     AND (@credential_id IS NULL)) OR
     ((@credential_name IS NOT NULL) AND (@credential_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, @name_of_id_parameter, @name_of_name_parameter)
    RETURN(1) -- Failure
  END

  -- Check credential_id
  IF (@credential_id IS NOT NULL)
  BEGIN
    SELECT @credential_name = name,
    @credential_identity = credential_identity
    FROM sys.credentials
    WHERE (credential_id = @credential_id)

    IF (@credential_name IS NULL)
    BEGIN
     SELECT @credential_id_as_char = CONVERT(nvarchar(36), @credential_id)
      RAISERROR(14262, -1, -1, '@credential_id', @credential_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check credential name
  IF (@credential_name IS NOT NULL)
  BEGIN
      -- The name is not ambiguous, so get the corresponding credential_id (if the job exists)
    SELECT @credential_id = credential_id,
    @credential_identity = credential_identity
    FROM sys.credentials
    WHERE (name = @credential_name)

    IF (@credential_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@credential_name', @credential_name)
      RETURN(1) -- Failure
    END
  END

  IF(@allow_only_windows_credential IS NOT NULL)
  BEGIN
    IF(@allow_only_windows_credential = 1)
    BEGIN
       -- Allow only windows credentials. ( domain\user format)
       IF(CHARINDEX(N'\', @credential_identity) = 0)
       BEGIN
          RAISERROR(14720, -1, -1, '@credential_name', @credential_name)
          RETURN(1) -- Failure
       END
    END
  END

  RETURN(0) -- Success
END
go


/**************************************************************/
/* sp_verify_subsystems                                       */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_subsystems...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_subsystems', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_subsystems
GO

CREATE PROCEDURE dbo.sp_verify_subsystems
   @syssubsytems_refresh_needed BIT = 0
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @retval         INT
    DECLARE @VersionRootPath nvarchar(512)
    DECLARE @ComRootPath nvarchar(512)
    DECLARE @DtsRootPath nvarchar(512)
    DECLARE @SQLPSPath nvarchar(512)
    DECLARE @DTExec nvarchar(512)
    DECLARE @DTExecExists INT
    DECLARE @ToolsPath nvarchar(512)
    DECLARE @PathSeparator nvarchar(2)

    SELECT @PathSeparator = CAST(SERVERPROPERTY('pathseparator') as nvarchar(2))

    IF ( (@syssubsytems_refresh_needed=1) OR (NOT EXISTS(select * from syssubsystems)) )
    BEGIN
        EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\Microsoft Sql Server\150', N'VerSpecificRootDir', @VersionRootPath OUTPUT

        IF @VersionRootPath IS NULL
        BEGIN
            RAISERROR(14659, -1, -1) WITH LOG
            RETURN(1)
        END

        EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\Microsoft SQL Server\150\SSIS\Setup\DTSPath', N'', @DtsRootPath OUTPUT, N'no_output'

        IF (@DtsRootPath IS NOT NULL)
        BEGIN
            SELECT @DtsRootPath  = @DtsRootPath  + N'Binn' + @PathSeparator
            SELECT @DTExec = @DtsRootPath + N'DTExec.exe'
            CREATE TABLE #t (file_exists int, is_directory int, parent_directory_exists int)
            INSERT #t EXEC xp_fileexist @DTExec
            SELECT TOP 1 @DTExecExists=file_exists from #t
            DROP TABLE #t
            IF ((@DTExecExists IS NULL) OR (@DTExecExists = 0))
            BEGIN
                SET @DtsRootPath = NULL
            END
        END

        SELECT @ComRootPath  = @VersionRootPath  + N'COM' + @PathSeparator

        DECLARE @edition nvarchar(256)
        DECLARE @bitness int
        SELECT @edition = @@version
        SET @bitness = CASE WHEN @edition like '%(X64)%' THEN 64 ELSE 32 END
        -- Get tools path from the registry. It's in Wow32 subtree on Windows and in the main tree otherwise
        IF (@bitness = 64 AND EXISTS (SELECT 1 FROM master.sys.dm_os_windows_info WHERE windows_release<>N''))
        BEGIN
            EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Wow6432Node\Microsoft\Microsoft Sql Server\150\Tools\ClientSetup', N'SQLPath', @ToolsPath OUTPUT
        END
        ELSE
        BEGIN
            EXEC master.dbo.xp_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\Microsoft Sql Server\150\Tools\ClientSetup', N'SQLPath', @ToolsPath OUTPUT
        END

        SELECT @SQLPSPath  = CONCAT(@ToolsPath, @PathSeparator, N'Binn', @PathSeparator, N'SQLPS.exe')

        -- Procedure must start its own transaction if we don't have one already.
        DECLARE @TranCounter INT;
        SET @TranCounter = @@TRANCOUNT;
        IF @TranCounter = 0
        BEGIN
            BEGIN TRANSACTION;
        END

        -- backup subsystem's max worker thread setting
        DECLARE @subsystemsettings TABLE
        (
            subsystem          NVARCHAR(40) COLLATE database_default NOT NULL,
            max_worker_threads INT           NULL
        )

        INSERT INTO @subsystemsettings
        SELECT
        subsystem, max_worker_threads
        FROM  syssubsystems

        -- Fix for #525111 - when MSDB is restored from any other sqlserver, it is possible that physical path to agent_exe, subsystem_dll may not be valid on current server
        --  It is better to delete all records in this table and reinsert them again
        -- perform delete and re-insert operations within a transaction
        TRUNCATE TABLE syssubsystems

        DECLARE @processor_count INT
        SELECT @processor_count=cpu_count FROM sys.dm_os_sys_info

        BEGIN TRY
            --create subsystems
            --TSQL subsystem
            INSERT syssubsystems
            VALUES
            (
                1, N'TSQL',14556, FORMATMESSAGE(14557), FORMATMESSAGE(14557), FORMATMESSAGE(14557), FORMATMESSAGE(14557), FORMATMESSAGE(14557), 20 * @processor_count
            )

            --CmdExec subsystem
            INSERT syssubsystems
            VALUES
            (
                3, N'CmdExec', 14550,  N'SQLCMDSS.DLL',NULL,N'CmdExecStart',N'CmdEvent',N'CmdExecStop', 10 * @processor_count
            )

            --Snapshot subsystem
            INSERT syssubsystems
            VALUES
            (
                4, N'Snapshot',   14551, N'SQLREPSS.DLL', @ComRootPath + N'SNAPSHOT.EXE', N'ReplStart',N'ReplEvent',N'ReplStop',100 * @processor_count
            )

            --LogReader subsystem
            INSERT syssubsystems
            VALUES
            (
                5, N'LogReader',  14552, N'SQLREPSS.DLL', @ComRootPath + N'logread.exe',N'ReplStart',N'ReplEvent',N'ReplStop',25 * @processor_count
            )

            --Distribution subsystem
            INSERT syssubsystems
            VALUES
            (
                6, N'Distribution',  14553,  N'SQLREPSS.DLL', @ComRootPath + N'DISTRIB.EXE',N'ReplStart',N'ReplEvent',N'ReplStop',100 * @processor_count
            )

            --Merge subsystem
            INSERT syssubsystems
            VALUES
            (
                7, N'Merge',   14554,  N'SQLREPSS.DLL',@ComRootPath + N'REPLMERG.EXE',N'ReplStart',N'ReplEvent',N'ReplStop',100 * @processor_count
            )

            --QueueReader subsystem
            INSERT syssubsystems
            VALUES
            (
                8, N'QueueReader',   14581,  N'SQLREPSS.dll',@ComRootPath + N'qrdrsvc.exe',N'ReplStart',N'ReplEvent',N'ReplStop',100 * @processor_count
            )

            --ANALYSISQUERY subsystem
            INSERT syssubsystems
            VALUES
            (
                9, N'ANALYSISQUERY', 14513, N'SQLOLAPSS.DLL',NULL,N'OlapStart',N'OlapQueryEvent',N'OlapStop',100 * @processor_count
            )

            --ANALYSISCOMMAND subsystem
            INSERT syssubsystems
            VALUES
            (
                10, N'ANALYSISCOMMAND', 14514, N'SQLOLAPSS.DLL',NULL,N'OlapStart',N'OlapCommandEvent',N'OlapStop',100 * @processor_count
            )

            IF(@DtsRootPath IS NOT NULL)
            BEGIN
                --DTS subsystem
                INSERT syssubsystems
                VALUES
                (
	                11, N'SSIS', 14538,  N'SQLDTSSS.DLL',@DtsRootPath + N'DTExec.exe',N'DtsStart',N'DtsEvent',N'DtsStop',100 * @processor_count
                )
            END

            --PowerShell subsystem
            INSERT syssubsystems
            VALUES
            (
                    12, N'PowerShell', 14698,  N'SQLPOWERSHELLSS.DLL', @SQLPSPath, N'PowerShellStart',N'PowerShellEvent',N'PowerShellStop',2
            )

            -- restore back subsystem's max_worker thread setting(s)
            UPDATE syssubsystems
            SET max_worker_threads = se.max_worker_threads
            FROM syssubsystems sub, @subsystemsettings se
            WHERE sub.subsystem = se.subsystem

        END TRY
        BEGIN CATCH
            DECLARE @ErrorMessage NVARCHAR(400)
            DECLARE @ErrorSeverity INT
            DECLARE @ErrorState INT

            SELECT @ErrorMessage = ERROR_MESSAGE()
            SELECT @ErrorSeverity = ERROR_SEVERITY()
            SELECT @ErrorState = ERROR_STATE()

            -- Roll back the transaction that we started if we are not nested
            IF @TranCounter = 0
            BEGIN
                ROLLBACK TRANSACTION;
            END

            -- if we are nested inside another transaction just raise the
            -- error and let the outer transaction do the rollback
            RAISERROR (@ErrorMessage, -- Message text.
                    @ErrorSeverity, -- Severity.
                    @ErrorState -- State.
                    )
            RETURN (1)
        END CATCH
    END --(NOT EXISTS(select * from syssubsystems))

    -- commit the transaction we started
    IF @TranCounter = 0
    BEGIN
        COMMIT TRANSACTION;
    END

    RETURN(0) -- Success
END
GO

/**************************************************************/
/* sp_verify_subsystem_identifiers                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_subsystem_identifiers...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_subsystem_identifiers', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_subsystem_identifiers
go

CREATE PROCEDURE dbo.sp_verify_subsystem_identifiers
   @name_of_name_parameter [varchar](60),
   @name_of_id_parameter [varchar](60),
   @subsystem_name [sysname] OUTPUT,
   @subsystem_id [INT] OUTPUT
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @subsystem_id_as_char NVARCHAR(36)

  SET NOCOUNT ON

  -- this call will populate subsystems table if necessary
  EXEC @retval = msdb.dbo.sp_verify_subsystems
  IF @retval <> 0
     RETURN(@retval)

  -- Remove any leading/trailing spaces from parameters
  SELECT @name_of_name_parameter = LTRIM(RTRIM(@name_of_name_parameter))
  SELECT @name_of_id_parameter   = LTRIM(RTRIM(@name_of_id_parameter))
  SELECT @subsystem_name         = LTRIM(RTRIM(@subsystem_name))

  IF (@subsystem_name = N'') SELECT @subsystem_name = NULL

  IF ((@subsystem_name IS NULL)     AND (@subsystem_id IS NULL)) OR
     ((@subsystem_name IS NOT NULL) AND (@subsystem_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, @name_of_id_parameter, @name_of_name_parameter)
    RETURN(1) -- Failure
  END

  -- Check subsystem_id
  IF (@subsystem_id IS NOT NULL)
  BEGIN
    SELECT @subsystem_name = subsystem
    FROM msdb.dbo.syssubsystems
    WHERE (subsystem_id = @subsystem_id)
    IF (@subsystem_name IS NULL)
    BEGIN
     SELECT @subsystem_id_as_char = CONVERT(nvarchar(36), @subsystem_id)
      RAISERROR(14262, -1, -1, '@subsystem_id', @subsystem_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check subsystem name
  IF (@subsystem_name IS NOT NULL)
  BEGIN
    -- Make sure Dts is translated into new subsystem's name SSIS
    IF UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS) = N'DTS'
    BEGIN
      SET @subsystem_name = N'SSIS'
    END

    -- The name is not ambiguous, so get the corresponding subsystem_id (if the subsystem exists)
    SELECT @subsystem_id = subsystem_id
    FROM msdb.dbo.syssubsystems
    WHERE (UPPER(subsystem collate SQL_Latin1_General_CP1_CS_AS) = UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS))
    IF (@subsystem_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@subsystem_name', @subsystem_name)
      RETURN(1) -- Failure
    END
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* sp_verify_login_identifiers                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_login_identifiers...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_login_identifiers', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_login_identifiers
go

CREATE PROCEDURE dbo.sp_verify_login_identifiers
   @login_name [nvarchar](256),
   @fixed_server_role [nvarchar](256),
   @msdb_role [nvarchar](256),
   @name [nvarchar](256) OUTPUT,
  @sid  varbinary(85)   OUTPUT,
   @flags   INT OUTPUT
AS
BEGIN
   DECLARE @retval         INT
    DECLARE @raise_error    bit
   SET NOCOUNT ON

   SELECT @flags = -1, @raise_error = 0
  SELECT @sid = NULL

  IF @login_name IS NOT NULL
   BEGIN
      --check validity
      --use the new optional parameter of SUSER_SID to have a case insensitive comparation for NT users
    SELECT @sid = SUSER_SID(@login_name, 0)
      IF @sid IS NULL
      BEGIN
         RAISERROR(14520, -1, -1, @login_name)
         RETURN(1) -- Failure
      END
      SELECT @name = @login_name, @flags = 0
   END

   IF COALESCE(@login_name, @fixed_server_role, @msdb_role) IS NULL
   BEGIN
      RAISERROR(14519, -1, -1)
      RETURN(1) -- Failure
   END

  IF @fixed_server_role IS NOT NULL  AND @flags <> -1
      SELECT @raise_error = 1
   ELSE IF @fixed_server_role IS NOT NULL
   --check validity
   BEGIN
      -- IS_SRVROLEMEMBER return NULL for an invalid server role
      IF ISNULL(IS_SRVROLEMEMBER(@fixed_server_role), -1) = -1
      BEGIN
         RAISERROR(14521, -1, -1, @fixed_server_role)
         RETURN(1) -- Failure
      END
      SELECT @name = @fixed_server_role, @flags = 1
    SELECT @sid = SUSER_SID(@fixed_server_role)
   END

  IF @msdb_role IS NOT NULL  AND @flags <> -1
      SELECT @raise_error = 1
   ELSE IF @msdb_role IS NOT NULL
   BEGIN
      --check the correctness of msdb role
      IF ISNULL(IS_MEMBER(@msdb_role), -1) = -1
      BEGIN
         RAISERROR(14522, -1, -1, @msdb_role)
         RETURN(1) -- Failure
      END
      SELECT @sid = sid from sys.database_principals
      WHERE  UPPER(@msdb_role collate SQL_Latin1_General_CP1_CS_AS) = UPPER(name collate SQL_Latin1_General_CP1_CS_AS)
    AND type = 'R'
    IF @sid IS NULL
      BEGIN
         RAISERROR(14522, -1, -1, @msdb_role)
         RETURN(1) -- Failure
      END
      SELECT @name = @msdb_role, @flags = 2
   END

   IF    @raise_error = 1
   BEGIN
      RAISERROR(14519, -1, -1)
      RETURN(1) -- Failure
   END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_VERIFY_PROXY                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_proxy
go

CREATE PROCEDURE dbo.sp_verify_proxy
   @proxy_id [INT] = NULL,
   @proxy_name [sysname],
   @enabled [tinyint],
   @description [nvarchar](512) = NULL
AS
BEGIN
  DECLARE @return_code INT
  SET NOCOUNT ON

  -- Check if the NewName is unique
  IF (EXISTS ( SELECT *
               FROM msdb.dbo.sysproxies
               WHERE (name = @proxy_name) AND
            proxy_id <> ISNULL(@proxy_id,0) ))
  BEGIN
    RAISERROR(14261, 16, 1, '@name', @proxy_name)
    RETURN(1) -- Failure
  END

  -- Enabled must be 0 or 1
  IF (@enabled NOT IN (0, 1))
  BEGIN
    RAISERROR(14266, 16, 1, '@enabled', '0, 1')
    RETURN(1) -- Failure
  END

  RETURN(0)
END
go

/**************************************************************/
/* SP_ADD_PROXY                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_add_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_add_proxy
go

CREATE PROCEDURE dbo.sp_add_proxy
   @proxy_name [sysname],
   @enabled [tinyint] = 1,
   @description [nvarchar](512) = NULL,
   @credential_name [sysname] = NULL,
  @credential_id [INT] = NULL,
   @proxy_id [int] = NULL OUTPUT
AS
BEGIN
  DECLARE @retval INT
  DECLARE @full_name NVARCHAR(257) --two sysnames + \
  DECLARE @user_sid VARBINARY(85)
  DECLARE @cred_date_time DATETIME
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @proxy_name                = LTRIM(RTRIM(@proxy_name))
  SELECT @description               = LTRIM(RTRIM(@description))

  IF @proxy_name  = ''  SELECT @proxy_name  = NULL
  IF @description = ''  SELECT @description = NULL

  EXECUTE @retval = sp_verify_proxy NULL,
                                    @proxy_name,
                           @enabled,
                           @description

  IF (@retval <> 0)
     RETURN(1) -- Failure

   EXECUTE @retval = sp_verify_credential_identifiers '@credential_name',
                                                  '@credential_id',
                                                   @credential_name OUTPUT,
                                                   @credential_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure
  -- warn if the user_domain\user_name does not exist
  SELECT @full_name = credential_identity, @cred_date_time = create_date from master.sys.credentials
  WHERE  credential_id = @credential_id
  --force case insensitive comparation for NT users
  SELECT @user_sid = SUSER_SID(@full_name,0)
  IF @user_sid IS NULL
  BEGIN
    RAISERROR(14529, -1, -1, @full_name)
    RETURN(1)
  END

  -- Finally, do the actual INSERT
  INSERT INTO msdb.dbo.sysproxies
   (
      name,
    credential_id,
      enabled,
      description,
    user_sid,
    credential_date_created
   )
   VALUES
   (
      @proxy_name,
    @credential_id,
      @enabled,
      @description,
    @user_sid,
    @cred_date_time
   )

   --get newly created proxy_id;
   SELECT @proxy_id = SCOPE_IDENTITY()
END
go

/**************************************************************/
/* SP_DELETE_PROXY                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_delete_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_delete_proxy
go

CREATE PROCEDURE dbo.sp_delete_proxy
   @proxy_id      int = NULL,
   @proxy_name    sysname = NULL
   -- must specify only one of above parameters to identify the proxy
AS
BEGIN
   DECLARE @retval   INT
   SET NOCOUNT ON

   EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                  '@proxy_id',
                                                   @proxy_name OUTPUT,
                                                   @proxy_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

   --no jobsteps should use this proxy
   IF EXISTS (SELECT * FROM sysjobsteps
            WHERE @proxy_id = proxy_id)
   BEGIN
      RAISERROR(14518, -1, -1, @proxy_id)
      RETURN(1) -- Failure
   END

    BEGIN TRANSACTION
      --delete any association between subsystems and this proxy
      DELETE sysproxysubsystem
      WHERE  proxy_id = @proxy_id

      --delete any association between logins and this proxy
      DELETE sysproxylogin
      WHERE  proxy_id = @proxy_id

      -- delete the entry in sysproxies table
      DELETE sysproxies
      WHERE proxy_id = @proxy_id

    COMMIT
   RETURN(0)
END
go


/**************************************************************/
/* SP_UPDATE_PROXY                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_update_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_update_proxy
go

CREATE PROCEDURE dbo.sp_update_proxy
   @proxy_id [int] = NULL,
   @proxy_name [sysname] = NULL,
   -- must specify only one of above parameter identify the proxy
   @credential_name [sysname] = NULL,
   @credential_id [INT] = NULL,
   @new_name [sysname] = NULL,
   @enabled [tinyint] = NULL,
   @description [nvarchar](512) = NULL
AS
BEGIN
   DECLARE  @x_new_name [sysname]
   DECLARE  @x_credential_id [int]
   DECLARE  @x_enabled [tinyint]
   DECLARE @x_description [nvarchar](512)
   DECLARE @x_credential_date_created [datetime]
   DECLARE @user_sid VARBINARY(85)
      DECLARE @full_name [sysname] --two sysnames + \
   DECLARE @retval   INT
   SET NOCOUNT ON

   EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                  '@proxy_id',
                                                   @proxy_name OUTPUT,
                                                   @proxy_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  IF @credential_id IS NOT NULL OR @credential_name IS NOT NULL
  BEGIN
     EXECUTE @retval = sp_verify_credential_identifiers '@credential_name',
                                                    '@credential_id',
                                                    @credential_name OUTPUT,
                                                    @credential_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

   -- Remove any leading/trailing spaces from parameters
   SELECT @new_name                = LTRIM(RTRIM(@new_name))
   SELECT @description             = LTRIM(RTRIM(@description))
  -- Turn [nullable] empty string parameters into NULLs
  IF @new_name      = '' SELECT @new_name = NULL
  IF @description    = '' SELECT @description = NULL

  -- Set the x_ (existing) variables
  SELECT    @x_new_name      = name,
    @x_credential_id = credential_id,
      @x_enabled       = enabled,
      @x_description   = description,
    @x_credential_date_created = credential_date_created
   FROM sysproxies
   WHERE proxy_id = @proxy_id

  --get the new date from credential table
  IF  (@credential_id IS NOT NULL)
    SELECT @x_credential_date_created = create_date FROM master.sys.credentials
    WHERE  credential_id = @credential_id

    -- Fill out the values for all non-supplied parameters from the existing values
   IF    (@new_name      IS NULL) SELECT   @new_name          =           @x_new_name
   IF (@credential_id IS NULL) SELECT   @credential_id     =           @x_credential_id
   IF (@enabled       IS NULL) SELECT   @enabled           =           @x_enabled
   IF (@description   IS NULL) SELECT   @description       =           @x_description

  -- warn if the user_domain\user_name does not exist
  SELECT @full_name = credential_identity from master.sys.credentials
  WHERE  credential_id = @credential_id

  --force case insensitive comparation for NT users
  SELECT @user_sid = SUSER_SID(@full_name, 0)
  IF @user_sid IS NULL
  BEGIN
    RAISERROR(14529, -1, -1, @full_name)
    RETURN(1)
  END

  -- Finally, do the actual UPDATE
  UPDATE msdb.dbo.sysproxies
   SET
   name     =  @new_name,
   credential_id  =  @credential_id,
   user_sid =  @user_sid,
   enabled     =  @enabled,
   description =  @description,
   credential_date_created = @x_credential_date_created  --@x_ is OK in this case
   WHERE proxy_id = @proxy_id
END
go

/**************************************************************/
/* SP_SQLAGENT_IS_MEMBER                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_is_member...'
IF (NOT OBJECT_ID(N'dbo.sp_sqlagent_is_member', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_sqlagent_is_member
go

-- check if a login is member of NT group\database role
--
-- if we specify a NT group SID @login_sid should be NOT NULL
--
-- if a @role_principal_id is specified, a NULL login is allowed
-- in this case we check if the msdb database user associated
-- with the current security context is member of the specified
-- msdb database role (this allows us to verify if a particular
-- msdb database loginless msdb user is member of that msdb role)
CREATE PROCEDURE dbo.sp_sqlagent_is_member
(
  @group_sid VARBINARY(85) = NULL,
   @role_principal_id  INT = NULL,
   @login_sid VARBINARY(85)
)
AS
BEGIN
   DECLARE @ret_success  INT
  DECLARE @login        NVARCHAR(256)
   DECLARE @impersonated INT
  DECLARE @group_name   NVARCHAR(256)
   SELECT   @ret_success = 0 --failure
  SELECT  @impersonated = 0

  IF (@group_sid IS NOT NULL AND @login_sid IS NULL)
    RETURN(0)

  --a sysadmin can check for every user group membership
  IF (@login_sid IS NOT NULL) AND (ISNULL(IS_SRVROLEMEMBER('sysadmin'),0) = 1)
  BEGIN
    --get login name from principal_id
    SELECT @login = SUSER_SNAME(@login_sid)

    IF SUSER_SNAME() <> @login
    BEGIN
       --impersonate
        EXECUTE sp_setuserbylogin @login
      SELECT @impersonated = 1
    END
  END

  IF @group_sid IS NOT NULL
    SELECT @group_name = SUSER_SNAME(@group_sid)
  ELSE
    SELECT @group_name = USER_NAME(@role_principal_id)

   -- return success, if login is member of the group, and failure if group doesnt exist or login is not member of the group
   SELECT  @ret_success = ISNULL(IS_MEMBER(@group_name),0)

   --revert to self
  IF @impersonated = 1
         EXECUTE sp_setuserbylogin

   RETURN @ret_success
END
go

/**************************************************************/
/* sp_verify_proxy_permissions                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_proxy_permissions...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_proxy_permissions', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_proxy_permissions
go
CREATE PROCEDURE dbo.sp_verify_proxy_permissions
   @subsystem_name sysname,
   @proxy_id      INT = NULL,
   @name       NVARCHAR(256) = NULL,
   @raise_error    INT = 1,
   @allow_disable_proxy INT = 0,
   @verify_special_account INT = 0,
   @check_only_read_perm INT = 0
AS
BEGIN
  DECLARE @retval   INT
  DECLARE @granted_sid VARBINARY(85)
  DECLARE @is_member INT
  DECLARE @is_sysadmin BIT
  DECLARE @flags TINYINT
  DECLARE @enabled TINYINT
  DECLARE @name_sid VARBINARY(85)
  DECLARE @role_from_sid sysname
  DECLARE @name_from_sid sysname
  DECLARE @is_SQLAgentOperatorRole BIT
  DECLARE @check_only_subsystem BIT
  DECLARE proxy_subsystem CURSOR LOCAL
  FOR
    SELECT p.sid, p.flags
    FROM sysproxyloginsubsystem_view p, syssubsystems s
    WHERE p.proxy_id = @proxy_id AND p.subsystem_id = s.subsystem_id
        AND UPPER(s.subsystem collate SQL_Latin1_General_CP1_CS_AS) =
            UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS)

  SET NOCOUNT ON
  SELECT @retval = 1

  IF @proxy_id IS NULL
    RETURN(0)

   -- TSQL subsystem prohibited
  IF (UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS) = N'TSQL')
  BEGIN
    RAISERROR(14517, -1, -1)
    RETURN(1) -- Failure
  END

  --check if the date stored inside proxy still exists and match the cred create_date inside proxy
  --otherwise the credential has been tempered from outside
  --if so, disable proxy and continue execution
  --only a sysadmin caller have cross database permissions but
  --when executing by sqlagent this check will be always performed
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
  BEGIN
    IF NOT EXISTS(SELECT * FROM sysproxies p JOIN master.sys.credentials c ON p.credential_id = c.credential_id
            WHERE p.proxy_id = @proxy_id AND p.credential_date_created = c.create_date AND enabled=1)
    BEGIN
        UPDATE sysproxies SET enabled=0 WHERE proxy_id = @proxy_id
    END
  END

  --if no login has been passed check permission against the caller
  IF @name IS NULL
    SELECT @name = SUSER_SNAME()

  --check if the proxy is disable and continue or not based on
  --allow_disable_proxy
  --allow creation of a job step with a disabled proxy but
  --sqlagent always call with @allow_disable_proxy = 0
  SELECT @enabled = enabled FROM sysproxies WHERE proxy_id = @proxy_id
  IF (@enabled = 0) AND (@allow_disable_proxy = 0)
  BEGIN
    RAISERROR(14537, -1, -1, @proxy_id)
    RETURN(2) -- Failure
  END

  --we need to check permission only against subsystem in following cases
  --1. @name is sysadmin
  --2. @name is member of SQLAgentOperatorRole and @check_only_read_perm=1
  --3. @verify_special_account =1
  --sysadmin and SQLAgentOperatorRole have permission to view all proxies
  IF (@verify_special_account = 1)
    SET @check_only_subsystem = 1
  ELSE
  BEGIN
    EXEC @is_sysadmin = sp_sqlagent_is_srvrolemember N'sysadmin', @name
    IF (@is_sysadmin = 1)
      SET @check_only_subsystem = 1
    ELSE
    BEGIN
      EXEC @is_SQLAgentOperatorRole = sp_sqlagent_is_srvrolemember N'SQLAgentOperatorRole', @name -- check role membership
      IF ((@is_SQLAgentOperatorRole = 1) AND (@check_only_read_perm = 1))
        SET @check_only_subsystem = 1
    END
  END

  IF (@check_only_subsystem = 1)
  BEGIN
    IF NOT EXISTS(SELECT * FROM sysproxysubsystem sp JOIN syssubsystems s ON sp.subsystem_id = s.subsystem_id
                  WHERE proxy_id = @proxy_id  AND UPPER(s.subsystem collate SQL_Latin1_General_CP1_CS_AS) =
                     UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS))
    BEGIN
      IF (@raise_error <> 0)
      BEGIN
        RAISERROR(14516, -1, -1, @proxy_id, @subsystem_name, @name)
      END
      RETURN(1) -- Failure
    END
    RETURN(0)
  END

  --get SID from name; we verify if a login has permission to use a certain proxy
  --force case insensitive comparation for NT users
  SELECT @name_sid = SUSER_SID(@name, 0)

  --check first if name has been granted explicit permissions
  IF (@name_sid IS NOT NULL)
  BEGIN
      IF EXISTS(SELECT * FROM sysproxyloginsubsystem_view p, syssubsystems s
        WHERE p.proxy_id = @proxy_id AND p.subsystem_id = s.subsystem_id
            AND UPPER(s.subsystem collate SQL_Latin1_General_CP1_CS_AS) =
                UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS)
            AND
        p.sid = @name_sid) -- name has been granted explicit permissions
      BEGIN
        RETURN(0)
      END
  END

  OPEN proxy_subsystem
  FETCH NEXT FROM proxy_subsystem INTO @granted_sid, @flags
  WHILE (@@fetch_status = 0 AND @retval = 1)
  BEGIN
    IF @flags = 0 AND @granted_sid IS NOT NULL AND @name_sid IS NOT NULL -- NT GROUP
    BEGIN
        EXEC @is_member = sp_sqlagent_is_member @group_sid = @granted_sid, @login_sid = @name_sid
        IF @is_member = 1
          SELECT @retval = 0
    END
    ELSE IF @flags = 2 AND @granted_sid IS NOT NULL -- MSDB role (@name_sid can be null in case of a loginless user member of msdb)
    BEGIN
        DECLARE @principal_id INT
        SET @principal_id = msdb.dbo.get_principal_id(@granted_sid)
        EXEC @is_member = sp_sqlagent_is_member @role_principal_id = @principal_id, @login_sid = @name_sid
        IF @is_member = 1
          SELECT @retval = 0
    END
    ELSE IF (@flags = 1) AND @granted_sid IS NOT NULL AND @name_sid IS NOT NULL -- FIXED SERVER Roles
    BEGIN
      -- we have to use impersonation to check for role membership
      SELECT @role_from_sid = SUSER_SNAME(@granted_sid)
      SELECT @name_from_sid = SUSER_SNAME(@name_sid)
      EXEC   @is_member = sp_sqlagent_is_srvrolemember @role_from_sid, @name_from_sid -- check role membership

      IF @is_member = 1
        SELECT @retval = 0
    END

    IF @retval = 1
    BEGIN
        SELECT @granted_sid = NULL
        FETCH NEXT FROM proxy_subsystem INTO @granted_sid, @flags
    END
  END
  DEALLOCATE proxy_subsystem

  IF (@retval = 1 AND @raise_error <> 0)
  BEGIN
    RAISERROR(14516, -1, -1, @proxy_id, @subsystem_name, @name)
    RETURN(1) -- Failure
  END

   --0 is for success
   RETURN @retval
END
go

/**************************************************************/
/* SP_HELP_PROXY                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_help_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_help_proxy
go

CREATE PROCEDURE dbo.sp_help_proxy
   @proxy_id          int           = NULL,
   @proxy_name      sysname       = NULL,
   @subsystem_name sysname       = NULL,
   @name           nvarchar(256) = NULL
AS
BEGIN
  DECLARE @retval INT
  DECLARE @subsystem_id INT
  DECLARE @cur_subsystem_name NVARCHAR(40)
  DECLARE @current_proxy_id INT
  DECLARE @not_have_permission INT
  DECLARE cur_proxy CURSOR LOCAL
  FOR
    SELECT p.proxy_id, s.subsystem
    FROM sysproxies p, syssubsystems s
    WHERE ISNULL(UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS),
        UPPER(s.subsystem collate SQL_Latin1_General_CP1_CS_AS) ) =
        UPPER(s.subsystem collate SQL_Latin1_General_CP1_CS_AS) AND
        s.subsystem_id <> 1 --last is TSQL subsystem

  -- this call will populate subsystems table if necessary
  EXEC @retval = msdb.dbo.sp_verify_subsystems
  IF @retval <> 0
     RETURN(1) --failure

  --create temp table with returned rows
  DECLARE @temp_proxy TABLE
   (
      proxy_id             INT  --used to identify a proxy
   )

  SET NOCOUNT ON

  SELECT @subsystem_id = NULL

  -- Remove any leading/trailing spaces from parameters
  SELECT @proxy_name       = LTRIM(RTRIM(@proxy_name))
  IF @proxy_name           = '' SELECT @proxy_name = NULL
  SELECT @subsystem_name   = LTRIM(RTRIM(@subsystem_name))
  IF @proxy_name           = '' SELECT @proxy_name = NULL
  SELECT @name             = LTRIM(RTRIM(@name))
  IF @name                 = '' SELECT @name = NULL

  IF (@proxy_id IS NOT NULL OR @proxy_name IS NOT NULL)
  BEGIN
    EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                        '@proxy_id',
                                        @proxy_name OUTPUT,
                                        @proxy_id   OUTPUT
    IF (@retval <> 0)
        RETURN(1) -- Failure
  END

  IF @subsystem_name IS NOT NULL
  BEGIN
    EXECUTE @retval = sp_verify_subsystem_identifiers '@subsystem_name',
                                      '@subsystem_id',
                                      @subsystem_name OUTPUT,
                                      @subsystem_id   OUTPUT
    IF (@retval <> 0)
        RETURN(1) -- Failure
  END

  IF (@subsystem_name IS NOT NULL AND @name IS NULL) OR
    (@subsystem_name IS NULL AND @name IS NOT NULL)
  BEGIN
    RAISERROR(14532, -1, -1, '@subsystem_name', '@name')
    RETURN(1) -- Failure
  END

  --only member of sysadmin and SQLAgentOperatorRole roles can see proxies granted to somebody else
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0) AND
      (ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) = 0))
  BEGIN
    SELECT @name = SUSER_SNAME()
  END

  IF @name IS NOT NULL
  BEGIN
    OPEN cur_proxy
    FETCH NEXT FROM cur_proxy INTO @current_proxy_id, @cur_subsystem_name
    WHILE (@@fetch_status = 0)
    BEGIN
      --verify if supplied user have permission to use the current proxy for specified subsystem
      --disabled proxy should be shown as well
      IF NOT EXISTS(SELECT * FROM @temp_proxy WHERE proxy_id = @current_proxy_id)
      BEGIN
        EXECUTE @not_have_permission = sp_verify_proxy_permissions
          @subsystem_name = @cur_subsystem_name,
          @proxy_id = @current_proxy_id,
          @name = @name,
          @raise_error = 0,
          @allow_disable_proxy = 1,
          @verify_special_account = 0,
          @check_only_read_perm = 1
        IF (@not_have_permission = 0) -- have permissions
            INSERT @temp_proxy VALUES(@current_proxy_id)
      END
      FETCH NEXT FROM cur_proxy INTO @current_proxy_id, @cur_subsystem_name
    END
    CLOSE cur_proxy
    DEALLOCATE cur_proxy
  END
  ELSE
    INSERT @temp_proxy SELECT proxy_id from sysproxies

  -- returns different result sets if caller is admin or not
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) OR
      (ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) = 1))
  BEGIN
    SELECT p.proxy_id,
          p.name,
          c.credential_identity,
          p.enabled,
          p.description,
          p.user_sid,
          p.credential_id,
          CASE WHEN c.credential_id IS NULL THEN 0 ELSE 1 END as credential_identity_exists
    FROM sysproxies p LEFT JOIN master.sys.credentials c ON p.credential_id = c.credential_id
                  JOIN @temp_proxy t ON p.proxy_id = t.proxy_id
              WHERE ISNULL(@proxy_id, p.proxy_id) = p.proxy_id
  END
  ELSE
  BEGIN
    SELECT p.proxy_id, p.name, null as credential_identity, p.enabled, p.description, null as user_sid, p.credential_id, null as credential_identity_exists
    FROM sysproxies p, @temp_proxy t
    WHERE  ISNULL(@proxy_id, p.proxy_id) = p.proxy_id AND
              p.proxy_id = t.proxy_id
  END

END
go


/**************************************************************/
/* sp_get_proxy_properties                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_get_proxy_properties...'
GO

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_get_proxy_properties')
              AND (type = 'P')))
  DROP PROCEDURE sp_get_proxy_properties
GO
CREATE PROCEDURE sp_get_proxy_properties
	@proxy_id [int] = NULL,  -- specify either @current_proxy_id or @current_proxy_name ; if both are specified as null, propery for all proxies are returned back
	@proxy_name [sysname] = NULL
AS
BEGIN
    DECLARE @retval   INT
    SET NOCOUNT ON

	-- Validate only if either proxy name or proxy id was specified
    IF NOT (@proxy_id IS NULL ) AND (@proxy_name IS NULL)
	BEGIN
		EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                 '@proxy_id',
                                                 @proxy_name OUTPUT,
                                                 @proxy_id   OUTPUT

		IF (@retval <> 0)
		BEGIN
			-- exception message was raised inside sp_verify_proxy_identifiers; we dont need to RAISERROR again here
			RETURN(1) -- Failure
		END
	END

    -- return domain name, user name, credential id; used by SQL agent to query for proxy
	SELECT CASE CHARINDEX(N'\', c.credential_identity)
		WHEN 0 THEN  NULL
		ELSE LEFT(c.credential_identity, CHARINDEX(N'\', c.credential_identity)-1)
		END
		AS user_domain,
	RIGHT(c.credential_identity, LEN(c.credential_identity)- CHARINDEX(N'\', c.credential_identity)) AS user_name,
	c.credential_id
	FROM msdb.dbo.sysproxies p JOIN
	sys.credentials c
	ON p.credential_id = c.credential_id
	WHERE (p.proxy_id = @proxy_id OR @proxy_id IS NULL)

END
GO

/**************************************************************/
/* sp_grant_proxy_to_subsystem                             */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_grant_proxy_to_subsystem...'
IF (NOT OBJECT_ID(N'dbo.sp_grant_proxy_to_subsystem', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_grant_proxy_to_subsystem
go
CREATE PROCEDURE dbo.sp_grant_proxy_to_subsystem
   @proxy_id      int = NULL,
   @proxy_name    sysname = NULL,
   -- must specify only one of above parameter to identify the proxy
   @subsystem_id  int = NULL,
   @subsystem_name sysname = NULL
   -- must specify only one of above parameter to identify the subsystem
AS
BEGIN
   DECLARE @retval   INT
   DECLARE @proxy_account sysname
   SET NOCOUNT ON

   -- Remove any leading/trailing spaces from parameters
   SELECT @subsystem_name          = LTRIM(RTRIM(@subsystem_name))
   SELECT @proxy_name              = LTRIM(RTRIM(@proxy_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF @subsystem_name    = '' SELECT @subsystem_name = NULL
  IF @proxy_name         = '' SELECT @proxy_name = NULL

   EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                  '@proxy_id',
                                                   @proxy_name OUTPUT,
                                                   @proxy_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

   EXECUTE @retval = sp_verify_subsystem_identifiers '@subsystem_name',
                                                  '@subsystem_id',
                                                   @subsystem_name OUTPUT,
                                                   @subsystem_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure


  --TSQL subsystem is prohibited
  IF @subsystem_id = 1
   BEGIN
     RAISERROR(14530, -1, -1)
     RETURN(1) -- Failure
   END

  --check if we already added an user for the pair subsystem-proxy
  IF (EXISTS(SELECT * FROM sysproxysubsystem WHERE subsystem_id = @subsystem_id
               AND proxy_id = @proxy_id))
  BEGIN
     RAISERROR(14531, -1, -1)
     RETURN(1) -- Failure
   END

   -- For CmdExec and Powershell subsystems, make sure that proxy is mapped to windows login
    IF ((UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS) = 'CMDEXEC') OR
        (UPPER(@subsystem_name collate SQL_Latin1_General_CP1_CS_AS) = 'POWERSHELL'))
    BEGIN
        DECLARE  @credential_name [sysname]
        DECLARE @credential_id [INT]

        SELECT @credential_id = credential_id FROM sysproxies
        WHERE  proxy_id = @proxy_id

        EXECUTE @retval = sp_verify_credential_identifiers '@credential_name',
                                                            '@credential_id',
                                                            @credential_name OUTPUT,
                                                            @credential_id   OUTPUT,
                                                            @allow_only_windows_credential = 1
        IF (@retval <> 0)
        BEGIN
            RETURN(1) -- Failure
        END
    END

   INSERT INTO sysproxysubsystem
   (  subsystem_id,  proxy_id )
   VALUES
   (  @subsystem_id,    @proxy_id )

END
go

/**************************************************************/
/* sp_grant_login_to_proxy                           */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_grant_login_to_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_grant_login_to_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_grant_login_to_proxy
go
CREATE PROCEDURE dbo.sp_grant_login_to_proxy
   @login_name        NVARCHAR(256) = NULL,
   @fixed_server_role NVARCHAR(256) = NULL,
   @msdb_role         NVARCHAR(256) = NULL,
   -- must specify only one of above parameter to identify the type of login
   @proxy_id             int           = NULL,
   @proxy_name         sysname       = NULL
   -- must specify only one of above parameter to identify the proxy
AS
BEGIN
  DECLARE @retval   INT
  DECLARE @name nvarchar(256)
  DECLARE @flags INT
  DECLARE @sid VARBINARY(85)
  DECLARE @is_sysadmin BIT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @proxy_name              = LTRIM(RTRIM(@proxy_name))
  SELECT @fixed_server_role       = LTRIM(RTRIM(@fixed_server_role))
  SELECT @msdb_role               = LTRIM(RTRIM(@msdb_role))

  -- Turn [nullable] empty string parameters into NULLs
  IF @proxy_name         = '' SELECT @proxy_name = NULL
  IF @login_name         = '' SELECT @login_name = NULL
  IF @fixed_server_role  = '' SELECT @fixed_server_role = NULL
  IF @msdb_role          = '' SELECT @msdb_role  = NULL

  EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                 '@proxy_id',
                                                 @proxy_name OUTPUT,
                                                 @proxy_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  EXECUTE @retval = sp_verify_login_identifiers  @login_name,
                                                 @fixed_server_role,
                                                 @msdb_role,
                                                 @name OUTPUT,
                                                 @sid OUTPUT,
                                                 @flags OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- is login member of sysadmin role?
  SELECT @is_sysadmin = 0
  IF (@login_name IS NOT NULL)
  BEGIN
     EXEC @is_sysadmin = sp_sqlagent_is_srvrolemember N'sysadmin', @login_name -- check role membership
  END

  IF (@is_sysadmin = 1)
  BEGIN
   -- @name is sysadmin, it cannot granted to proxy
   -- issue a message and do nothing
   RAISERROR(14395, 10, 1, @name)
  END
  ELSE
  BEGIN
   --check if we already added an user for the pair subsystem-proxy
   IF (EXISTS(SELECT * FROM sysproxylogin WHERE proxy_id = @proxy_id
               AND ISNULL(sid, 0) = ISNULL(@sid,0)
               AND flags = @flags))
   BEGIN
      RAISERROR(14531, -1, -1)
      RETURN(1) -- Failure
   END

   INSERT INTO sysproxylogin
      (  proxy_id, sid,  flags )
      VALUES
      ( @proxy_id, @sid, @flags)
  END
END
go

/**************************************************************/
/* sp_revoke_login_from_proxy                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_revoke_login_from_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_revoke_login_from_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_revoke_login_from_proxy
go

CREATE PROCEDURE dbo.sp_revoke_login_from_proxy
   @name         NVARCHAR(256),
   @proxy_id        INT = NULL,
   @proxy_name    sysname = NULL
   -- must specify only one of above parameter to identify the proxy
AS
BEGIN
   DECLARE @retval   INT
   DECLARE @sid VARBINARY(85)
   DECLARE @is_sysadmin BIT
   DECLARE @flags INT
   DECLARE @affected_records INT = 0

   SET NOCOUNT ON

   -- Remove any leading/trailing spaces from parameters
   SELECT @proxy_name              = LTRIM(RTRIM(@proxy_name))
   SELECT @name                    = LTRIM(RTRIM(@name))

   -- Turn [nullable] empty string parameters into NULLs
   IF @proxy_name         = '' SELECT @proxy_name = NULL
   IF @name               = '' SELECT @name = NULL

   EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                  '@proxy_id',
                                                   @proxy_name OUTPUT,
                                                   @proxy_id   OUTPUT
   IF (@retval <> 0)
     RETURN(1) -- Failure

  -- is login member of sysadmin role?
  SELECT @is_sysadmin = 0
  IF (@name IS NOT NULL)
  BEGIN
    EXEC @is_sysadmin = sp_sqlagent_is_srvrolemember N'sysadmin', @name -- check role membership
  END

  IF (@is_sysadmin = 1)
  BEGIN
    -- @name is sysadmin, it cannot be revoked from proxy
    -- issue a message and do nothing
    RAISERROR(14395, 10, -1, @name)
    RETURN(1) -- Failure
  END
  ELSE
  BEGIN
    DECLARE revoke_cursor CURSOR LOCAL
	FOR
	SELECT flags FROM sysproxylogin WHERE proxy_id = @proxy_id

	OPEN revoke_cursor
	FETCH NEXT FROM revoke_cursor INTO @flags

	WHILE (@@fetch_status = 0)
	BEGIN
		if @flags = 1 OR @flags = 0 -- @flags with value 1 indicates fixed server role, flags with value 0 indicates login, both sid(s) should be read from sys.server_principals
			SELECT @sid = SUSER_SID(@name, 0) --force case insensitive comparation for NT users
		ELSE
			SELECT @sid = sid FROM msdb.sys.database_principals WHERE  name = @name -- @flags with value 2 indicates MSDB role

		--check parametrs validity
		IF (ISNULL(@sid, 0) <> 0)
		BEGIN
		   DELETE FROM sysproxylogin WHERE
									   proxy_id = @proxy_id AND
									   sid = @sid AND
									   flags = @flags
		   SELECT @affected_records = @affected_records + @@ROWCOUNT
		END

		FETCH NEXT FROM revoke_cursor INTO @flags
	END

	CLOSE revoke_cursor
	DEALLOCATE revoke_cursor

	if @affected_records = 0
    BEGIN
       RAISERROR(14523, -1, -1, @name, @proxy_name)
       RETURN(1) -- Failure
    END
  END

  RETURN(0)
END
go

/**************************************************************/
/* sp_revoke_proxy_from_subsystem                                       */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_revoke_proxy_from_subsystem...'
IF (NOT OBJECT_ID(N'dbo.sp_revoke_proxy_from_subsystem', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_revoke_proxy_from_subsystem
go

CREATE PROCEDURE dbo.sp_revoke_proxy_from_subsystem
   @proxy_id          INT = NULL,
   @proxy_name      sysname = NULL,
   -- must specify only one of above parameter to identify the proxyAS
   @subsystem_id    INT = NULL,
   @subsystem_name sysname = NULL
   -- must specify only one of above parameter to identify the subsystem
AS
BEGIN
   DECLARE @retval   INT
   DECLARE @proxy_account sysname
   SET NOCOUNT ON

   -- Remove any leading/trailing spaces from parameters
   SELECT @subsystem_name          = LTRIM(RTRIM(@subsystem_name))
   SELECT @proxy_name              = LTRIM(RTRIM(@proxy_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF @subsystem_name    = '' SELECT @subsystem_name = NULL
  IF @proxy_name         = '' SELECT @proxy_name = NULL

   EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                  '@proxy_id',
                                                   @proxy_name OUTPUT,
                                                   @proxy_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

   EXECUTE @retval = sp_verify_subsystem_identifiers '@subsystem_name',
                                                  '@subsystem_id',
                                                   @subsystem_name OUTPUT,
                                                   @subsystem_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure


  --check parametrs validity
   IF (EXISTS(SELECT * FROM sysproxysubsystem WHERE
      subsystem_id = @subsystem_id AND
      proxy_id     = @proxy_id ))
      BEGIN
        DELETE FROM sysproxysubsystem WHERE
           subsystem_id = @subsystem_id AND
           proxy_id     = @proxy_id
      END
   ELSE
      BEGIN
         RAISERROR(14600, -1, -1, @proxy_name, @subsystem_name)
         RETURN(1) -- Failure
      END

   RETURN(0)

END
go

/**************************************************************/
/* SP_ENUM_PROXY_FOR_SUBSYSTEM                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_enum_proxy_for_subsystem...'
IF (NOT OBJECT_ID(N'dbo.sp_enum_proxy_for_subsystem', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_enum_proxy_for_subsystem
go

CREATE PROCEDURE sp_enum_proxy_for_subsystem
   @proxy_id      int = NULL,
   @proxy_name    sysname = NULL,
   -- must specify only one of above parameter to identify the proxy or none
   @subsystem_id  int = NULL,
   @subsystem_name sysname = NULL
   -- must specify only one of above parameter to identify the subsystem or none
AS
BEGIN
   DECLARE @retval   INT
   SET NOCOUNT ON

   -- Remove any leading/trailing spaces from parameters
   SELECT @subsystem_name          = LTRIM(RTRIM(@subsystem_name))
   SELECT @proxy_name              = LTRIM(RTRIM(@proxy_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF @subsystem_name    = '' SELECT @subsystem_name = NULL
  IF @proxy_name         = '' SELECT @proxy_name = NULL

   IF @proxy_name IS NOT NULL OR @proxy_id IS NOT NULL
   BEGIN
      EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                          '@proxy_id',
                                          @proxy_name OUTPUT,
                                          @proxy_id   OUTPUT
      IF (@retval <> 0)
   RETURN(1) -- Failure
   END

   IF @subsystem_name IS NOT NULL OR @subsystem_id IS NOT NULL
   BEGIN
      EXECUTE @retval = sp_verify_subsystem_identifiers '@subsystem_name',
                                       '@subsystem_id',
                                       @subsystem_name OUTPUT,
                                       @subsystem_id   OUTPUT
      IF (@retval <> 0)
      RETURN(1) -- Failure
   END

  SELECT ps.subsystem_id AS subsystem_id, s.subsystem AS subsystem_name, ps.proxy_id AS proxy_id, p.name AS proxy_name
   FROM sysproxysubsystem ps JOIN sysproxies p ON ps.proxy_id = p.proxy_id
  JOIN syssubsystems s ON ps.subsystem_id = s.subsystem_id
   WHERE
        ISNULL(@subsystem_id, ps.subsystem_id) = ps.subsystem_id AND
        ISNULL(@proxy_id,     ps.proxy_id    ) = ps.proxy_id
END
go

/**************************************************************/
/* sp_enum_login_for_proxy                                           */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_enum_login_for_proxy...'
IF (NOT OBJECT_ID(N'dbo.sp_enum_login_for_proxy', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_enum_login_for_proxy
go

CREATE PROCEDURE sp_enum_login_for_proxy
   @name         NVARCHAR(256) = NULL,
   @proxy_id        INT           = NULL,
   @proxy_name    sysname       = NULL
   -- must specify only one of above parameter to identify the proxy or none
AS
BEGIN
   DECLARE @retval   INT
  DECLARE @sid VARBINARY(85)
   SET NOCOUNT ON

   -- Remove any leading/trailing spaces from parameters
   SELECT @proxy_name              = LTRIM(RTRIM(@proxy_name))
   SELECT @name                    = LTRIM(RTRIM(@name))

  -- Turn [nullable] empty string parameters into NULLs
  IF @proxy_name         = '' SELECT @proxy_name = NULL
  IF @name                 = '' SELECT @name = NULL

   IF @proxy_name IS NOT NULL OR @proxy_id IS NOT NULL
   BEGIN
      EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                          '@proxy_id',
                                          @proxy_name OUTPUT,
                                          @proxy_id   OUTPUT
     IF (@retval <> 0)
       RETURN(1) -- Failure
   END

   IF (@name IS NOT NULL) AND
     --force case insensitive comparation for NT users
     (ISNULL(SUSER_SID(@name, 0), 0) = 0) AND
     (ISNULL(IS_SRVROLEMEMBER(@name), -1) = -1) AND
      (ISNULL(IS_MEMBER(@name), -1) = -1)
   BEGIN
            RAISERROR(14520, -1, -1, @name)
            RETURN(1) -- Failure
   END

  --force case insensitive comparation for NT users
  SELECT @sid = SUSER_SID(@name, 0)
  IF @sid IS NULL -- then @name is a MSDB role
    SELECT @sid = sid FROM sys.database_principals
    WHERE  name = @name

  SELECT pl.proxy_id AS proxy_id, p.name AS proxy_name, pl.flags as flags,
  CASE pl.flags
          WHEN  0 THEN SUSER_SNAME(pl.sid)  -- SQLLOGIN, NT USER/GROUP
          WHEN  1 THEN SUSER_SNAME(pl.sid)  -- SQL fixed server role
          WHEN  2 THEN USER_NAME(msdb.dbo.get_principal_id(pl.sid)) -- MSDB role
          ELSE         NULL -- should never be the case
  END AS name,  pl.sid AS sid, msdb.dbo.get_principal_id(pl.sid) AS principal_id
   FROM sysproxylogin pl JOIN sysproxies p ON pl.proxy_id = p.proxy_id
   WHERE
        COALESCE(@proxy_id,     pl.proxy_id,     0 ) = ISNULL(pl.proxy_id, 0)  AND
        COALESCE(@sid,          pl.sid,          0 ) = ISNULL(pl.sid, 0)
END
go

/**************************************************************/
/* sp_reassign_proxy                                                                                                             */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_reassign_proxy...'
GO

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_reassign_proxy')
              AND (type = 'P')))
  DROP PROCEDURE sp_reassign_proxy
GO
CREATE PROCEDURE sp_reassign_proxy
	@current_proxy_id [int] = NULL,  -- must specify either @current_proxy_id or @current_proxy_name
	@current_proxy_name [sysname] = NULL,
	@target_proxy_id [int] = NULL,  -- must specify either @target_proxy_id or @target_proxy_name
	@target_proxy_name [sysname] = NULL -- N'' is a special case to allow change of an existing proxy as NULL (run job step in sql agent service account context)
AS
BEGIN
    DECLARE @retval   INT
    SET NOCOUNT ON

    -- validate current proxy id
    EXECUTE @retval = sp_verify_proxy_identifiers '@current_proxy_name',
                                                 '@current_proxy_id',
                                                @current_proxy_name OUTPUT,
                                                 @current_proxy_id   OUTPUT

    IF (@retval <> 0)
    BEGIN
        -- exception message was raised inside sp_verify_proxy_identifiers; we dont need to RAISERROR again here
        RETURN(1) -- Failure
    END

    -- @target_proxy_name = N'' is a special case to allow change of an existing proxy as NULL (run job step in sql agent service account context)
    IF (@target_proxy_id IS NOT NULL) OR (@target_proxy_name IS NOT NULL AND @target_proxy_name <> N'')
    BEGIN
        EXECUTE @retval = sp_verify_proxy_identifiers '@target_proxy_name',
                                                 '@target_proxy_id',
                                                 @target_proxy_name OUTPUT,
                                                 @target_proxy_id   OUTPUT

        IF (@retval <> 0)
        BEGIN
            -- exception message was raised inside sp_verify_proxy_identifiers; we dont need to RAISERROR again here
            RETURN(1) -- Failure
        END
    END

    -- Validate that  current proxy id and target proxy id are not the same
    IF(@current_proxy_id = @target_proxy_id)
    BEGIN
        RAISERROR(14399, -1, -1, @current_proxy_id, @target_proxy_id)
	RETURN(1) -- Failure
    END

    DECLARE @job_id UNIQUEIDENTIFIER
    DECLARE @step_id int
    DECLARE @proxy_id int
    DECLARE @subsystem_id int

    -- cursor to enumerate list of job steps what has proxy_id as current proxy_id
    DECLARE @jobstep_cursor CURSOR
    SET @jobstep_cursor = CURSOR FOR
    SELECT js.job_id, js.step_id,  js.proxy_id , subsys.subsystem_id
    FROM sysjobsteps js
    JOIN syssubsystems subsys ON js.subsystem = subsys.subsystem
    WHERE js.proxy_id = @current_proxy_id

    OPEN @jobstep_cursor
    FETCH NEXT FROM @jobstep_cursor INTO @job_id, @step_id, @proxy_id, @subsystem_id

    WHILE @@FETCH_STATUS = 0
    BEGIN
        -- current proxy might have been granted to be used by this specific subsystem
        -- making sure that the target proxy has been granted access to same subsystem
        -- Grant target proxy to subsystem if it was not granted before
        IF NOT EXISTS( SELECT  DISTINCT ps.proxy_id, subsyst.subsystem_id
                        FROM  syssubsystems subsyst
                        JOIN sysproxysubsystem  ps ON  (ps.subsystem_id = subsyst.subsystem_id
				                        AND ps.proxy_id = @target_proxy_id
				                        AND ps.subsystem_id = @subsystem_id)
                    )
        BEGIN
            -- throw error that user needs to grant permission to this target proxy
            IF @target_proxy_id IS NOT NULL
            BEGIN
		     RAISERROR(14400, -1, -1, @target_proxy_id, @subsystem_id)
		     RETURN(1) -- Failure
            END
        END

        -- Update proxy_id for job step with target proxy id using sp_update_jobstep
        EXEC sp_update_jobstep @job_id = @job_id, @step_id = @step_id , @proxy_name = @target_proxy_name

        FETCH NEXT FROM @jobstep_cursor INTO @job_id, @step_id, @proxy_id, @subsystem_id
    END

    CLOSE @jobstep_cursor
    DEALLOCATE @jobstep_cursor

    RETURN(0)
END
GO


/**************************************************************/
/* SP_SQLAGENT_GET_STARTUP_INFO                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_get_startup_info...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_sqlagent_get_startup_info')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_get_startup_info
go
CREATE PROCEDURE sp_sqlagent_get_startup_info
AS
BEGIN
  DECLARE @tbu INT
  DECLARE @agentAllowed INT

  SET NOCOUNT ON

  IF (ServerProperty('InstanceName') IS NULL)
  BEGIN
    EXECUTE @tbu = master.dbo.xp_qv '1338198028'
    EXECUTE @agentAllowed = master.dbo.xp_qv '2858542058'
  END
  ELSE
  BEGIN
    DECLARE @instancename NVARCHAR(128)
    SELECT @instancename = CONVERT(NVARCHAR(128), ServerProperty('InstanceName'))
    EXECUTE @tbu = master.dbo.xp_qv '1338198028', @instancename
    EXECUTE @agentAllowed = master.dbo.xp_qv '2858542058', @instancename
  END

  IF (@tbu < 0)
    SELECT @tbu = 0

  IF (@agentAllowed < 0)
    SELECT @agentAllowed = 0

  SELECT ( CASE WHEN DATABASEPROPERTYEX('msdb', 'Updateability') = 'READ_ONLY'
                THEN 1
                ELSE 0
           END )  AS msdb_read_only,
         ( CASE WHEN DATABASEPROPERTYEX('msdb', 'Status') = 'ONLINE' THEN 1 ELSE 0 END  &
           CASE WHEN DATABASEPROPERTYEX('msdb', 'UserAccess') = 'MULTI_USER' THEN 1 ELSE 0 END)  AS msdb_available,
         CASE ISNULL((SELECT 1 WHERE 'a' = 'A'), 0)
             WHEN 1 THEN 0
             ELSE 1
         END AS case_sensitive_server,
         (SELECT value_in_use FROM sys.configurations WHERE (name = 'user connections')) AS max_user_connection,
         CONVERT(sysname, SERVERPROPERTY('SERVERNAME')) AS sql_server_name,
         ISNULL(@tbu, 0) AS tbu,
         PLATFORM() AS platform,
         ISNULL(CONVERT(sysname, SERVERPROPERTY('INSTANCENAME')), 'MSSQLSERVER') AS instance_name ,
         CONVERT(INT, SERVERPROPERTY('ISCLUSTERED')) AS is_clustered,
         @agentAllowed AS agent_allowed

  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_SQLAGENT_UPDATE_AGENT_XPS                               */
/*  Sql Agent uses the XPs listed in the "Agent XPs" bucket.  */
/*  The configuration is enable on agent startup,             */
/*  and disabled on agent shutdown.                           */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_update_agent_xps...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_sqlagent_update_agent_xps')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_update_agent_xps
go
CREATE PROCEDURE sp_sqlagent_update_agent_xps
  @new_value        bit = 1  -- the new value for the "Agent XPs" configuration option.
AS
BEGIN
  declare @agent_enabled bit
  declare @agent_value bit
  declare @show_advanced bit

  select @show_advanced = cast(value_in_use as bit)
    from sys.configurations
    where name = N'show advanced options'

  select @agent_enabled = cast(value_in_use as bit)
    from sys.configurations
    where name = N'Agent XPs'

  select @agent_value = cast(value as bit)
    from sys.configurations
    where name = N'Agent XPs'

  if (@new_value <> @agent_enabled OR (SERVERPROPERTY('EngineEdition') = 8 AND @new_value <> @agent_value))
  begin
    if 1 <> @show_advanced
    begin
      exec sys.sp_configure @configname = N'show advanced options', @configvalue = 1
      reconfigure with override
    end

    exec sys.sp_configure @configname = N'Agent XPs', @configvalue = @new_value
    reconfigure with override
    if 1 <> @show_advanced
    begin
      exec sys.sp_configure @configname = N'show advanced options', @configvalue = 0
      reconfigure with override
    end
  end
END
go


/**************************************************************/
/* SP_SQLAGENT_HAS_SERVER_ACCESS                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_has_server_access...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_sqlagent_has_server_access')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_has_server_access
go
CREATE PROCEDURE sp_sqlagent_has_server_access
  @login_name         sysname = NULL,
  @job_id             uniqueidentifier = NULL, -- if this is not null, @login_name will be ignored!
  @is_sysadmin_member INT     = NULL OUTPUT
AS
BEGIN
  DECLARE @has_server_access BIT
  DECLARE @is_sysadmin       BIT
  DECLARE @actual_login_name sysname
  -- Set only when login_name is actually found. It will be zero when @actual_login_name is (unknown).
  DECLARE @login_found BIT
  DECLARE @cachedate         DATETIME

  SET NOCOUNT ON

  SELECT @cachedate = NULL

  -- remove expired entries from the cache
  DELETE msdb.dbo.syscachedcredentials
  WHERE  DATEDIFF(MINUTE, cachedate, GETDATE()) >= 29

  -- query the cache
  SELECT  @is_sysadmin = is_sysadmin_member,
          @has_server_access = has_server_access,
          @cachedate = cachedate
  FROM    msdb.dbo.syscachedcredentials
  WHERE   login_name = @login_name
  AND     DATEDIFF(MINUTE, cachedate, GETDATE()) < 29

  IF (@cachedate IS NOT NULL)
  BEGIN
    -- no output variable
    IF (@is_sysadmin_member IS NULL)
    BEGIN
      -- Return result row
      SELECT has_server_access = @has_server_access,
             is_sysadmin       = @is_sysadmin,
             actual_login_name = @login_name
      RETURN
    END
    ELSE
    BEGIN
      SELECT @is_sysadmin_member = @is_sysadmin
      RETURN
    END
  END -- select from cache

  -- Set defaults
  SELECT @has_server_access = 0
  SELECT @is_sysadmin = 0
  SELECT @actual_login_name = FORMATMESSAGE(14205)
  SELECT @login_found = 0

    -- If @job_id was set, get the current name associated with the job owner sid.
  if (@job_id IS NOT NULL)
  BEGIN
	SELECT @login_name = dbo.SQLAGENT_SUSER_SNAME(owner_sid)
	FROM msdb.dbo.sysjobs_view
	WHERE @job_id = job_id

    -- If the job_id is invalid, return error
    IF (@login_name IS NULL)
    BEGIN
      RETURN 1;
    END

  END

 IF (@login_name IS NULL)
  BEGIN
    SELECT has_server_access = 1,
           is_sysadmin       = IS_SRVROLEMEMBER(N'sysadmin'),
           actual_login_name = SUSER_SNAME()
    RETURN
  END

  IF (@login_name LIKE '%\%')
  BEGIN
    -- Handle the LocalSystem account ('NT AUTHORITY\SYSTEM') as a special case
    IF (UPPER(@login_name collate SQL_Latin1_General_CP1_CS_AS) = N'NT AUTHORITY\SYSTEM')
    BEGIN
      IF (EXISTS (SELECT *
                  FROM master.dbo.syslogins
                  WHERE (UPPER(loginname collate SQL_Latin1_General_CP1_CS_AS) = N'NT AUTHORITY\SYSTEM')))
      BEGIN
        SELECT @has_server_access = hasaccess,
               @is_sysadmin = sysadmin,
               @actual_login_name = loginname
        FROM master.dbo.syslogins
        WHERE (UPPER(loginname collate SQL_Latin1_General_CP1_CS_AS) = N'NT AUTHORITY\SYSTEM')

        SET @login_found = 1
      END
      ELSE
      IF (EXISTS (SELECT *
                  FROM master.dbo.syslogins
                  WHERE (UPPER(loginname collate SQL_Latin1_General_CP1_CS_AS) = N'BUILTIN\ADMINISTRATORS')))
      BEGIN
        SELECT @has_server_access = hasaccess,
               @is_sysadmin = sysadmin,
               @actual_login_name = loginname
        FROM master.dbo.syslogins
        WHERE (UPPER(loginname collate SQL_Latin1_General_CP1_CS_AS) = N'BUILTIN\ADMINISTRATORS')

        SET @login_found = 1
      END
    END
    ELSE
    BEGIN
      -- Check if the NT login has been explicitly denied access
      IF (EXISTS (SELECT *
                  FROM master.dbo.syslogins
                  WHERE (loginname = @login_name)
                    AND (denylogin = 1)))
      BEGIN
        SELECT @has_server_access = 0,
               @is_sysadmin = sysadmin,
               @actual_login_name = loginname
        FROM master.dbo.syslogins
        WHERE (loginname = @login_name)

        SET @login_found = 1
      END
      ELSE
      BEGIN
        -- declare table variable for storing results
        DECLARE @xp_results TABLE
        (
        account_name      sysname      COLLATE database_default NOT NULL PRIMARY KEY,
        type              NVARCHAR(10) COLLATE database_default NOT NULL,
        privilege         NVARCHAR(10) COLLATE database_default NOT NULL,
        mapped_login_name sysname      COLLATE database_default NOT NULL,
        permission_path   sysname      COLLATE database_default NULL
        )

        -- Call xp_logininfo to determine server access
        INSERT INTO @xp_results
        EXECUTE master.dbo.xp_logininfo @login_name

        IF (SELECT COUNT(*) FROM @xp_results) > 0
        BEGIN
          SET @has_server_access = 1
          SET @login_found = 1
        END

        SELECT @actual_login_name = mapped_login_name,
               @is_sysadmin = CASE UPPER(privilege collate SQL_Latin1_General_CP1_CS_AS)
                                WHEN 'ADMIN' THEN 1
                                ELSE 0
                             END
        FROM @xp_results
      END
    END
    -- Only cache the NT logins to approximate the behavior of Sql Server and Windows (see bug 323287)
    -- update the cache only if something is found
    IF  (UPPER(@actual_login_name collate SQL_Latin1_General_CP1_CS_AS) <> '(UNKNOWN)')
    BEGIN
      -- Procedure starts its own transaction.
      BEGIN TRANSACTION;

      -- Modify database.
      -- use a try catch login to prevent any error when trying
      -- to insert/update syscachedcredentials table
      -- no need to fail since the job owner has been validated
      BEGIN TRY
        IF EXISTS (SELECT * FROM msdb.dbo.syscachedcredentials WITH (TABLOCKX) WHERE login_name = @login_name)
        BEGIN
          UPDATE msdb.dbo.syscachedcredentials
          SET    has_server_access = @has_server_access,
                is_sysadmin_member = @is_sysadmin,
                cachedate = GETDATE()
          WHERE  login_name = @login_name
        END
        ELSE
        BEGIN
          INSERT INTO msdb.dbo.syscachedcredentials(login_name, has_server_access, is_sysadmin_member)
          VALUES(@login_name, @has_server_access, @is_sysadmin)
        END
        END TRY
        BEGIN CATCH
            -- If an error occurred we want to ignore it
        END CATCH

        -- The procedure must commit the transaction it started.
        COMMIT TRANSACTION;
    END

  END
  ELSE
  BEGIN
    -- Standard login
    IF (EXISTS (SELECT *
                FROM master.dbo.syslogins
                WHERE (loginname = @login_name)))
    BEGIN
      SELECT @has_server_access = hasaccess,
             @is_sysadmin = sysadmin,
             @actual_login_name = loginname
      FROM master.dbo.syslogins
      WHERE (loginname = @login_name)

      SET @login_found = 1
    END
  END

  IF (@is_sysadmin_member IS NULL)
    -- Return result row
    SELECT has_server_access = @has_server_access,
           is_sysadmin       = @is_sysadmin,
           actual_login_name = @actual_login_name,
           login_found       = @login_found
  ELSE
    -- output variable only
    SELECT @is_sysadmin_member = @is_sysadmin
END
go


/**************************************************************/
/* SP_SQLAGENT_GET_PERF_COUNTERS                              */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_sqlagent_get_perf_counters...'
GO
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_sqlagent_get_perf_counters')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_get_perf_counters
GO
CREATE PROCEDURE sp_sqlagent_get_perf_counters
  @all_counters BIT = 0
AS
BEGIN

  SET NOCOUNT ON

  -- 32 bit fraction counter types
  DECLARE @perfTypeRawFraction INT
  DECLARE @perfTypeRawBase     INT

  -- A counter of type PERF_RAW_FRACTION, which is a 32-bit counter value.
  SET @perfTypeRawFraction = 537003008 --  In hex, 0x20020400.

   -- A count of type PERF_RAW_BASE, which is the 32-bit divisor used
   -- when handling PERF_RAW_FRACTION types. This counter type should
   -- not be displayed to the user since it is used for mathematical
   -- operations.
  SET @perfTypeRawBase     = 1073939459 -- In hex, 0x40030403.


  -- 64 bit fraction counter types
  DECLARE @perfTypeLargeRawFraction INT
  DECLARE @perfTypeLargeRawBase     INT

  -- A counter of type PERF_LARGE RAW_FRACTION, which is a 64-bit counter value.
  SET @perfTypeLargeRawFraction = 537003264 --  In hex, 0x20020500.

   -- A count of type PERF_LARGE_RAW_BASE, which is the 64-bit divisor used
   -- when handling PERF_LARGE_RAW_FRACTION types. This counter type should
   -- not be displayed to the user since it is used for mathematical
   -- operations.
  SET @perfTypeLargeRawBase     = 1073939712 -- In hex, 0x40030500.



  IF (@all_counters = 0)
  BEGIN
        SELECT  spi1.object_name,
                spi1.counter_name,
                'instance_name' = CASE spi1.instance_name
                                    WHEN N'' THEN NULL
                                    ELSE spi1.instance_name
                                    END,
                'value' = CASE spi1.cntr_type
                            WHEN @perfTypeRawFraction -- 32 bit fraction
                                THEN CONVERT(FLOAT, spi1.cntr_value) / (SELECT CASE spi2.cntr_value
                                                                            WHEN 0 THEN 1
                                                                            ELSE spi2.cntr_value
                                                                            END
                                                                        FROM sysalerts_performance_counters_view spi2
                                                                        WHERE (RTRIM(spi1.counter_name) + ' ' = SUBSTRING(spi2.counter_name, 1, PATINDEX('% base%', LOWER(spi2.counter_name))))
                                                                        AND spi1.object_name = spi2.object_name
                                                                        AND spi1.server_name = spi2.server_name
                                                                        AND spi1.instance_name = spi2.instance_name
                                                                        AND spi2.cntr_type = @perfTypeRawBase
                                                                        )
                            WHEN @perfTypeLargeRawFraction  -- 64 bit fraction
                                THEN CONVERT(FLOAT, spi1.cntr_value) / (SELECT CASE spi2.cntr_value
                                                                            WHEN 0 THEN 1
                                                                            ELSE spi2.cntr_value
                                                                            END
                                                                        FROM sysalerts_performance_counters_view spi2
                                                                        WHERE (RTRIM(spi1.counter_name) + ' ' = SUBSTRING(spi2.counter_name, 1, PATINDEX('% base%', LOWER(spi2.counter_name))))
                                                                        AND spi1.object_name = spi2.object_name
                                                                        AND spi1.server_name = spi2.server_name
                                                                        AND spi1.instance_name = spi2.instance_name
                                                                        AND spi2.cntr_type = @perfTypeLargeRawBase
                                                                        )
                                ELSE spi1.cntr_value
                            END,
       'type' = spi1.cntr_type,
        spi1.server_name
        FROM sysalerts_performance_counters_view spi1,
        (
                SELECT DISTINCT
                    SUBSTRING(performance_condition,
                                PATINDEX('%:%', performance_condition) + 1,
                                CHARINDEX('|', performance_condition,
                                            PATINDEX('%_|_%', performance_condition) + 2)-(PATINDEX('%:%', performance_condition) + 1
                                         )
                             )
                AS performance_condition_s
                FROM msdb.dbo.sysalerts
                WHERE performance_condition IS NOT NULL
                AND ISNULL(event_id, 0) <> 8 -- exclude WMI events that reuse performance_condition field
                AND enabled = 1
        ) tmp -- We want to select only those counters that have an enabled performance sysalert
        WHERE spi1.cntr_type <> @perfTypeRawBase      -- ignore 32-bit denominator counter type
        AND spi1.cntr_type <> @perfTypeLargeRawBase      -- ignore 64-bit denominator counter type
        AND tmp.performance_condition_s = (spi1.object_name + '|' + spi1.counter_name)
        OPTION (HASH JOIN, LOOP JOIN) -- Avoid merge join when small number of alerts are defined
  END
  ELSE
  BEGIN
        SELECT  spi1.object_name,
                spi1.counter_name,
                'instance_name' = CASE spi1.instance_name
                                    WHEN N'' THEN NULL
                                    ELSE spi1.instance_name
                                    END,
                'value' = CASE spi1.cntr_type
                            WHEN @perfTypeRawFraction -- 32 bit fraction
                            THEN CONVERT(FLOAT, spi1.cntr_value) / (SELECT CASE spi2.cntr_value
                                                                        WHEN 0 THEN 1
                                                                        ELSE spi2.cntr_value
                                                                        END
                                                                    FROM sysalerts_performance_counters_view spi2
                                                                    WHERE (RTRIM(spi1.counter_name) + ' ' = SUBSTRING(spi2.counter_name, 1, PATINDEX('% base%', LOWER(spi2.counter_name))))
                                                                    AND spi1.object_name = spi2.object_name
                                                                    AND spi1.server_name = spi2.server_name
                                                                    AND spi1.instance_name = spi2.instance_name
                                                                    AND spi2.cntr_type = @perfTypeRawBase
                                                                    )
                            WHEN @perfTypeLargeRawFraction  -- 64 bit fraction
                            THEN CONVERT(FLOAT, spi1.cntr_value) / (SELECT CASE spi2.cntr_value
                                                                        WHEN 0 THEN 1
                                                                        ELSE spi2.cntr_value
                                                                        END
                                                                    FROM sysalerts_performance_counters_view spi2
                                                                    WHERE (RTRIM(spi1.counter_name) + ' ' = SUBSTRING(spi2.counter_name, 1, PATINDEX('% base%', LOWER(spi2.counter_name))))
                                                                    AND spi1.object_name = spi2.object_name
                                                                    AND spi1.server_name = spi2.server_name
                                                                    AND spi1.instance_name = spi2.instance_name
                                                                    AND spi2.cntr_type = @perfTypeLargeRawBase
                                                                    )
                            ELSE spi1.cntr_value
                        END,
                'type' = spi1.cntr_type,
                spi1.server_name
        FROM sysalerts_performance_counters_view spi1
        WHERE spi1.cntr_type <> @perfTypeRawBase      -- ignore 32-bit denominator counter type
        AND spi1.cntr_type <> @perfTypeLargeRawBase -- ignore 64-bit denominator counter type
  END
END

GO


/**************************************************************/
/* SP_SQLAGENT_NOTIFY                                         */
/*                                                            */
/* NOTE: We define this procedure here instead of in the      */
/*      'Support procedures' section because of the many      */
/*       other procedures that reference it.                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_notify...'
go

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_sqlagent_notify')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_notify
go
CREATE PROCEDURE sp_sqlagent_notify
  @op_type     NCHAR(1),                -- One of: J (Job action [refresh or start/stop]),
                                        --         S (Schedule action [refresh only])
                                        --         A (Alert action [refresh only]),
                                        --         G (Re-cache all registry settings),
                                        --         D (Dump job [or job schedule] cache to errorlog)
                                        --         P (Force an immediate poll of the MSX)
                                        --         L (Cycle log file)
                                        --         T (Test WMI parameters (namespace and query))
                                        --         M (DatabaseMail action [ refresh profile  associated with sql agent)
  @job_id      UNIQUEIDENTIFIER = NULL, -- JobID (for OpTypes 'J', 'S' and 'D')
  @schedule_id INT              = NULL, -- ScheduleID (for OpType 'S')
  @alert_id    INT              = NULL, -- AlertID (for OpType 'A')
  @action_type NCHAR(1)         = NULL, -- For 'J' one of: R (Run - no service check),
                                        --                 S (Start - with service check),
                                        --                 I (Insert),
                                        --                 U (Update),
                                        --                 D (Delete),
                                        --                 C (Stop [Cancel])
                                        -- For 'S' or 'A' one of: I (Insert),
                                        --                        U (Update),
                                        --                        D (Delete)
  @error_flag  INT              = 1,    -- Set to 0 to suppress the error from xp_sqlagent_notify if SQLServer agent is not running
  @wmi_namespace nvarchar(128) = NULL,
  @wmi_query     nvarchar(512) = NULL
AS
BEGIN



  DECLARE @retval         INT
  DECLARE @id_as_char     VARCHAR(10)
  DECLARE @job_id_as_char VARCHAR(36)
  DECLARE @nt_user_name   NVARCHAR(100)



  SET NOCOUNT ON

  SELECT @retval = 0 -- Success

  -- Make sure that we're dealing only with uppercase characters
  SELECT @op_type     = UPPER(@op_type collate SQL_Latin1_General_CP1_CS_AS)
  SELECT @action_type = UPPER(@action_type collate SQL_Latin1_General_CP1_CS_AS)

  -- Verify operation code
  IF (CHARINDEX(@op_type, N'JSAGDPLTM') = 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@op_type', 'J, S, A, G, D, P, L, T, M')
    RETURN(1) -- Failure
  END

  -- Check the job id for those who use it
  IF (CHARINDEX(@op_type, N'JSD') <> 0)
  BEGIN
    IF (NOT ((@op_type = N'D' OR @op_type = N'S') AND (@job_id IS NULL))) -- For 'D' and 'S' job_id is optional
    BEGIN
      IF ((@job_id IS NULL) OR
          ((@action_type <> N'D') AND NOT EXISTS (SELECT *
                                                  FROM msdb.dbo.sysjobs_view
                                                  WHERE (job_id = @job_id))))
      BEGIN
        SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)
        RAISERROR(14262, -1, -1, '@job_id', @job_id_as_char)
        RETURN(1) -- Failure
      END
    END
  END

  -- Verify 'job' action parameters
  IF (@op_type = N'J')
  BEGIN
    SELECT @alert_id = 0
    IF (@schedule_id IS NULL) SELECT @schedule_id = 0

    -- The schedule_id (if specified) is the start step
    IF ((CHARINDEX(@action_type, N'RS') <> 0) AND (@schedule_id <> 0))
    BEGIN
      IF (NOT EXISTS (SELECT *
                      FROM msdb.dbo.sysjobsteps
                      WHERE (job_id = @job_id)
                        AND (step_id = @schedule_id)))
      BEGIN
        SELECT @id_as_char = ISNULL(CONVERT(VARCHAR, @schedule_id), '(null)')
        RAISERROR(14262, -1, -1, '@schedule_id', @id_as_char)
        RETURN(1) -- Failure
      END
    END
    ELSE
      SELECT @schedule_id = 0

    IF (CHARINDEX(@action_type, N'RSIUDC') = 0)
    BEGIN
      RAISERROR(14266, -1, -1, '@action_type', 'R, S, I, U, D, C')
      RETURN(1) -- Failure
    END
  END

  -- Verify 'schedule' action parameters
  IF (@op_type = N'S')
  BEGIN
    SELECT @alert_id = 0

    IF (CHARINDEX(@action_type, N'IUD') = 0)
    BEGIN
      RAISERROR(14266, -1, -1, '@action_type', 'I, U, D')
      RETURN(1) -- Failure
    END

    IF ((@schedule_id IS NULL) OR
        ((@action_type <> N'D') AND NOT EXISTS (SELECT *
                                                FROM msdb.dbo.sysschedules
                                                WHERE (schedule_id = @schedule_id))))
    BEGIN
      SELECT @id_as_char = ISNULL(CONVERT(VARCHAR, @schedule_id), '(null)')
      RAISERROR(14262, -1, -1, '@schedule_id', @id_as_char)
      RETURN(1) -- Failure
    END
  END

  -- Verify 'alert' action parameters
  IF (@op_type = N'A')
  BEGIN
    SELECT @job_id = 0x00
    SELECT @schedule_id = 0

    IF (CHARINDEX(@action_type, N'IUD') = 0)
    BEGIN
      RAISERROR(14266, -1, -1, '@action_type', 'I, U, D')
      RETURN(1) -- Failure
    END

    IF ((@alert_id IS NULL) OR
        ((@action_type <> N'D') AND NOT EXISTS (SELECT *
                                                FROM msdb.dbo.sysalerts
                                                WHERE (id = @alert_id))))
    BEGIN
      SELECT @id_as_char = ISNULL(CONVERT(VARCHAR, @alert_id), '(null)')
      RAISERROR(14262, -1, -1, '@alert_id', @id_as_char)
      RETURN(1) -- Failure
    END
  END

  -- Verify 'registry', 'job dump' and 'force MSX poll' , 'cycle log', dbmail profile refresh action parameters
  IF (CHARINDEX(@op_type, N'GDPLM') <> 0)
  BEGIN
    IF (@op_type <> N'D')
      SELECT @job_id = 0x00
    SELECT @alert_id = 0
    SELECT @schedule_id = 0
    SELECT @action_type = NULL
  END

  -- Parameters are valid, so now check execution permissions...

  -- For anything except a job (or schedule) action the caller must be SysAdmin, DBO, or DB_Owner
  IF (@op_type NOT IN (N'J', N'S'))
  BEGIN
    IF NOT ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) OR
            (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1) OR
            (UPPER(USER_NAME() collate SQL_Latin1_General_CP1_CS_AS) = N'DBO'))
    BEGIN
      RAISERROR(14260, -1, -1)
      RETURN(1) -- Failure
    END
  END

  -- For a Job Action the caller must be SysAdmin, DBO, DB_Owner, or the job owner
  IF (@op_type = N'J')
  BEGIN
    IF NOT ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) OR
            (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1) OR
            (UPPER(USER_NAME() collate SQL_Latin1_General_CP1_CS_AS) = N'DBO') OR
            (EXISTS (SELECT *
                     FROM msdb.dbo.sysjobs_view
                     WHERE (job_id = @job_id))))
    BEGIN
      RAISERROR(14252, -1, -1)
      RETURN(1) -- Failure
    END
  END

  --verify WMI parameters
  IF (@op_type = N'T')
  BEGIN
   SELECT @wmi_namespace = LTRIM(RTRIM(@wmi_namespace))
   SELECT @wmi_query = LTRIM(RTRIM(@wmi_query))
    IF (@wmi_namespace IS NULL) or (@wmi_query IS NULL)
   BEGIN
          RAISERROR(14508, 16, 1)
          RETURN(1) -- Failure
   END
  END

  -- Ok, let's do it...
  SELECT @nt_user_name = ISNULL(NT_CLIENT(), ISNULL(SUSER_SNAME(), FORMATMESSAGE(14205)))
  EXECUTE @retval = master.dbo.xp_sqlagent_notify @op_type, @job_id, @schedule_id, @alert_id, @action_type, @nt_user_name, @error_flag, @@trancount, @wmi_namespace, @wmi_query

  RETURN(@retval)
END
go

/**************************************************************/
/* SP_IS_SQLAGENT_STARTING                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_is_sqlagent_starting...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_is_sqlagent_starting')
              AND (type = 'P')))
  DROP PROCEDURE sp_is_sqlagent_starting
go
CREATE PROCEDURE sp_is_sqlagent_starting
AS
BEGIN
  DECLARE @retval INT

  SELECT @retval = 0
  EXECUTE master.dbo.xp_sqlagent_is_starting @retval OUTPUT
  IF (@retval = 1)
    RAISERROR(14258, -1, -1)

  RETURN(@retval)
END
go


/**************************************************************/
/* SP_VERIFY_JOB_IDENTIFIERS                                  */
/*                                                            */
/* NOTE: We define this procedure here instead of in the      */
/*      'Support procedures' section because of the many      */
/*       other procedures that reference it.                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_job_identifiers...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_job_identifiers')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_job_identifiers
go
CREATE PROCEDURE sp_verify_job_identifiers
  @name_of_name_parameter  VARCHAR(60),             -- Eg. '@job_name'
  @name_of_id_parameter    VARCHAR(60),             -- Eg. '@job_id'
  @job_name                sysname          OUTPUT, -- Eg. 'My Job'
  @job_id                  UNIQUEIDENTIFIER OUTPUT,
  @sqlagent_starting_test  VARCHAR(7) = 'TEST',      -- By default we DO want to test if SQLServerAgent is running (caller should specify 'NO_TEST' if not desired)
  @owner_sid                VARBINARY(85) = NULL OUTPUT
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @job_id_as_char VARCHAR(36)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name_of_name_parameter = LTRIM(RTRIM(@name_of_name_parameter))
  SELECT @name_of_id_parameter   = LTRIM(RTRIM(@name_of_id_parameter))
  SELECT @job_name               = LTRIM(RTRIM(@job_name))

  IF (@job_name = N'') SELECT @job_name = NULL

  IF ((@job_name IS NULL)     AND (@job_id IS NULL)) OR
     ((@job_name IS NOT NULL) AND (@job_id IS NOT NULL))
  BEGIN
    RAISERROR(14294, -1, -1, @name_of_id_parameter, @name_of_name_parameter)
    RETURN(1) -- Failure
  END

  -- Check job id
  IF (@job_id IS NOT NULL)
  BEGIN
    SELECT @job_name = name,
           @owner_sid = owner_sid
    FROM msdb.dbo.sysjobs_view
    WHERE (job_id = @job_id)

    -- the view would take care of all the permissions issues.
    IF (@job_name IS NULL)
    BEGIN
      SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)
      RAISERROR(14262, -1, -1, '@job_id', @job_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check job name
  IF (@job_name IS NOT NULL)
  BEGIN
    -- Check if the job name is ambiguous
    IF ((SELECT COUNT(*)
         FROM msdb.dbo.sysjobs_view
         WHERE (name = @job_name)) > 1)
    BEGIN
      RAISERROR(14293, -1, -1, @job_name, @name_of_id_parameter, @name_of_name_parameter)
      RETURN(1) -- Failure
    END

    -- The name is not ambiguous, so get the corresponding job_id (if the job exists)
    SELECT @job_id = job_id,
           @owner_sid = owner_sid
    FROM msdb.dbo.sysjobs_view
    WHERE (name = @job_name)

    -- the view would take care of all the permissions issues.
    IF (@job_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@job_name', @job_name)
      RETURN(1) -- Failure
    END
  END

  IF (@sqlagent_starting_test = 'TEST')
  BEGIN
    -- Finally, check if SQLServerAgent is in the process of starting and if so prevent the
    -- calling SP from running
    EXECUTE @retval = msdb.dbo.sp_is_sqlagent_starting
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_VERIFY_SCHEDULE_IDENTIFIERS                             */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_schedule_identifiers...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_schedule_identifiers')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_schedule_identifiers
go
CREATE PROCEDURE sp_verify_schedule_identifiers
  @name_of_name_parameter   VARCHAR(60),             -- Eg. '@schedule_name'
  @name_of_id_parameter     VARCHAR(60),             -- Eg. '@schedule_id'
  @schedule_name            sysname             OUTPUT,
  @schedule_id              INT                 OUTPUT,
  @owner_sid                VARBINARY(85)       OUTPUT,
  @orig_server_id           INT                 OUTPUT,
  @job_id_filter            UNIQUEIDENTIFIER    = NULL
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @schedule_id_as_char VARCHAR(36)
  DECLARE @sch_name_count INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name_of_name_parameter = LTRIM(RTRIM(@name_of_name_parameter))
  SELECT @name_of_id_parameter   = LTRIM(RTRIM(@name_of_id_parameter))
  SELECT @schedule_name          = LTRIM(RTRIM(@schedule_name))
  SELECT @sch_name_count         = 0


  IF (@schedule_name = N'') SELECT @schedule_name = NULL

  IF ((@schedule_name IS NULL)     AND (@schedule_id IS NULL)) OR
     ((@schedule_name IS NOT NULL) AND (@schedule_id IS NOT NULL))
  BEGIN
    RAISERROR(14373, -1, -1, @name_of_id_parameter, @name_of_name_parameter)
    RETURN(1) -- Failure
  END

  -- Check schedule id
  IF (@schedule_id IS NOT NULL)
  BEGIN
    -- if Agent is calling look in all schedules not just the local server schedules
    if(PROGRAM_NAME() LIKE N'SQLAgent%')
    BEGIN
        -- Look at all schedules
        SELECT @schedule_name   = name,
           @owner_sid           = owner_sid,
           @orig_server_id      = originating_server_id
        FROM msdb.dbo.sysschedules
        WHERE (schedule_id = @schedule_id)
    END
    ELSE
    BEGIN
        --Look at local schedules only
        SELECT @schedule_name   = name,
           @owner_sid           = owner_sid,
           @orig_server_id      = originating_server_id
        FROM msdb.dbo.sysschedules_localserver_view
        WHERE (schedule_id = @schedule_id)
    END

    IF (@schedule_name IS NULL)
    BEGIN
     --If the schedule is from an MSX and a sysadmin is calling report a specific 'MSX' message
      IF(PROGRAM_NAME() NOT LIKE N'SQLAgent%' AND
         ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1 AND
         EXISTS(SELECT *
                FROM msdb.dbo.sysschedules as sched
                  JOIN msdb.dbo.sysoriginatingservers_view as svr
                    ON sched.originating_server_id = svr.originating_server_id
                WHERE (schedule_id = @schedule_id) AND
                      (svr.master_server = 1)))
     BEGIN
       RAISERROR(14274, -1, -1)
     END
      ELSE
      BEGIN
        SELECT @schedule_id_as_char = CONVERT(VARCHAR(36), @schedule_id)
        RAISERROR(14262, -1, -1, '@schedule_id', @schedule_id_as_char)
      END

      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check job name
  IF (@schedule_name IS NOT NULL)
  BEGIN
    -- if a job_id is supplied use it as a filter. This helps with V8 legacy support
    IF(@job_id_filter IS NOT NULL)
    BEGIN
        -- Check if the job name is ambiguous and also get the schedule_id optimistically.
        -- If the name is not ambiguous this gets the corresponding schedule_id (if the schedule exists)
        SELECT @sch_name_count = COUNT(*),
               @schedule_id    = MIN(s.schedule_id),
               @owner_sid      = MIN(owner_sid),
               @orig_server_id = MIN(originating_server_id)
        FROM msdb.dbo.sysschedules_localserver_view as s
          JOIN msdb.dbo.sysjobschedules as js
            ON s.schedule_id = js.schedule_id
        WHERE (name = @schedule_name) AND
              (js.job_id = @job_id_filter)
    END
    ELSE
    BEGIN
      -- Check if the job name is ambiguous from the count(*) result
        -- If the name is not ambiguous it is safe use the fields returned by the MIN() function
        SELECT @sch_name_count = COUNT(*),
         @schedule_id     = MIN(schedule_id),
            @owner_sid       = MIN(owner_sid),
            @orig_server_id  = MIN(originating_server_id)
        FROM msdb.dbo.sysschedules_localserver_view
        WHERE (name = @schedule_name)
    END

    IF(@sch_name_count > 1)
    BEGIN
        -- ambiguous, user needs to use a schedule_id instead of a schedule_name
        RAISERROR(14371, -1, -1, @schedule_name, @name_of_id_parameter, @name_of_name_parameter)
        RETURN(1) -- Failure
    END

    --schedule_id isn't visible to this user or doesn't exist
    IF (@schedule_id IS NULL)
    BEGIN
      --If the schedule is from an MSX and a sysadmin is calling report a specific 'MSX' message
      IF(PROGRAM_NAME() NOT LIKE N'SQLAgent%' AND
         ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1 AND
         EXISTS(SELECT *
                FROM msdb.dbo.sysschedules as sched
                  JOIN msdb.dbo.sysoriginatingservers_view as svr
                    ON sched.originating_server_id = svr.originating_server_id
                  JOIN msdb.dbo.sysjobschedules as js
                    ON sched.schedule_id = js.schedule_id
                WHERE (svr.master_server = 1) AND
                      (name = @schedule_name) AND
                      ((@job_id_filter IS NULL) OR (js.job_id = @job_id_filter))))
     BEGIN
       RAISERROR(14274, -1, -1)
     END
      ELSE
      BEGIN
        --If not a MSX schedule raise local error
        RAISERROR(14262, -1, -1, '@schedule_name', @schedule_name)
      END

      RETURN(1) -- Failure
    END
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_VERIFY_JOBPROC_CALLER                                   */
/*                                                            */
/* NOTE: We define this procedure here instead of in the      */
/*      'Support procedures' section because of the many      */
/*       other procedures that reference it.                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_jobproc_caller...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_jobproc_caller')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_jobproc_caller
go
CREATE PROCEDURE sp_verify_jobproc_caller
  @job_id       UNIQUEIDENTIFIER,
  @program_name sysname
AS
BEGIN
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @program_name = LTRIM(RTRIM(@program_name))

  IF (EXISTS (SELECT    *
              FROM      msdb.dbo.sysjobs_view
              WHERE     (job_id = @job_id)
              AND       (master_server = 1) )) -- master_server = 1 filters on MSX jobs in this TSX server
              AND       (PROGRAM_NAME() NOT LIKE @program_name)
  BEGIN
    RAISERROR(14274, -1, -1)
    RETURN(1) -- Failure
  END

  RETURN(0)
END
go

/**************************************************************/
/* SP_DOWNLOADED_ROW_LIMITER                                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_downloaded_row_limiter...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_downloaded_row_limiter')
              AND (type = 'P')))
  DROP PROCEDURE dbo.sp_downloaded_row_limiter
go
CREATE PROCEDURE sp_downloaded_row_limiter
  @server_name sysname -- Target server name
AS
BEGIN
  -- This trigger controls how many downloaded (status = 1) sysdownloadlist rows exist
  -- for any given server.  It does NOT control the absolute number of rows in the table.

  DECLARE @current_rows_per_server INT
  DECLARE @max_rows_per_server     INT -- This value comes from the resgistry (DownloadedMaxRows)
  DECLARE @rows_to_delete          INT
  DECLARE @quoted_server_name      NVARCHAR(514) -- enough room to accomodate the quoted name
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @server_name = LTRIM(RTRIM(@server_name))

  -- Check the server name (if it's bad we fail silently)
  IF (@server_name IS NULL) OR
     (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysdownloadlist
                  WHERE (target_server = @server_name)))
    RETURN(1) -- Failure

  SELECT @max_rows_per_server = 0

  -- Get the max-rows-per-server from the registry
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'DownloadedMaxRows',
                                         @max_rows_per_server OUTPUT,
                                         N'no_output'

  -- Check if we are limiting sysdownloadlist rows
  IF (ISNULL(@max_rows_per_server, -1) = -1)
    RETURN

  -- Check that max_rows_per_server is >= 0
  IF (@max_rows_per_server < -1)
  BEGIN
    -- It isn't, so default to 100 rows
    SELECT @max_rows_per_server = 100
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'DownloadedMaxRows',
                                            N'REG_DWORD',
                                            @max_rows_per_server
  END

  -- Get the number of downloaded rows in sysdownloadlist for the target server in question
  -- NOTE: Determining this [quickly] requires a [non-clustered] index on target_server
  SELECT @current_rows_per_server = COUNT(*)
  FROM msdb.dbo.sysdownloadlist
  WHERE (target_server = @server_name)
    AND (status = 1)

  -- Delete the oldest downloaded row(s) for the target server in question if the new row has
  -- pushed us over the per-server row limit
  SELECT @rows_to_delete = @current_rows_per_server - @max_rows_per_server
  IF (@rows_to_delete > 0)
  BEGIN
    WITH RowsToDelete AS (
      SELECT TOP (@rows_to_delete) *
      FROM msdb.dbo.sysdownloadlist
      WHERE (target_server = @server_name)
        AND (status = 1)
      ORDER BY instance_id
    )
    DELETE FROM RowsToDelete;
  END
END
go

/**************************************************************/
/* SP_POST_MSX_OPERATION                                      */
/*                                                            */
/* NOTE: We define this procedure here instead of in the      */
/*      'Support procedures' section because of the many      */
/*       other procedures that reference it.                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_post_msx_operation...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_post_msx_operation')
              AND (type = 'P')))
  DROP PROCEDURE sp_post_msx_operation
go
CREATE PROCEDURE sp_post_msx_operation
  @operation              VARCHAR(64),
  @object_type            VARCHAR(64)       = 'JOB',-- Can be JOB, SERVER or SCHEDULE
  @job_id                 UNIQUEIDENTIFIER  = NULL, -- NOTE: 0x00 means 'ALL' jobs
  @specific_target_server sysname           = NULL,
  @value                  INT               = NULL, -- For polling interval value
  @schedule_uid           UNIQUEIDENTIFIER  = NULL  -- schedule_uid if the @object_type = 'SCHEDULE'
AS
BEGIN
  DECLARE @operation_code            INT
  DECLARE @specific_target_server_id INT
  DECLARE @instructions_posted       INT
  DECLARE @job_id_as_char            VARCHAR(36)
  DECLARE @schedule_uid_as_char      VARCHAR(36)
  DECLARE @msx_time_zone_adjustment  INT
  DECLARE @local_machine_name        sysname
  DECLARE @retval                    INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @operation              = LTRIM(RTRIM(@operation))
  SELECT @object_type            = LTRIM(RTRIM(@object_type))
  SELECT @specific_target_server = LTRIM(RTRIM(@specific_target_server))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@specific_target_server = N'') SELECT @specific_target_server = NULL

  -- Only a sysadmin can do this, but fail silently for a non-sysadmin
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
    RETURN(0) -- Success (or more accurately a no-op)

  -- Check operation
  SELECT @operation = UPPER(@operation collate SQL_Latin1_General_CP1_CS_AS)
  SELECT @operation_code = CASE @operation
                             WHEN 'INSERT'    THEN 1
                             WHEN 'UPDATE'    THEN 2
                             WHEN 'DELETE'    THEN 3
                             WHEN 'START'     THEN 4
                             WHEN 'STOP'      THEN 5
                             WHEN 'RE-ENLIST' THEN 6
                             WHEN 'DEFECT'    THEN 7
                             WHEN 'SYNC-TIME' THEN 8
                             WHEN 'SET-POLL'  THEN 9
                             ELSE 0
                           END
  IF (@operation_code = 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@operation_code', 'INSERT, UPDATE, DELETE, START, STOP, RE-ENLIST, DEFECT, SYNC-TIME, SET-POLL')
    RETURN(1) -- Failure
  END

  -- Check object type (in 9.0 only 'JOB', 'SERVER' or 'SCHEDULE'are valid)
  IF ((@object_type <> 'JOB') AND (@object_type <> 'SERVER') AND (@object_type <> 'SCHEDULE'))
  BEGIN
    RAISERROR(14266, -1, -1, '@object_type', 'JOB, SERVER, SCHEDULE')
    RETURN(1) -- Failure
  END

  -- Check that for a object type of JOB a job_id has been supplied
  IF ((@object_type = 'JOB') AND (@job_id IS NULL))
  BEGIN
    RAISERROR(14233, -1, -1)
    RETURN(1) -- Failure
  END

    -- Check that for a object type of JOB a job_id has been supplied
  IF ((@object_type = 'SCHEDULE') AND (@schedule_uid IS NULL))
  BEGIN
    RAISERROR(14365, -1, -1)
    RETURN(1) -- Failure
  END

  -- Check polling interval value
  IF (@operation_code = 9) AND ((ISNULL(@value, 0) < 10) OR (ISNULL(@value, 0) > 28800))
  BEGIN
    RAISERROR(14266, -1, -1, '@value', '10..28800')
    RETURN(1) -- Failure
  END

  -- Check specific target server
  IF (@specific_target_server IS NOT NULL)
  BEGIN
    SELECT @specific_target_server = UPPER(@specific_target_server)

    -- Check if the local server is being targeted
    IF (@specific_target_server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))))
    BEGIN
      RETURN(0)
    END
    ELSE
    BEGIN
      SELECT @specific_target_server_id = server_id
      FROM msdb.dbo.systargetservers
      WHERE (UPPER(server_name) = @specific_target_server)
      IF (@specific_target_server_id IS NULL)
      BEGIN
        RAISERROR(14262, -1, -1, '@specific_target_server', @specific_target_server)
        RETURN(1) -- Failure
      END
    END
  END

  -- Check that this server is an MSX server
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.systargetservers) = 0)
  BEGIN
    RETURN(0)
  END

  -- Get local machine name
  EXECUTE @retval = master.dbo.xp_getnetname @local_machine_name OUTPUT
  IF (@retval <> 0) OR (@local_machine_name IS NULL)
  BEGIN
    RAISERROR(14225, -1, -1)
    RETURN(1)
  END

  -- Job-specific processing...
  IF (@object_type = 'JOB')
  BEGIN
    -- Validate the job (if supplied)
    IF (@job_id <> CONVERT(UNIQUEIDENTIFIER, 0x00))
    BEGIN
      SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)

      -- Check if the job exists
      IF (NOT EXISTS (SELECT *
                      FROM msdb.dbo.sysjobs_view
                      WHERE (job_id = @job_id)))
      BEGIN
        RAISERROR(14262, -1, -1, '@job_id', @job_id_as_char)
        RETURN(1) -- Failure
      END

      -- If this is a local job then there's nothing for us to do
      IF (EXISTS (SELECT *
                  FROM msdb.dbo.sysjobservers
                  WHERE (job_id = @job_id)
                    AND (server_id = 0))) -- 0 means local server
      OR (NOT EXISTS (SELECT *
                      FROM msdb.dbo.sysjobservers
                      WHERE (job_id = @job_id)))
      BEGIN
        RETURN(0)
      END
    END

    -- Generate the sysdownloadlist row(s)...
    IF (@operation_code = 1) OR  -- Insert
       (@operation_code = 2) OR  -- Update
       (@operation_code = 3) OR  -- Delete
       (@operation_code = 4) OR  -- Start
       (@operation_code = 5)     -- Stop
    BEGIN
      IF (@job_id = CONVERT(UNIQUEIDENTIFIER, 0x00)) -- IE. 'ALL'
      BEGIN
        -- All jobs

        -- Handle DELETE as a special case (rather than posting 1 instruction per job we just
        -- post a single instruction that means 'delete all jobs from the MSX')
        IF (@operation_code = 3)
        BEGIN
          INSERT INTO msdb.dbo.sysdownloadlist
                (source_server,
                 operation_code,
                 object_type,
                 object_id,
                 target_server)
          SELECT @local_machine_name,
                 @operation_code,
                 1,                -- 1 means 'JOB'
                 CONVERT(UNIQUEIDENTIFIER, 0x00),
                 sts.server_name
          FROM systargetservers sts
          WHERE ((@specific_target_server_id IS NULL) OR (sts.server_id = @specific_target_server_id))
            AND ((SELECT COUNT(*)
                  FROM msdb.dbo.sysjobservers
                  WHERE (server_id = sts.server_id)) > 0)
          SELECT @instructions_posted = @@rowcount
        END
        ELSE
        BEGIN
          INSERT INTO msdb.dbo.sysdownloadlist
                (source_server,
                 operation_code,
                 object_type,
                 object_id,
                 target_server)
          SELECT @local_machine_name,
                 @operation_code,
                 1,                -- 1 means 'JOB'
                 sjv.job_id,
                 sts.server_name
          FROM sysjobs_view     sjv,
               sysjobservers    sjs,
               systargetservers sts
          WHERE (sjv.job_id = sjs.job_id)
            AND (sjs.server_id = sts.server_id)
            AND (sjs.server_id <> 0) -- We want to exclude local jobs
            AND ((@specific_target_server_id IS NULL) OR (sjs.server_id = @specific_target_server_id))
          SELECT @instructions_posted = @@rowcount
        END
      END
      ELSE
      BEGIN
        -- Specific job (ie. @job_id is not 0x00)
        INSERT INTO msdb.dbo.sysdownloadlist
              (source_server,
               operation_code,
               object_type,
               object_id,
               target_server,
               deleted_object_name)
        SELECT @local_machine_name,
               @operation_code,
               1,                -- 1 means 'JOB'
               sjv.job_id,
               sts.server_name,
               CASE @operation_code WHEN 3 -- Delete
                                      THEN sjv.name
                                      ELSE NULL
                                    END
        FROM sysjobs_view     sjv,
             sysjobservers    sjs,
             systargetservers sts
        WHERE (sjv.job_id = @job_id)
          AND (sjv.job_id = sjs.job_id)
          AND (sjs.server_id = sts.server_id)
          AND (sjs.server_id <> 0) -- We want to exclude local jobs
          AND ((@specific_target_server_id IS NULL) OR (sjs.server_id = @specific_target_server_id))
        SELECT @instructions_posted = @@rowcount
      END
    END
    ELSE
    BEGIN
      RAISERROR(14266, -1, -1, '@operation_code', 'INSERT, UPDATE, DELETE, START, STOP')
      RETURN(1) -- Failure
    END
  END


  -- SCHEDULE specific processing for INSERT, UPDATE or DELETE schedule operations
  -- All msx jobs that use the specified @schedule_uid will be notified with an Insert operation.
  -- This will cause agent to reload all schedules for each job.
  -- This is compatible with the legacy shiloh servers that don't know about reusable schedules
  IF (@object_type = 'SCHEDULE')
  BEGIN
    -- Validate the schedule
    -- Check if the schedule exists
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysschedules_localserver_view
                    WHERE (schedule_uid = @schedule_uid)))
    BEGIN
      SELECT @schedule_uid_as_char = CONVERT(VARCHAR(36), @schedule_uid)

      RAISERROR(14262, -1, -1, '@schedule_uid', @schedule_uid_as_char)
      RETURN(1) -- Failure
    END

    -- If this schedule is only used locally (no target servers) then there's nothing to do
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysschedules    s,
                        msdb.dbo.sysjobschedules  js,
                        msdb.dbo.sysjobs_view     sjv,
                        msdb.dbo.sysjobservers    sjs,
                        msdb.dbo.systargetservers sts
                    WHERE (s.schedule_uid = @schedule_uid)
                    AND (s.schedule_id = js.schedule_id)
                    AND (sjv.job_id = js.job_id)
                    AND (sjv.job_id = sjs.job_id)
                    AND (sjs.server_id = sts.server_id)
                    AND (sjs.server_id <> 0)))
    BEGIN
      RETURN(0)
    END

    -- Generate the sysdownloadlist row(s)...
    IF (@operation_code = 1) OR  -- Insert
       (@operation_code = 2) OR  -- Update
       (@operation_code = 3)     -- Delete
    BEGIN
      -- Insert specific schedule into sysdownloadlist
      -- We need to create a sysdownloadlist JOB INSERT record for each job that runs the schedule
     INSERT INTO msdb.dbo.sysdownloadlist
         (source_server,
          operation_code,
          object_type,
          object_id,
          target_server)
     SELECT @local_machine_name,
          1,             -- 1 means 'Insert'
          1,             -- 1 means 'JOB'
          sjv.job_id,
          sts.server_name
     FROM msdb.dbo.sysschedules     s,
           msdb.dbo.sysjobschedules  js,
           msdb.dbo.sysjobs_view     sjv,
         msdb.dbo.sysjobservers    sjs,
         systargetservers          sts
     WHERE (s.schedule_id = js.schedule_id)
        AND (js.job_id = sjv.job_id)
        AND (sjv.job_id = sjs.job_id)
      AND (sjs.server_id = sts.server_id)
        AND (s.schedule_uid = @schedule_uid)
      AND (sjs.server_id <> 0)            -- We want to exclude local jobs
      AND ((@specific_target_server_id IS NULL) OR (sjs.server_id = @specific_target_server_id))

      SELECT @instructions_posted = @@rowcount


    END
    ELSE
    BEGIN
      RAISERROR(14266, -1, -1, '@operation_code', 'UPDATE, DELETE')
      RETURN(1) -- Failure
    END
  END


  -- Server-specific processing...
  IF (@object_type = 'SERVER')
  BEGIN
    -- Generate the sysdownloadlist row(s)...
    IF (@operation_code = 6) OR  -- ReEnlist
       (@operation_code = 7) OR  -- Defect
       (@operation_code = 8) OR  -- Synchronize time (with MSX)
       (@operation_code = 9)     -- Set MSX polling interval (in seconds)
    BEGIN
      IF (@operation_code = 8)
      BEGIN
        EXECUTE master.dbo.xp_regread N'HKEY_LOCAL_MACHINE',
                                      N'SYSTEM\CurrentControlSet\Control\TimeZoneInformation',
                                      N'Bias',
                                      @msx_time_zone_adjustment OUTPUT,
                                      N'no_output'
        SELECT @msx_time_zone_adjustment = -ISNULL(@msx_time_zone_adjustment, 0)
      END

      INSERT INTO msdb.dbo.sysdownloadlist
            (source_server,
             operation_code,
             object_type,
             object_id,
             target_server)
      SELECT @local_machine_name,
             @operation_code,
             2,                  -- 2 means 'SERVER'
             CASE @operation_code
               WHEN 8 THEN CONVERT(UNIQUEIDENTIFIER, CONVERT(BINARY(16), -(@msx_time_zone_adjustment - sts.time_zone_adjustment)))
               WHEN 9 THEN CONVERT(UNIQUEIDENTIFIER, CONVERT(BINARY(16), @value))
               ELSE CONVERT(UNIQUEIDENTIFIER, 0x00)
             END,
             sts.server_name
      FROM systargetservers sts
      WHERE ((@specific_target_server_id IS NULL) OR (sts.server_id = @specific_target_server_id))
      SELECT @instructions_posted = @@rowcount
    END
    ELSE
    BEGIN
      RAISERROR(14266, -1, -1, '@operation_code', 'RE-ENLIST, DEFECT, SYNC-TIME, SET-POLL')
      RETURN(1) -- Failure
    END
  END


  -- Report number of rows inserted
  IF (@object_type = 'JOB') AND
     (@job_id = CONVERT(UNIQUEIDENTIFIER, 0x00)) AND
     (@instructions_posted = 0) AND
     (@specific_target_server_id IS NOT NULL)
    RAISERROR(14231, 0, 1, '@specific_target_server', @specific_target_server)
  ELSE
    RAISERROR(14230, 0, 1, @instructions_posted, @operation)

  -- Delete any [downloaded] instructions that are over the registry-defined limit
  IF (@specific_target_server IS NOT NULL)
    EXECUTE msdb.dbo.sp_downloaded_row_limiter @specific_target_server

  RETURN(0) -- 0 means success
END
go

/**************************************************************/
/* SP_VERIFY_PERFORMANCE_CONDITION                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_performance_condition...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_performance_condition')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_performance_condition
go
CREATE PROCEDURE sp_verify_performance_condition
  @performance_condition NVARCHAR(512)
AS
BEGIN
  DECLARE @delimiter_count INT
  DECLARE @temp_str        NVARCHAR(512)
  DECLARE @object_name     sysname
  DECLARE @counter_name    sysname
  DECLARE @instance_name   sysname
  DECLARE @pos             INT

  SET NOCOUNT ON

  -- The performance condition must have the format 'object|counter|instance|comparator|value'
  -- NOTE: 'instance' may be empty.
  IF (PATINDEX(N'%_|%_|%|[><=]|[0-9]%', @performance_condition) = 0)
  BEGIN
    RAISERROR(14507, 16, 1)
    RETURN(1) -- Failure
  END

  -- Parse the performance_condition
  SELECT @delimiter_count = 0

  --Ex: "SqlServer:General Statistics|User Connections||>|5" => "General Statistics|User Connections||>|5"
  SELECT @temp_str = SUBSTRING(@performance_condition,
				PATINDEX('%:%', @performance_condition)+1,
				DATALENGTH(@performance_condition) - (PATINDEX('%:%', @performance_condition)+1) )
  SELECT @pos = CHARINDEX(N'|', @temp_str)
  WHILE (@pos <> 0)
  BEGIN
    SELECT @delimiter_count = @delimiter_count + 1
    IF (@delimiter_count = 1) SELECT @object_name = SUBSTRING(@temp_str, 1, @pos - 1)
    IF (@delimiter_count = 2) SELECT @counter_name = SUBSTRING(@temp_str, 1, @pos - 1)
    IF (@delimiter_count = 3) SELECT @instance_name = SUBSTRING(@temp_str, 1, @pos - 1)
    SELECT @temp_str = SUBSTRING(@temp_str, @pos + 1, (DATALENGTH(@temp_str) / 2) - @pos)
    SELECT @pos = CHARINDEX(N'|', @temp_str)
  END
  IF (@delimiter_count <> 4)
  BEGIN
    RAISERROR(14507, 16, 1)
    RETURN(1) -- Failure
  END

  -- Check the object_name
  IF (NOT EXISTS (SELECT object_name
                  FROM dbo.sysalerts_performance_counters_view
                  WHERE (object_name = @object_name)))
  BEGIN
    RAISERROR(14262, 16, 1, 'object_name', @object_name)
    RETURN(1) -- Failure
  END

  -- Check the counter_name
  IF (NOT EXISTS (SELECT counter_name
                  FROM dbo.sysalerts_performance_counters_view
                  WHERE (object_name = @object_name)
                    AND (counter_name = @counter_name)))
  BEGIN
    RAISERROR(14262, 16, 1, 'counter_name', @counter_name)
    RETURN(1) -- Failure
  END

  -- Check the instance_name
  IF (@instance_name IS NOT NULL)
  BEGIN
    IF (NOT EXISTS (SELECT instance_name
                    FROM dbo.sysalerts_performance_counters_view
                    WHERE (object_name = @object_name)
                      AND (counter_name = @counter_name)
                      AND (instance_name = @instance_name)))
    BEGIN
      RAISERROR(14262, 16, 1, 'instance_name', @instance_name)
      RETURN(1) -- Failure
    END
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_VERIFY_JOB_DATE                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_job_date...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_job_date')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_job_date
go
CREATE PROCEDURE sp_verify_job_date
  @date           INT,
  @date_name      VARCHAR(60) = 'date',
  @error_severity INT         = -1
AS
BEGIN
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @date_name = LTRIM(RTRIM(@date_name))

  IF ((ISDATE(CONVERT(VARCHAR, @date)) = 0) OR (@date < 19900101) OR (@date > 99991231))
  BEGIN
    RAISERROR(14266, @error_severity, -1, @date_name, '19900101..99991231')
    RETURN(1) -- Failure
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_VERIFY_JOB_TIME                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_job_time...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_job_time')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_job_time
go

CREATE PROCEDURE sp_verify_job_time
  @time           INT,
  @time_name      VARCHAR(60) = 'time',
  @error_severity INT = -1
AS
BEGIN
  DECLARE @hour      INT
  DECLARE @minute    INT
  DECLARE @second    INT
  DECLARE @part_name NVARCHAR(50)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @time_name = LTRIM(RTRIM(@time_name))

  IF ((@time < 0) OR (@time > 235959))
  BEGIN
    RAISERROR(14266, @error_severity, -1, @time_name, '000000..235959')
    RETURN(1) -- Failure
  END

  SELECT @hour   = (@time / 10000)
  SELECT @minute = (@time % 10000) / 100
  SELECT @second = (@time % 100)

  -- Check hour range
  IF (@hour > 23)
  BEGIN
    SELECT @part_name = FORMATMESSAGE(14218)
    RAISERROR(14287, @error_severity, -1, @time_name, @part_name)
    RETURN(1) -- Failure
  END

  -- Check minute range
  IF (@minute > 59)
  BEGIN
    SELECT @part_name = FORMATMESSAGE(14219)
    RAISERROR(14287, @error_severity, -1, @time_name, @part_name)
    RETURN(1) -- Failure
  END

  -- Check second range
  IF (@second > 59)
  BEGIN
    SELECT @part_name = FORMATMESSAGE(14220)
    RAISERROR(14287, @error_severity, -1, @time_name, @part_name)
    RETURN(1) -- Failure
  END

  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_VERIFY_ALERT                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_alert...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_alert')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_alert
go
CREATE PROCEDURE sp_verify_alert
  @name                          sysname,
  @message_id                    INT,
  @severity                      INT,
  @enabled                       TINYINT,
  @delay_between_responses       INT,
  @notification_message          NVARCHAR(512),
  @include_event_description_in  TINYINT,
  @database_name                 sysname,
  @event_description_keyword     NVARCHAR(100),
  @job_id                        UNIQUEIDENTIFIER OUTPUT,
  @job_name                      sysname          OUTPUT,
  @occurrence_count              INT,
  @raise_snmp_trap               TINYINT,
  @performance_condition         NVARCHAR(512),
  @category_name                 sysname,
  @category_id                   INT              OUTPUT,
  @count_reset_date              INT,
  @count_reset_time              INT,
  @wmi_namespace      NVARCHAR(512),      -- New for 9.0
  @wmi_query          NVARCHAR(512),      -- New for 9.0
  @event_id        INT     OUTPUT   -- New for 9.0
AS
BEGIN
  DECLARE @retval               INT
  DECLARE @non_alertable_errors VARCHAR(512)
  DECLARE @message_id_as_string VARCHAR(10)
  DECLARE @res_valid_range      NVARCHAR(100)
  DECLARE @alert_no_wmi_check   INT
  DECLARE @job_owner_sid        VARBINARY(85)

  SET NOCOUNT ON

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (SERVERPROPERTY('EngineEdition') = 8)
   BEGIN
    RAISERROR(41914, -1, 17, 'alerting');
    RETURN(1) -- Failure
  END

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (SERVERPROPERTY('EngineEdition') = 8 AND (@include_event_description_in & 4 = 4))
  BEGIN
    RAISERROR(41914, -1, 1, 'NetSend')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @name                      = LTRIM(RTRIM(@name))
  SELECT @notification_message      = LTRIM(RTRIM(@notification_message))
  SELECT @database_name             = LTRIM(RTRIM(@database_name))
  SELECT @event_description_keyword = LTRIM(RTRIM(@event_description_keyword))
  SELECT @job_name                  = LTRIM(RTRIM(@job_name))
  SELECT @performance_condition     = LTRIM(RTRIM(@performance_condition))
  SELECT @category_name             = LTRIM(RTRIM(@category_name))
  SELECT @alert_no_wmi_check        = 0

  -- Only a sysadmin can do this

  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check if the NewName is unique
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysalerts
              WHERE (name = @name)))
  BEGIN
    RAISERROR(14261, 16, 1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Check if the user has supplied MessageID OR Severity OR Performance-Condition OR WMI namespace/query
  IF ((@performance_condition IS NULL) AND (@message_id = 0) AND (@severity = 0) AND ((@wmi_namespace IS NULL) OR (@wmi_query IS NULL))) OR
     ((@performance_condition IS NOT NULL) AND ((@message_id <> 0) OR (@severity <> 0) OR (@wmi_namespace IS NOT NULL) OR (@wmi_query IS NOT NULL))) OR
     ((@message_id <> 0) AND ((@performance_condition IS NOT NULL) OR (@severity <> 0) OR (@wmi_namespace IS NOT NULL) OR (@wmi_query IS NOT NULL))) OR
     ((@severity <> 0) AND ((@performance_condition IS NOT NULL) OR (@message_id <> 0) OR (@wmi_namespace IS NOT NULL) OR (@wmi_query IS NOT NULL)))
  BEGIN
    RAISERROR(14500, 16, 1)
    RETURN(1) -- Failure
  END

  -- Check the Severity
  IF ((@severity < 0) OR (@severity > 25))
  BEGIN
    RAISERROR(14266, 16, 1, '@severity', '0..25')
    RETURN(1) -- Failure
  END

    -- Check the MessageID
    -- Allow if message id = 50000 (RAISERROR called with no specific message id)
    IF(@message_id <> 50000)
    BEGIN
        IF (@message_id <> 0) AND
            (NOT EXISTS (SELECT message_id
                            FROM sys.messages
                            WHERE message_id = @message_id))
        BEGIN
            SELECT @message_id_as_string = CONVERT(VARCHAR, @message_id)
            RAISERROR(14262, 16, 1, '@message_id', @message_id_as_string)
            RETURN(1) -- Failure
        END
    END

  -- Check if it is legal to set an alert on this MessageID
  DECLARE @TempRetVal TABLE (RetVal INT)
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'NonAlertableErrors',
                                         @non_alertable_errors OUTPUT,
                                         N'no_output'
  IF (ISNULL(@non_alertable_errors, N'NULL') <> N'NULL')
  BEGIN
    DECLARE @message_id_as_char VARCHAR(10)

    SELECT @message_id_as_char = CONVERT(VARCHAR(10), @message_id)
    INSERT INTO @TempRetVal
    EXECUTE ('IF (' + @message_id_as_char + ' IN (' + @non_alertable_errors + ')) SELECT 1')
  END

  IF (EXISTS (SELECT *
              FROM @TempRetVal))
  BEGIN
    RAISERROR(14506, 16, 1, @message_id)
    RETURN(1) -- Failure
  END

  -- Enabled must be 0 or 1
  IF (@enabled NOT IN (0, 1))
  BEGIN
    RAISERROR(14266, 16, 1, '@enabled', '0, 1')
    RETURN(1) -- Failure
  END

  -- DelayBetweenResponses must be > 0
  IF (@delay_between_responses < 0)
  BEGIN
    SELECT @res_valid_range = FORMATMESSAGE(14206)
    RAISERROR(14266, 16, 1, '@delay_between_responses', @res_valid_range)
    RETURN(1) -- Failure
  END

  -- NOTE: We don't check the notification message

  -- Check IncludeEventDescriptionIn
  IF ((@include_event_description_in < 0) OR (@include_event_description_in > 7))
  BEGIN
    SELECT @res_valid_range = FORMATMESSAGE(14208)
    RAISERROR(14266, 16, 1, '@include_event_description_in', @res_valid_range)
    RETURN(1) -- Failure
  END

  -- Check the database name
  IF (@database_name IS NOT NULL) AND (DB_ID(@database_name) IS NULL)
  BEGIN
    RAISERROR(15010, 16, 1, @database_name)
    RETURN(1) -- Failure
  END

  -- NOTE: We don't check the event description keyword

  -- Check JobName/ID
  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    -- We use '' as a special value which means 'no job' (we cannot use NULL since this forces
    -- sp_update_alert to use the existing value)
    IF (@job_name = N'')
      SELECT @job_id = 0x00
    ELSE
    BEGIN
      EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                  '@job_id',
                                                   @job_name OUTPUT,
                                                   @job_id   OUTPUT,
                                       @owner_sid = @job_owner_sid OUTPUT
      IF (@retval <> 0)
        RETURN(1) -- Failure

     -- Check permissions beyond what's checked by the sysjobs_view
     -- SQLAgentReaderRole and SQLAgentOperatorRole can see all jobs but
     -- cannot modify them
     IF (@job_owner_sid <> SUSER_SID()                   -- does not own the job
        AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))   -- is not sysadmin
     BEGIN
       RAISERROR(14525, -1, -1);
       RETURN(1) -- Failure
     END

      -- Check that the job is a local job
      IF (NOT EXISTS (SELECT *
                      FROM msdb.dbo.sysjobservers
                      WHERE (job_id = @job_id)
                        AND (server_id = 0)))
      BEGIN
        RAISERROR(14527, -1, -1, @job_name)
        RETURN(1) -- Failure
      END
    END
  END

  -- OccurrenceCount must be > 0
  IF (@occurrence_count < 0)
  BEGIN
    RAISERROR(14266, 16, 1, '@occurrence_count', '0..n')
    RETURN(1) -- Failure
  END

  -- RaiseSNMPTrap must be 0 or 1
  IF (@raise_snmp_trap NOT IN (0, 1))
  BEGIN
    RAISERROR(14266, 16, 1, '@raise_snmp_trap', '0, 1')
    RETURN(1) -- Failure
  END

  -- Check the performance condition (including invalid parameter combinations)
  IF (@performance_condition IS NOT NULL)
  BEGIN
    IF (@database_name IS NOT NULL)
    BEGIN
      RAISERROR(14505, 16, 1, '@database_name')
      RETURN(1) -- Failure
    END

    IF (@event_description_keyword IS NOT NULL)
    BEGIN
      RAISERROR(14505, 16, 1, '@event_description_keyword')
      RETURN(1) -- Failure
    END

    IF (@wmi_namespace IS NOT NULL)
    BEGIN
      RAISERROR(14505, 16, 1, '@wmi_namespace')
      RETURN(1) -- Failure
    END

    IF (@wmi_query IS NOT NULL)
    BEGIN
      RAISERROR(14505, 16, 1, '@wmi_query')
      RETURN(1) -- Failure
    END

    -- Verify the performance condition
    EXECUTE @retval = msdb.dbo.sp_verify_performance_condition @performance_condition
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check category name
  IF (@category_name = N'[DEFAULT]')
    SELECT @category_id = 98
  ELSE
  BEGIN
    SELECT @category_id = category_id
    FROM msdb.dbo.syscategories
    WHERE (category_class = 2) -- Alerts
      AND (category_type = 3) -- None
      AND (name = @category_name)
  END
  IF (@category_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@category_name', @category_name)
    RETURN(1) -- Failure
  END

  -- Check count reset date
  IF (@count_reset_date <> 0)
  BEGIN
    EXECUTE @retval = msdb.dbo.sp_verify_job_date @count_reset_date, '@count_reset_date'
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check count reset time
  IF (@count_reset_time <> 0)
  BEGIN
    EXECUTE @retval = msdb.dbo.sp_verify_job_time @count_reset_time, '@count_reset_time'
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check WMI parameters. Both must exist
  IF (@wmi_namespace IS NOT NULL)
  BEGIN
    IF (@wmi_query IS NULL)
   BEGIN
      RAISERROR(14509, 16, 1, '@wmi_query')
     RETURN(1) -- Failure
   END

    IF (@database_name IS NOT NULL)
    BEGIN
      RAISERROR(14510, 16, 1, '@database_name')
      RETURN(1) -- Failure
    END

    IF (@event_description_keyword IS NOT NULL)
    BEGIN
      RAISERROR(14510, 16, 1, '@event_description_keyword')
      RETURN(1) -- Failure
    END

    --do not check WMI properties if a registry setting is present
    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'AlertNoWmiCheck',
                                           @alert_no_wmi_check OUTPUT,
                                           'no_output'
    if (@alert_no_wmi_check <> 1)
    BEGIN
      EXECUTE @retval = msdb.dbo.sp_sqlagent_notify @op_type = N'T',
                    @wmi_namespace = @wmi_namespace,
               @wmi_query  = @wmi_query,
               @error_flag = 0
      IF (@retval <> 0)
     BEGIN
       RAISERROR(14511, 16, 1)
         RETURN(1) -- Failure
     END
    END

   -- Set event_id to indicate WMI alert
    SELECT @event_id = 8
  END
  ELSE IF (@wmi_query IS NOT NULL)
  BEGIN
    RAISERROR(14512, 16, 1, '@wmi_namespace')
    RETURN(1) -- Failure
  END

  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_UPDATE_ALERT                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_alert...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_alert')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_alert
go
CREATE PROCEDURE sp_update_alert
  @name                         sysname,
  @new_name                     sysname          = NULL,
  @enabled                      TINYINT          = NULL,
  @message_id                   INT              = NULL,
  @severity                     INT              = NULL,
  @delay_between_responses      INT              = NULL,
  @notification_message         NVARCHAR(512)    = NULL,
  @include_event_description_in TINYINT          = NULL, -- 0 = None, 1 = Email, 2 = Pager. 4 = NetSend, 7 = All
  @database_name                sysname          = NULL,
  @event_description_keyword    NVARCHAR(100)    = NULL,
  @job_id                       UNIQUEIDENTIFIER = NULL, -- If provided must NOT also provide job_name
  @job_name                     sysname          = NULL, -- If provided must NOT also provide job_id
  @occurrence_count             INT              = NULL, -- Can only be set to 0
  @count_reset_date             INT              = NULL,
  @count_reset_time             INT              = NULL,
  @last_occurrence_date         INT              = NULL, -- Can only be set to 0
  @last_occurrence_time         INT              = NULL, -- Can only be set to 0
  @last_response_date           INT              = NULL, -- Can only be set to 0
  @last_response_time           INT              = NULL, -- Can only be set to 0
  @raise_snmp_trap              TINYINT          = NULL,
  @performance_condition        NVARCHAR(512)    = NULL, -- New for 7.0
  @category_name                sysname          = NULL, -- New for 7.0
  @wmi_namespace           sysname         = NULL, -- New for 9.0
  @wmi_query               NVARCHAR(512)   = NULL  -- New for 9.0
AS
BEGIN
  DECLARE @x_enabled                   TINYINT
  DECLARE @x_message_id                INT
  DECLARE @x_severity                  INT
  DECLARE @x_delay_between_responses   INT
  DECLARE @x_notification_message      NVARCHAR(512)
  DECLARE @x_include_event_description TINYINT
  DECLARE @x_database_name             sysname
  DECLARE @x_event_description_keyword NVARCHAR(100)
  DECLARE @x_occurrence_count          INT
  DECLARE @x_count_reset_date          INT
  DECLARE @x_count_reset_time          INT
  DECLARE @x_last_occurrence_date      INT
  DECLARE @x_last_occurrence_time      INT
  DECLARE @x_last_response_date        INT
  DECLARE @x_last_response_time        INT
  DECLARE @x_flags                     INT
  DECLARE @x_performance_condition     NVARCHAR(512)
  DECLARE @x_job_id                    UNIQUEIDENTIFIER
  DECLARE @x_category_id               INT
  DECLARE @x_event_id                  INT
  DECLARE @x_wmi_namespace          sysname
  DECLARE @x_wmi_query              NVARCHAR(512)

  DECLARE @include_event_desc_code     TINYINT
  DECLARE @return_code                 INT
  DECLARE @duplicate_name              sysname
  DECLARE @category_id                 INT
  DECLARE @alert_id                    INT
  DECLARE @cached_attribute_modified   INT
  DECLARE @event_id                 INT

  SET NOCOUNT ON

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (SERVERPROPERTY('EngineEdition') = 8 AND (@include_event_description_in & 4 = 4))
  BEGIN
    RAISERROR(41914, -1, 2, 'NetSend')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @new_name                  = LTRIM(RTRIM(@new_name))
  SELECT @job_name                  = LTRIM(RTRIM(@job_name))
  SELECT @notification_message      = LTRIM(RTRIM(@notification_message))
  SELECT @database_name             = LTRIM(RTRIM(@database_name))
  SELECT @event_description_keyword = LTRIM(RTRIM(@event_description_keyword))
  SELECT @performance_condition     = LTRIM(RTRIM(@performance_condition))
  SELECT @category_name             = LTRIM(RTRIM(@category_name))

  -- Are we modifying an attribute which SQLServerAgent caches?
  IF ((@new_name                     IS NOT NULL) OR
      (@enabled                      IS NOT NULL) OR
      (@message_id                   IS NOT NULL) OR
      (@severity                     IS NOT NULL) OR
      (@delay_between_responses      IS NOT NULL) OR
      (@notification_message         IS NOT NULL) OR
      (@include_event_description_in IS NOT NULL) OR
      (@database_name                IS NOT NULL) OR
      (@event_description_keyword    IS NOT NULL) OR
      (@job_id                       IS NOT NULL) OR
      (@job_name                     IS NOT NULL) OR
      (@last_response_date           IS NOT NULL) OR
      (@last_response_time           IS NOT NULL) OR
      (@raise_snmp_trap              IS NOT NULL) OR
      (@performance_condition        IS NOT NULL) OR
      (@wmi_namespace             IS NOT NULL) OR
      (@wmi_query              IS NOT NULL))
    SELECT @cached_attribute_modified = 1
  ELSE
    SELECT @cached_attribute_modified = 0

  -- Map a job_id of 0 to the real value we use to mean 'no job'
  IF (@job_id = CONVERT(UNIQUEIDENTIFIER, 0x00)) AND (@job_name IS NULL)
    SELECT @job_name = N''

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1)
  END

  -- Check if SQLServerAgent is in the process of starting
  EXECUTE @return_code = msdb.dbo.sp_is_sqlagent_starting
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  -- Check if this Alert exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysalerts
                  WHERE (name = @name)))
  BEGIN
    RAISERROR(14262, 16, 1, '@name', @name)
    RETURN(1)
  END

  -- Certain values (if supplied) may only be updated to 0
  IF (@occurrence_count <> 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@occurrence_count', '0')
    RETURN(1) -- Failure
  END
  IF (@last_occurrence_date <> 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@last_occurrence_date', '0')
    RETURN(1) -- Failure
  END
  IF (@last_occurrence_time <> 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@last_occurrence_time', '0')
    RETURN(1) -- Failure
  END
  IF (@last_response_date <> 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@last_response_date', '0')
    RETURN(1) -- Failure
  END
  IF (@last_response_time <> 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@last_response_time', '0')
    RETURN(1) -- Failure
  END

  -- Get existing (@x_) values
  SELECT @alert_id                    = id,
         @x_enabled                   = enabled,
         @x_message_id                = message_id,
         @x_severity                  = severity,
         @x_delay_between_responses   = delay_between_responses,
         @x_notification_message      = notification_message,
         @x_include_event_description = include_event_description,
         @x_database_name             = database_name,
         @x_event_description_keyword = event_description_keyword,
         @x_occurrence_count          = occurrence_count,
         @x_count_reset_date          = count_reset_date,
         @x_count_reset_time          = count_reset_time,
         @x_job_id                    = job_id,
         @x_last_occurrence_date      = last_occurrence_date,
         @x_last_occurrence_time      = last_occurrence_time,
         @x_last_response_date        = last_response_date,
         @x_last_response_time        = last_response_time,
         @x_flags                     = flags,
         @x_performance_condition     = performance_condition,
         @x_category_id               = category_id,
       @x_event_id              = event_id
  FROM msdb.dbo.sysalerts
  WHERE (name = @name)

  SELECT @x_job_id = sjv.job_id
  FROM msdb.dbo.sysalerts    sa,
       msdb.dbo.sysjobs_view sjv
  WHERE (sa.job_id = sjv.job_id)
    AND (sa.name = @name)

  -- Fill out the values for all non-supplied parameters from the existsing values
  IF (@x_event_id = 8)
  BEGIN
   -- WMI alert type
   IF (@wmi_namespace IS NULL) SELECT @wmi_namespace = @x_database_name
   IF (@wmi_query IS NULL) SELECT @wmi_query = @x_performance_condition
  END
  ELSE
  BEGIN
   -- Non-WMI alert type
   IF (@database_name IS NULL) SELECT @database_name = @x_database_name
   IF (@performance_condition IS NULL) SELECT @performance_condition = @x_performance_condition
  END

  IF (@enabled                      IS NULL) SELECT @enabled                      = @x_enabled
  IF (@message_id                   IS NULL) SELECT @message_id                   = @x_message_id
  IF (@severity                     IS NULL) SELECT @severity                     = @x_severity
  IF (@delay_between_responses      IS NULL) SELECT @delay_between_responses      = @x_delay_between_responses
  IF (@notification_message         IS NULL) SELECT @notification_message         = @x_notification_message
  IF (@include_event_description_in IS NULL) SELECT @include_event_description_in = @x_include_event_description
  IF (@event_description_keyword    IS NULL) SELECT @event_description_keyword    = @x_event_description_keyword
  IF (@job_id IS NULL) AND (@job_name IS NULL) SELECT @job_id                     = @x_job_id
  IF (@occurrence_count             IS NULL) SELECT @occurrence_count             = @x_occurrence_count
  IF (@count_reset_date             IS NULL) SELECT @count_reset_date             = @x_count_reset_date
  IF (@count_reset_time             IS NULL) SELECT @count_reset_time             = @x_count_reset_time
  IF (@last_occurrence_date         IS NULL) SELECT @last_occurrence_date         = @x_last_occurrence_date
  IF (@last_occurrence_time         IS NULL) SELECT @last_occurrence_time         = @x_last_occurrence_time
  IF (@last_response_date           IS NULL) SELECT @last_response_date           = @x_last_response_date
  IF (@last_response_time           IS NULL) SELECT @last_response_time           = @x_last_response_time
  IF (@raise_snmp_trap              IS NULL) SELECT @raise_snmp_trap              = @x_flags & 0x1
  IF (@category_name                IS NULL) SELECT @category_name = name FROM msdb.dbo.syscategories WHERE (category_id = @x_category_id)

  IF (@category_name IS NULL)
  BEGIN
    SELECT @category_name = name
    FROM msdb.dbo.syscategories
    WHERE (category_id = 98)
  END

  -- Turn [nullable] empty string parameters into NULLs
  IF (@new_name                  = N'') SELECT @new_name                  = NULL
  IF (@notification_message      = N'') SELECT @notification_message      = NULL
  IF (@database_name             = N'') SELECT @database_name             = NULL
  IF (@event_description_keyword = N'') SELECT @event_description_keyword = NULL
  IF (@performance_condition     = N'') SELECT @performance_condition     = NULL
  IF (@wmi_namespace        = N'') SELECT @wmi_namespace         = NULL
  IF (@wmi_query            = N'') SELECT @wmi_query             = NULL

  -- Verify the Alert
  IF (@job_id = CONVERT(UNIQUEIDENTIFIER, 0x00))
    SELECT @job_id = NULL
  EXECUTE @return_code = sp_verify_alert @new_name,
                                         @message_id,
                                         @severity,
                                         @enabled,
                                         @delay_between_responses,
                                         @notification_message,
                                         @include_event_description_in,
                                         @database_name,
                                         @event_description_keyword,
                                         @job_id OUTPUT,
                                         @job_name OUTPUT,
                                         @occurrence_count,
                                         @raise_snmp_trap,
                                         @performance_condition,
                                         @category_name,
                                         @category_id OUTPUT,
                                         @count_reset_date,
                                         @count_reset_time,
                                         @wmi_namespace,
                                         @wmi_query,
                                     @event_id OUTPUT
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  -- If the user didn't supply a NewName, use the old one.
  -- NOTE: This must be done AFTER sp_verify_alert.
  IF (@new_name IS NULL)
    SELECT @new_name = @name

  -- Turn the 1st 'flags' bit on or off accordingly
  IF (@raise_snmp_trap = 0)
    SELECT @x_flags = @x_flags & 0xFFFE
  ELSE
    SELECT @x_flags = @x_flags | 0x0001

  -- For WMI alerts replace
  -- database_name with wmi_namespace and
  -- performance_conditon with wmi_query
  -- so we can store them in those columns in sysalerts table
  IF (@event_id = 8)
  BEGIN
   SELECT @database_name = @wmi_namespace
   SELECT @performance_condition = @wmi_query
  END

  -- Check if this Alert already exists
  SELECT @duplicate_name = FORMATMESSAGE(14205)
  SELECT @duplicate_name = name
  FROM msdb.dbo.sysalerts
  WHERE ((event_id = 8) AND
       (ISNULL(performance_condition, N'') = ISNULL(@performance_condition, N'')) AND
       (ISNULL(database_name, N'') = ISNULL(@database_name, N''))) OR
      ((ISNULL(event_id,1) <> 8) AND
       (ISNULL(performance_condition, N'apples') = ISNULL(@performance_condition, N'oranges'))) OR
      ((performance_condition IS NULL) AND
         (message_id = @message_id) AND
         (severity = @severity) AND
         (ISNULL(database_name, N'') = ISNULL(@database_name, N'')) AND
         (ISNULL(event_description_keyword, N'') = ISNULL(@event_description_keyword, N'')))
  IF (@duplicate_name <> FORMATMESSAGE(14205) AND @duplicate_name <> @name)
  BEGIN
    RAISERROR(14501, 16, 1, @duplicate_name)
    RETURN(1) -- Failure
  END

  -- Finally, do the actual UPDATE
  UPDATE msdb.dbo.sysalerts
  SET name                        = @new_name,
      message_id                  = @message_id,
      severity                    = @severity,
      enabled                     = @enabled,
      delay_between_responses     = @delay_between_responses,
      notification_message        = @notification_message,
      include_event_description   = @include_event_description_in,
      database_name               = @database_name,
      event_description_keyword   = @event_description_keyword,
      job_id                      = ISNULL(@job_id, CONVERT(UNIQUEIDENTIFIER, 0x00)),
      occurrence_count            = @occurrence_count,
      count_reset_date            = @count_reset_date,
      count_reset_time            = @count_reset_time,
      last_occurrence_date        = @last_occurrence_date,
      last_occurrence_time        = @last_occurrence_time,
      last_response_date          = @last_response_date,
      last_response_time          = @last_response_time,
      flags                       = @x_flags,
      performance_condition       = @performance_condition,
      category_id                 = @category_id,
      event_id               = @event_id
  WHERE (name = @name)

  -- Notify SQLServerAgent of the change
  IF (@cached_attribute_modified = 1)
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'A',
                                        @alert_id    = @alert_id,
                                        @action_type = N'U'
  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_DELETE_JOB_REFERENCES                                   */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_job_references...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_job_references')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_job_references
go
CREATE PROCEDURE sp_delete_job_references
  @notify_sqlagent BIT = 1
AS
BEGIN
  DECLARE @deleted_job_id  UNIQUEIDENTIFIER
  DECLARE @task_id_as_char VARCHAR(10)
  DECLARE @job_is_cached   INT
  DECLARE @alert_name      sysname
  DECLARE @maintplan_plan_id  UNIQUEIDENTIFIER
  DECLARE @maintplan_subplan_id  UNIQUEIDENTIFIER

  -- Keep SQLServerAgent's cache in-sync and cleanup any 'webtask' cross-references to the deleted job(s)
  -- NOTE: The caller must have created a table called #temp_jobs_to_delete of the format
  --       (job_id UNIQUEIDENTIFIER NOT NULL, job_is_cached INT NOT NULL).

  DECLARE sqlagent_notify CURSOR LOCAL
  FOR
  SELECT job_id, job_is_cached
  FROM #temp_jobs_to_delete

  OPEN sqlagent_notify
  FETCH NEXT FROM sqlagent_notify INTO @deleted_job_id, @job_is_cached

  WHILE (@@fetch_status = 0)
  BEGIN
    -- NOTE: We only notify SQLServerAgent if we know the job has been cached
    IF(@job_is_cached = 1 AND @notify_sqlagent = 1)
      EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                          @job_id      = @deleted_job_id,
                                          @action_type = N'D'

    IF (EXISTS (SELECT *
                FROM master.dbo.sysobjects
                WHERE (name = N'sp_cleanupwebtask')
                  AND (type = 'P')))
    BEGIN
      SELECT @task_id_as_char = CONVERT(VARCHAR(10), task_id)
      FROM msdb.dbo.systaskids
      WHERE (job_id = @deleted_job_id)
      IF (@task_id_as_char IS NOT NULL)
        EXECUTE ('master.dbo.sp_cleanupwebtask @taskid = ' + @task_id_as_char)
    END

    -- Maintenance plan cleanup for SQL 2005.
    -- If this job came from another server and it runs a subplan of a
    -- maintenance plan, then delete the subplan record. If that was
    -- the last subplan still referencing that plan, delete the plan.
    -- This removes a distributed maintenance plan from a target server
    -- once all of jobs from the master server that used that maintenance
    -- plan are deleted.
    SELECT @maintplan_plan_id = plans.plan_id, @maintplan_subplan_id = plans.subplan_id
    FROM sysmaintplan_subplans plans, sysjobs_view sjv
    WHERE plans.job_id = @deleted_job_id
      AND plans.job_id = sjv.job_id
      AND sjv.master_server = 1 -- This means the job came from the master

    IF (@maintplan_subplan_id is not NULL)
    BEGIN
      EXECUTE sp_maintplan_delete_subplan @subplan_id = @maintplan_subplan_id, @delete_jobs = 0
      IF (NOT EXISTS (SELECT *
                      FROM sysmaintplan_subplans
                      where plan_id = @maintplan_plan_id))
      BEGIN
        DECLARE @plan_name sysname

        SELECT @plan_name = name
          FROM sysmaintplan_plans
          WHERE id = @maintplan_plan_id

        EXECUTE sp_ssis_deletepackage @name = @plan_name, @folderid = '08aa12d5-8f98-4dab-a4fc-980b150a5dc8' -- this is the guid for 'Maintenance Plans'
      END
    END

    FETCH NEXT FROM sqlagent_notify INTO @deleted_job_id, @job_is_cached
  END
  DEALLOCATE sqlagent_notify

  -- Remove systaskid references (must do this AFTER sp_cleanupwebtask stuff)
  DELETE FROM msdb.dbo.systaskids
  WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

  -- Remove sysdbmaintplan_jobs references (legacy maintenance plans prior to SQL 2005)
  DELETE FROM msdb.dbo.sysdbmaintplan_jobs
  WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

  -- Finally, clean up any dangling references in sysalerts to the deleted job(s)
  DECLARE sysalerts_cleanup CURSOR LOCAL
  FOR
  SELECT name
  FROM msdb.dbo.sysalerts
  WHERE (job_id IN (SELECT job_id FROM #temp_jobs_to_delete))

  OPEN sysalerts_cleanup
  FETCH NEXT FROM sysalerts_cleanup INTO @alert_name
  WHILE (@@fetch_status = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_update_alert @name   = @alert_name,
                                     @job_id = 0x00
    FETCH NEXT FROM sysalerts_cleanup INTO @alert_name
  END
  DEALLOCATE sysalerts_cleanup
END
go

/**************************************************************/
/* SP_DELETE_ALL_MSX_JOBS                                     */
/*                                                            */
/* NOTE: This is a separate procedure because SQLServerAgent  */
/*       needs to call it, as does sp_msx_defect and          */
/*       sp_delete_job.                                       */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_all_msx_jobs...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_all_msx_jobs')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_all_msx_jobs
go
CREATE PROCEDURE sp_delete_all_msx_jobs
  @msx_server   sysname,
  @jobs_deleted INT = NULL OUTPUT
AS
BEGIN
  SET NOCOUNT ON

  -- Change server name to always reflect real servername or servername\instancename
  IF (UPPER(@msx_server collate SQL_Latin1_General_CP1_CS_AS) = '(LOCAL)')
    SELECT @msx_server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

  -- Delete all the jobs that originated from the MSX
  -- Note: This temp table is referenced by msdb.dbo.sp_delete_job_references
  CREATE TABLE #temp_jobs_to_delete (job_id UNIQUEIDENTIFIER NOT NULL, job_is_cached INT NOT NULL, owner_sid VARBINARY(85) NOT NULL)

  -- Table of msx schedules to delete
  DECLARE @temp_schedules_to_delete TABLE (schedule_id INT NOT NULL)

  -- Non-sysadmins can only delete jobs they own. sysjobs_view returns all jobs
  -- for members of SQLAgentReaderRole and SQLAgentOperatorRole, but they should
  -- not be able to delete those jobs
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
  BEGIN
   -- NOTE: The left outer-join here is to handle the [unlikely] case of missing sysjobservers rows
   INSERT INTO #temp_jobs_to_delete
   SELECT sjv.job_id,
         CASE sjs.server_id WHEN 0 THEN 1 ELSE 0 END,
         sjv.owner_sid
   FROM msdb.dbo.sysjobs_view sjv
      LEFT OUTER JOIN msdb.dbo.sysjobservers sjs ON (sjv.job_id = sjs.job_id)
   WHERE (ISNULL(sjs.server_id, 0) = 0)
      AND (sjv.originating_server = @msx_server)
  END
  ELSE
  BEGIN
   -- NOTE: The left outer-join here is to handle the [unlikely] case of missing sysjobservers rows
   INSERT INTO #temp_jobs_to_delete
   SELECT sjv.job_id,
         CASE sjs.server_id WHEN 0 THEN 1 ELSE 0 END,
         sjv.owner_sid
   FROM msdb.dbo.sysjobs_view sjv
      LEFT OUTER JOIN msdb.dbo.sysjobservers sjs ON (sjv.job_id = sjs.job_id)
   WHERE (ISNULL(sjs.server_id, 0) = 0)
      AND (sjv.originating_server = @msx_server)
      AND (sjv.owner_sid = SUSER_SID())
  END

  -- Must do this before deleting the job itself since sp_sqlagent_notify does a lookup on sysjobs_view
  EXECUTE msdb.dbo.sp_delete_job_references

  BEGIN TRANSACTION

    --Get the list of schedules to delete, these cant be deleted until the references are deleted in sysjobschedules
    INSERT INTO @temp_schedules_to_delete
    SELECT DISTINCT schedule_id
    FROM   msdb.dbo.sysschedules
    WHERE (schedule_id IN
            (SELECT schedule_id
            FROM msdb.dbo.sysjobschedules as js
           JOIN #temp_jobs_to_delete as tjd ON (js.job_id = tjd.job_id)))

    DELETE FROM msdb.dbo.sysjobschedules
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

    --Now OK to delete the schedule
    DELETE FROM msdb.dbo.sysschedules
    WHERE schedule_id IN
    (SELECT schedule_id
        FROM @temp_schedules_to_delete)

    DELETE FROM msdb.dbo.sysjobservers
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

    DELETE FROM msdb.dbo.sysjobsteps
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

    DELETE FROM msdb.dbo.sysjobs
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

    DELETE FROM msdb.dbo.sysjobhistory
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

   --Finally cleanup any orphaned sysschedules that were downloaded from the MSX
   DELETE msdb.dbo.sysschedules
   FROM msdb.dbo.sysschedules s
      JOIN msdb.dbo.sysoriginatingservers_view os ON (s.originating_server_id = os.originating_server_id)
   WHERE (os.originating_server = @msx_server)

  COMMIT TRANSACTION

  SELECT @jobs_deleted = COUNT(*)
  FROM #temp_jobs_to_delete

  DROP TABLE #temp_jobs_to_delete
END
go


/**************************************************************/
/* SP_GENERATE_TARGET_SERVER_JOB_ASSIGNMENT_SQL               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_generate_target_server_job_assignment_sql...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_generate_target_server_job_assignment_sql')
              AND (type = 'P')))
  DROP PROCEDURE sp_generate_target_server_job_assignment_sql
go
CREATE PROCEDURE sp_generate_target_server_job_assignment_sql
  @server_name     sysname = NULL,
  @new_server_name sysname = NULL  -- Use this if the target server computer has been renamed
AS
BEGIN
  SET NOCOUNT ON

  -- Change server name to always reflect real servername or servername\instancename
  IF (@server_name IS NULL) OR (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = '(LOCAL)')
    SELECT @server_name = CONVERT(sysname, SERVERPROPERTY('ServerName'))

  IF (@server_name IS NOT NULL)
    SELECT @server_name = UPPER(@server_name)

  -- Verify the server name
  IF (@server_name <> UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))) AND
     (NOT EXISTS (SELECT *
                  FROM msdb.dbo.systargetservers
                  WHERE (UPPER(server_name) = @server_name)))
  BEGIN
    RAISERROR(14262, 16, 1, '@server_name', @server_name)
    RETURN(1) -- Failure
  END

  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers    sjs,
                   msdb.dbo.systargetservers sts
              WHERE (sjs.server_id = sts.server_id)
                AND (UPPER(sts.server_name) = @server_name)))
  BEGIN
    -- Generate the SQL
    SELECT 'Execute this SQL to re-assign jobs to the target server' =
           'EXECUTE msdb.dbo.sp_add_jobserver @job_id = ''' + CONVERT(VARCHAR(36), sjs.job_id) +
           ''', @server_name = ''' +  ISNULL(@new_server_name, sts.server_name) + ''''
    FROM msdb.dbo.sysjobservers    sjs,
         msdb.dbo.systargetservers sts
    WHERE (sjs.server_id = sts.server_id)
      AND (UPPER(sts.server_name) = @server_name)
  END
  ELSE
    RAISERROR(14548, 10, 1, @server_name)

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_GENERATE_SERVER_DESCRIPTION                             */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_generate_server_description...'
go

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_generate_server_description')
              AND (type = 'P')))
  DROP PROCEDURE sp_generate_server_description
go
CREATE PROCEDURE sp_generate_server_description
  @description NVARCHAR(100) = NULL OUTPUT,
  @result_set  BIT = 0
AS
BEGIN
  SET NOCOUNT ON

  DECLARE @xp_results TABLE
  (
  id              INT           NOT NULL,
  name            NVARCHAR(30)  COLLATE database_default NOT NULL,
  internal_value  INT           NULL,
  character_value NVARCHAR(212) COLLATE database_default NULL
  )
  INSERT INTO @xp_results
  EXECUTE master.dbo.xp_msver

  UPDATE @xp_results
  SET character_value = FORMATMESSAGE(14205)
  WHERE (character_value IS NULL)

  SELECT @description = (SELECT character_value FROM @xp_results WHERE (id = 1)) + N' ' +
                        (SELECT character_value FROM @xp_results WHERE (id = 2)) + N' / Windows ' +
                        (SELECT character_value FROM @xp_results WHERE (id = 15)) + N' / ' +
                        (SELECT character_value FROM @xp_results WHERE (id = 16)) + N' ' +
                        (SELECT CASE character_value
                                  WHEN N'PROCESSOR_INTEL_386'     THEN N'386'
                                  WHEN N'PROCESSOR_INTEL_486'     THEN N'486'
                                  WHEN N'PROCESSOR_INTEL_PENTIUM' THEN N'Pentium'
                                  WHEN N'PROCESSOR_MIPS_R4000'    THEN N'MIPS'
                                  WHEN N'PROCESSOR_ALPHA_21064'   THEN N'Alpha'
                                  ELSE character_value
                                END
                         FROM @xp_results WHERE (id = 18)) + N' CPU(s) / ' +
                        (SELECT CONVERT(NVARCHAR, internal_value) FROM @xp_results WHERE (id = 19)) + N' MB RAM.'
  IF (@result_set = 1)
    SELECT @description
END
go

/**************************************************************/
/* SP_MSX_SET_ACCOUNT                                              */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_msx_set_account...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_msx_set_account')
              AND (type = 'P')))
  DROP PROCEDURE sp_msx_set_account
go
CREATE PROCEDURE sp_msx_set_account
  @credential_name sysname = NULL,
  @credential_id   INT = NULL
AS
BEGIN
  DECLARE @retval INT
  IF @credential_id IS NOT NULL OR @credential_name IS NOT NULL
  BEGIN
     EXECUTE @retval = sp_verify_credential_identifiers  '@credential_name',
                                                        '@credential_id',
                                                        @credential_name OUTPUT,
                                                        @credential_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

    --set credential_id to agent registry
    EXECUTE master.dbo.xp_instance_regwrite  'HKEY_LOCAL_MACHINE',
                                    'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                    'MSXCredentialID',
                                    'REG_DWORD',
                                    @credential_id
    --set connections to standard
    EXECUTE master.dbo.xp_instance_regwrite  'HKEY_LOCAL_MACHINE',
                                    'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                    'RegularMSXConnections',
                                    'REG_DWORD',
                                    1
  END
  ELSE
  BEGIN
    --just set connection to integrated
    EXECUTE master.dbo.xp_instance_regwrite  'HKEY_LOCAL_MACHINE',
                                    'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                    'RegularMSXConnections',
                                    'REG_DWORD',
                                    0
  END
END
go

/**************************************************************/
/* SP_MSX_GET_ACCOUNT                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_msx_get_account...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_msx_get_account')
              AND (type = 'P')))
  DROP PROCEDURE sp_msx_get_account
go
CREATE PROCEDURE sp_msx_get_account
AS
BEGIN
  DECLARE @msx_connection INT
  DECLARE @credential_id  INT

  SELECT  @msx_connection  = 0    --integrated connections
  SELECT  @credential_id   = NULL
  EXECUTE master.dbo.xp_instance_regread  N'HKEY_LOCAL_MACHINE',
                                          N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                          N'RegularMSXConnections',
                                          @msx_connection OUTPUT,
                                          N'no_output'
  IF @msx_connection = 1
  BEGIN
    EXECUTE master.dbo.xp_instance_regread  N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'MSXCredentialID',
                                            @credential_id OUTPUT,
                                            N'no_output'
    SELECT msx_connection = @msx_connection , msx_credential_id = @credential_id,
           msx_credential_name = sc.name , msx_login_name = sc.credential_identity
    FROM   master.sys.credentials sc
    WHERE  credential_id = @credential_id
  END
END
go

/**************************************************************/
/* SP_DELETE_OPERATOR                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_operator...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_operator')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_operator
go
CREATE PROCEDURE sp_delete_operator
  @name                 sysname,
  @reassign_to_operator sysname = NULL
AS
BEGIN
  DECLARE @id                         INT
  DECLARE @alert_fail_safe_operator   sysname
  DECLARE @job_id                     UNIQUEIDENTIFIER
  DECLARE @job_id_as_char             VARCHAR(36)
  DECLARE @notify_email_operator_id   INT
  DECLARE @notify_netsend_operator_id INT
  DECLARE @notify_page_operator_id    INT
  DECLARE @reassign_to_id             INT
  DECLARE @cmd                        NVARCHAR(1000)
  DECLARE @current_msx_server         sysname
  DECLARE @reassign_to_escaped        NVARCHAR(256)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name                 = LTRIM(RTRIM(@name))
  SELECT @reassign_to_operator = LTRIM(RTRIM(@reassign_to_operator))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@reassign_to_operator = N'') SELECT @reassign_to_operator = NULL

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check if this Operator exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysoperators
                  WHERE (name = @name)))
  BEGIN
    RAISERROR(14262, 16, 1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Check if this operator the FailSafe Operator
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'AlertFailSafeOperator',
                                         @alert_fail_safe_operator OUTPUT,
                                         N'no_output'

  -- If it is, we disallow the delete operation
  IF (LTRIM(RTRIM(@alert_fail_safe_operator)) = @name)
  BEGIN
    RAISERROR(14504, 16, 1, @name, @name)
    RETURN(1) -- Failure
  END

  -- Check if this operator is 'MSXOperator'
  IF (@name = N'MSXOperator')
  BEGIN
    DECLARE @server_type VARCHAR(3)

    -- Disallow the delete operation if we're an MSX or a TSX
    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'MSXServerName',
                                           @current_msx_server OUTPUT,
                                           N'no_output'
    IF (@current_msx_server IS NOT NULL)
      SELECT @server_type = 'TSX'

    IF ((SELECT COUNT(*)
         FROM msdb.dbo.systargetservers) > 0)
      SELECT @server_type = 'MSX'

    IF (@server_type IS NOT NULL)
    BEGIN
      RAISERROR(14223, 16, 1, 'MSXOperator', @server_type)
      RETURN(1) -- Failure
    END
  END

  -- Convert the Name to it's ID
  SELECT @id = id
  FROM msdb.dbo.sysoperators
  WHERE (name = @name)

  IF (@reassign_to_operator IS NOT NULL)
  BEGIN
    -- On a TSX or standalone server, disallow re-assigning to the MSXOperator
    IF (@reassign_to_operator = N'MSXOperator') AND
       (NOT EXISTS (SELECT *
                    FROM msdb.dbo.systargetservers))
    BEGIN
      RAISERROR(14251, -1, -1, @reassign_to_operator)
      RETURN(1) -- Failure
    END

    SELECT @reassign_to_id = id
    FROM msdb.dbo.sysoperators
    WHERE (name = @reassign_to_operator)

    IF (@reassign_to_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@reassign_to_operator', @reassign_to_operator)
      RETURN(1) -- Failure
    END
  END

  -- Double up any single quotes in @reassign_to_operator
  IF (@reassign_to_operator IS NOT NULL)
    SET @reassign_to_escaped  = REPLACE(@reassign_to_operator, N'''', N'''''')

  BEGIN TRANSACTION

    -- Reassign (or delete) any sysnotifications rows that reference this operator
    IF (@reassign_to_operator IS NOT NULL)
    BEGIN
      UPDATE msdb.dbo.sysnotifications
      SET operator_id = @reassign_to_id
      WHERE (operator_id = @id)
        AND (NOT EXISTS (SELECT *
                         FROM msdb.dbo.sysnotifications sn2
                         WHERE (sn2.alert_id = msdb.dbo.sysnotifications.alert_id)
                           AND (sn2.operator_id = @reassign_to_id)))
    END

    DELETE FROM msdb.dbo.sysnotifications
    WHERE (operator_id = @id)

    -- Update any jobs that reference this operator
    DECLARE jobs_referencing_this_operator CURSOR LOCAL
    FOR
    SELECT job_id,
           notify_email_operator_id,
           notify_netsend_operator_id,
           notify_page_operator_id
    FROM msdb.dbo.sysjobs
    WHERE (notify_email_operator_id = @id)
       OR (notify_netsend_operator_id = @id)
       OR (notify_page_operator_id = @id)

    OPEN jobs_referencing_this_operator
    FETCH NEXT FROM jobs_referencing_this_operator INTO @job_id,
                                                        @notify_email_operator_id,
                                                        @notify_netsend_operator_id,
                                                        @notify_page_operator_id
    WHILE (@@fetch_status = 0)
    BEGIN
      SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)
      SELECT @cmd = N'msdb.dbo.sp_update_job @job_id = ''' + @job_id_as_char + N''', '

      IF (@notify_email_operator_id = @id)
        IF (@reassign_to_operator IS NOT NULL)
          SELECT @cmd = @cmd + N'@notify_email_operator_name = N''' + @reassign_to_escaped  + N''', '
        ELSE
          SELECT @cmd = @cmd + N'@notify_email_operator_name = N'''', @notify_level_email = 0, '

      IF (@notify_netsend_operator_id = @id)
        IF (@reassign_to_operator IS NOT NULL)
          SELECT @cmd = @cmd + N'@notify_netsend_operator_name = N''' + @reassign_to_escaped  + N''', '
        ELSE
          SELECT @cmd = @cmd + N'@notify_netsend_operator_name = N'''', @notify_level_netsend = 0, '

      IF (@notify_page_operator_id = @id)
        IF (@reassign_to_operator IS NOT NULL)
          SELECT @cmd = @cmd + N'@notify_page_operator_name = N''' + @reassign_to_escaped  + N''', '
        ELSE
          SELECT @cmd = @cmd + N'@notify_page_operator_name = N'''', @notify_level_page = 0, '

      SELECT @cmd = SUBSTRING(@cmd, 1, (DATALENGTH(@cmd) / 2) - 2)
      EXECUTE (N'EXECUTE ' + @cmd)

      FETCH NEXT FROM jobs_referencing_this_operator INTO @job_id,
                                                          @notify_email_operator_id,
                                                          @notify_netsend_operator_id,
                                                          @notify_page_operator_id
    END
    DEALLOCATE jobs_referencing_this_operator

    -- Finally, do the actual DELETE
    DELETE FROM msdb.dbo.sysoperators
    WHERE (id = @id)

  COMMIT TRANSACTION

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_MSX_DEFECT                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_msx_defect...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_msx_defect')
              AND (type = 'P')))
  DROP PROCEDURE sp_msx_defect
go
CREATE PROCEDURE sp_msx_defect
  @forced_defection BIT = 0
AS
BEGIN
  DECLARE @current_msx_server sysname
  DECLARE @retval             INT
  DECLARE @jobs_deleted       INT
  DECLARE @polling_interval   INT
  DECLARE @nt_user            NVARCHAR(100)

  SET NOCOUNT ON

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  SELECT @retval = 0
  SELECT @jobs_deleted = 0

  -- Get the current MSX server name from the registry
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'MSXServerName',
                                         @current_msx_server OUTPUT,
                                         N'no_output'

  SELECT @current_msx_server = UPPER(LTRIM(RTRIM(@current_msx_server)))
  IF ((@current_msx_server IS NULL) OR (@current_msx_server = N''))
  BEGIN
    RAISERROR(14298, -1, -1)
    RETURN(1) -- Failure
  END

  SELECT @nt_user = ISNULL(NT_CLIENT(), ISNULL(SUSER_SNAME(), FORMATMESSAGE(14205)))

  EXECUTE @retval = master.dbo.xp_msx_enlist 1, @current_msx_server, @nt_user

  IF (@retval <> 0) AND (@forced_defection = 0)
    RETURN(1) -- Failure

  -- Clear the MSXServerName registry entry
  EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                          N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                          N'MSXServerName',
                                          N'REG_SZ',
                                          N''

  -- Delete the MSXPollingInterval registry entry
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'MSXPollInterval',
                                         @polling_interval OUTPUT,
                                         N'no_output'
  IF (@polling_interval IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regdeletevalue N'HKEY_LOCAL_MACHINE',
                                                  N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                                  N'MSXPollInterval'

  -- Remove the entry from sqlagent_info
  DELETE FROM msdb.dbo.sqlagent_info
  WHERE (attribute = N'DateEnlisted')

  -- Delete all the jobs that originated from the MSX
  -- NOTE: We can't use sp_delete_job here since sp_delete_job checks if the caller is
  --       SQLServerAgent (only SQLServerAgent can delete non-local jobs).
  EXECUTE msdb.dbo.sp_delete_all_msx_jobs @current_msx_server, @jobs_deleted OUTPUT
  RAISERROR(14227, 0, 1, @current_msx_server, @jobs_deleted)

  -- Now delete the old msx server record
  DELETE msdb.dbo.sysoriginatingservers
  WHERE (originating_server = @current_msx_server)
    AND (master_server = 1)

  -- If a forced defection was performed, attempt to notify the MSXOperator
  IF (@forced_defection = 1)
  BEGIN
    DECLARE @network_address    NVARCHAR(100)
    DECLARE @command            NVARCHAR(512)
    DECLARE @local_machine_name sysname
    DECLARE @res_warning        NVARCHAR(300)

    SELECT @network_address = netsend_address
    FROM msdb.dbo.sysoperators
    WHERE (name = N'MSXOperator')

    IF (@network_address IS NOT NULL)
    BEGIN
      EXECUTE @retval = master.dbo.xp_getnetname @local_machine_name OUTPUT
      IF (@retval <> 0)
        RETURN(1) -- Failure
      SELECT @res_warning = FORMATMESSAGE(14217)
      SELECT @command = N'NET SEND ' + @network_address + N' ' + @res_warning
      SELECT @command = STUFF(@command, PATINDEX(N'%[%%]s%', @command), 2, NT_CLIENT())
      SELECT @command = STUFF(@command, PATINDEX(N'%[%%]s%', @command), 2, @local_machine_name)
      EXECUTE master.dbo.xp_cmdshell @command, no_output
    END
  END

  -- Delete the 'MSXOperator' (must do this last)
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysoperators
              WHERE (name = N'MSXOperator')))
    EXECUTE msdb.dbo.sp_delete_operator @name = N'MSXOperator'

  RETURN(0) -- 0 means success
END
go

/**************************************************************/
/* SP_MSX_ENLIST                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_msx_enlist...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_msx_enlist')
              AND (type = 'P')))
  DROP PROCEDURE sp_msx_enlist
go
CREATE PROCEDURE sp_msx_enlist
  @msx_server_name sysname,
  @location        NVARCHAR(100) = NULL -- The procedure will supply a default
AS
BEGIN
  DECLARE @current_msx_server       sysname
  DECLARE @local_machine_name       sysname
  DECLARE @msx_originating_server   sysname
  DECLARE @retval                   INT
  DECLARE @time_zone_adjustment     INT
  DECLARE @local_time               NVARCHAR(100)
  DECLARE @nt_user                  NVARCHAR(100)
  DECLARE @poll_interval            INT

  SET NOCOUNT ON

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Only an NT server can be enlisted
  IF ((PLATFORM() & 0x1) <> 0x1) -- NT
  BEGIN
    RAISERROR(14540, -1, 1)
    RETURN(1) -- Failure
  END

  -- Only SBS, Standard, or Enterprise editions of SQL Server can be enlisted
  IF ((PLATFORM() & 0x100) = 0x100) -- Desktop package
  BEGIN
    RAISERROR(14539, -1, -1)
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @msx_server_name  = UPPER(LTRIM(RTRIM(@msx_server_name)))
  SELECT @location         = LTRIM(RTRIM(@location))
  SELECT @local_machine_name = UPPER(CONVERT(NVARCHAR(30), SERVERPROPERTY('ServerName')))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@location = N'') SELECT @location = NULL

  SELECT @retval = 0

  -- Get the values that we'll need for the [re]enlistment operation (except the local time
  -- which we get right before we call xp_msx_enlist to that it's as accurate as possible)
  SELECT @nt_user = ISNULL(NT_CLIENT(), ISNULL(SUSER_SNAME(), FORMATMESSAGE(14205)))
  EXECUTE master.dbo.xp_regread N'HKEY_LOCAL_MACHINE',
                                N'SYSTEM\CurrentControlSet\Control\TimeZoneInformation',
                                N'Bias',
                                @time_zone_adjustment OUTPUT,
                                N'no_output'
  IF ((PLATFORM() & 0x1) = 0x1) -- NT
    SELECT @time_zone_adjustment = -ISNULL(@time_zone_adjustment, 0)
  ELSE
    SELECT @time_zone_adjustment = -CONVERT(INT, CONVERT(BINARY(2), ISNULL(@time_zone_adjustment, 0)))

  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'MSXPollInterval',
                                         @poll_interval OUTPUT,
                                         N'no_output'
  SELECT @poll_interval = ISNULL(@poll_interval, 60) -- This should be the same as DEF_REG_MSX_POLL_INTERVAL
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'MSXServerName',
                                         @current_msx_server OUTPUT,
                                         N'no_output'
  SELECT @current_msx_server = LTRIM(RTRIM(@current_msx_server))

  -- Check if this machine is an MSX (and therefore cannot be enlisted into another MSX)
  IF (EXISTS (SELECT *
              FROM msdb.dbo.systargetservers))
  BEGIN
   --Get local server/instance name
    RAISERROR(14299, -1, -1, @local_machine_name)
    RETURN(1) -- Failure
  END

  -- Check if the MSX supplied is the same as the local machine (this is not allowed)
  IF (UPPER(@local_machine_name) = @msx_server_name)
  BEGIN
    RAISERROR(14297, -1, -1)
    RETURN(1) -- Failure
  END

  -- Check if MSDB has be re-installed since we enlisted
  IF (@current_msx_server IS NOT NULL) AND
     (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sqlagent_info
                  WHERE (attribute = 'DateEnlisted')))
  BEGIN
    -- User is tring to [re]enlist after a re-install, so we have to forcefully defect before
    -- we can fully enlist again
    EXECUTE msdb.dbo.sp_msx_defect @forced_defection = 1
    SELECT @current_msx_server = NULL
  END

  -- Check if we are already enlisted, in which case we re-enlist
  IF ((@current_msx_server IS NOT NULL) AND (@current_msx_server <> N''))
  BEGIN
    IF (UPPER(@current_msx_server) = @msx_server_name)
    BEGIN
      -- Update the [existing] enlistment
      SELECT @local_time = CONVERT(NVARCHAR, GETDATE(), 112) + N' ' + CONVERT(NVARCHAR, GETDATE(), 108)
      EXECUTE @retval = master.dbo.xp_msx_enlist 2, @msx_server_name, @nt_user, @location, @time_zone_adjustment, @local_time, @poll_interval
      RETURN(@retval) -- 0 means success
    END
    ELSE
    BEGIN
      RAISERROR(14296, -1, -1, @current_msx_server)
      RETURN(1) -- Failure
    END
  END

  -- If we get this far then we're dealing with a new enlistment...


  -- If no location is supplied, generate one (such as we can)
  IF (@location IS NULL)
    EXECUTE msdb.dbo.sp_generate_server_description @location OUTPUT

  SELECT @local_time = CONVERT(NVARCHAR, GETDATE(), 112) + ' ' + CONVERT(NVARCHAR, GETDATE(), 108)
  EXECUTE @retval = master.dbo.xp_msx_enlist 0, @msx_server_name, @nt_user, @location, @time_zone_adjustment, @local_time, @poll_interval

  IF (@retval = 0)
  BEGIN
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'MSXServerName',
                                            N'REG_SZ',
                                            @msx_server_name

    IF (@current_msx_server IS NOT NULL)
      RAISERROR(14228, 0, 1, @current_msx_server, @msx_server_name)
    ELSE
      RAISERROR(14229, 0, 1, @msx_server_name)

    -- Update the sysoriginatingservers table with the msx server name. May need to clean up if it already has an msx entry
    SELECT @msx_originating_server = NULL
    -- Get the msx server name
    SELECT @msx_originating_server = originating_server
    FROM msdb.dbo.sysoriginatingservers
    WHERE (master_server = 1)

    IF(@msx_originating_server IS NULL)
    BEGIN
        -- Good. No msx server found so just add the new one
        INSERT INTO msdb.dbo.sysoriginatingservers(originating_server, master_server) VALUES (@msx_server_name, 1)
    END
    ELSE
    BEGIN
        -- Found a previous entry. If it isn't the same server we need to clean up any existing msx jobs
        IF(@msx_originating_server != @msx_server_name)
        BEGIN
            INSERT INTO msdb.dbo.sysoriginatingservers(originating_server, master_server) VALUES (@msx_server_name, 1)
            -- Optimistically try and remove any msx jobs left over from the previous msx enlistment.
            EXECUTE msdb.dbo.sp_delete_all_msx_jobs @msx_originating_server
            -- And finally delete the old msx server record
            DELETE msdb.dbo.sysoriginatingservers
            WHERE (originating_server = @msx_originating_server)
              AND (master_server = 1)
        END
    END

    -- Add entry to sqlagent_info
    INSERT INTO msdb.dbo.sqlagent_info (attribute, value) VALUES ('DateEnlisted', CONVERT(VARCHAR(10), GETDATE(), 112))
  END

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_TARGETSERVER                                     */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_targetserver...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_targetserver')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_targetserver
go
CREATE PROCEDURE sp_delete_targetserver
  @server_name        sysname,
  @clear_downloadlist BIT = 1,
  @post_defection     BIT = 1
AS
BEGIN
  DECLARE @server_id INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @server_name = UPPER(LTRIM(RTRIM(@server_name)))

  -- Check server name
  SELECT @server_id = server_id
  FROM msdb.dbo.systargetservers
  WHERE (UPPER(server_name) = @server_name)

  IF (@server_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@server_name', @server_name)
    RETURN(1) -- Failure
  END

  BEGIN TRANSACTION

    IF (@clear_downloadlist = 1)
    BEGIN
      DELETE FROM msdb.dbo.sysdownloadlist
      WHERE (target_server = @server_name)
    END

    IF (@post_defection = 1)
    BEGIN
      -- Post a defect instruction to the server
      -- NOTE: We must do this BEFORE deleting the systargetservers row
      EXECUTE msdb.dbo.sp_post_msx_operation 'DEFECT', 'SERVER', 0x00, @server_name
    END

    DELETE FROM msdb.dbo.systargetservers
    WHERE (server_id = @server_id)

    DELETE FROM msdb.dbo.systargetservergroupmembers
    WHERE (server_id = @server_id)

    DELETE FROM msdb.dbo.sysjobservers
    WHERE (server_id = @server_id)

  COMMIT TRANSACTION

  RETURN(@@error) -- 0 means success
END
go


/**************************************************************/
/* SP_ENLIST_TSX                                              */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_enlist_tsx'
go

IF EXISTS (SELECT name FROM sysobjects
         WHERE name = 'sp_enlist_tsx' AND type = 'P')
   DROP PROCEDURE sp_enlist_tsx
GO

create proc sp_enlist_tsx
   @Action int,            -- 0 - enlist; 1 - defect; 2 - update
   @ServerName  sysname,      -- tsx server name
   @Location  nvarchar(200),  -- tsx server location
   @TimeZoneAdjustment int,   -- tsx server time zone adjustment
   @LocalTime datetime,    -- tsx server local time
   @NTUserName nvarchar(100), -- name of the user performing the enlistment
   @PollInterval int,          -- polling interval
    @TSX_Version int = 0        -- VersionMajor: ((@TSX_Version / 0x1000000) & 0xff)
                                -- VersionMinor: ((@TSX_Version / 0x10000) & 0xff)
                                -- Build no:      (@TSX_Version & 0xFFFF)
as
begin
   SET NOCOUNT ON

   /* check permissions */
   IF (ISNULL(IS_MEMBER(N'TargetServersRole'), 0) = 0) AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0)
   begin
   raiserror(15003,-1,-1, N'TargetServersRole')
   return 1
   end

   --9.0 and above servers set this version param
   if(@TSX_Version is null)
      set @TSX_Version = 0

   --Only check version during enlistment
   if(@Action = 0 AND ((@TSX_Version / 0x1000000) & 0xff) < 9)
   begin
      DECLARE @majorVer int, @minorVer int, @buildNo int
      SELECT @majorVer = ((@@microsoftversion / 0x1000000) & 0xff),
             @minorVer = ((@@microsoftversion / 0x10000) & 0xff),
             @buildNo = (@@microsoftversion & 0xfff)

      raiserror(14306, -1, -1, @majorVer, @minorVer, @buildNo )
      return 12
   end

   /* check input parameters */
   if @ServerName is null
   begin
   raiserror(14043, -1, -1, '@ServerName')
   return 2
   end

   select @ServerName = LTRIM(@ServerName)
   select @ServerName = RTRIM(@ServerName)
   if @ServerName = ''
   begin
   raiserror(21263, -1, -1, '@ServerName')
   return 3
   end

   select @ServerName = UPPER(@ServerName)

   if @Action <> 1 And @Action <> 2
   begin
   /* default action is to enlist */
   select @Action = 0
   end

  if @Action = 0 /* enlisting */
  begin
   /* check input parameters */
   if @NTUserName is null
   begin
      raiserror(14043, -1, -1, '@NTUserName')
      return 4
   end

   select @NTUserName = LTRIM(@NTUserName)
   select @NTUserName = RTRIM(@NTUserName)
   if @NTUserName = ''
   begin
     raiserror(21263, -1, -1, '@NTUserName')
     return 5
   end

   /* check if local server is already configured as TSX machine */
   declare @msx_server_name sysname
   select @msx_server_name = N''

   execute master.dbo.xp_instance_regread
      N'HKEY_LOCAL_MACHINE',
      N'Software\Microsoft\MSSQLServer\SQLServerAgent',
      N'MSXServerName',
      @msx_server_name OUTPUT

   select @msx_server_name = LTRIM(@msx_server_name)
   select @msx_server_name = RTRIM(@msx_server_name)
   if @msx_server_name <> N''
   begin
      raiserror(14360, -1, -1, @@SERVERNAME)
      return 6
   end

   /*
   * check that local server is not running a desktop SKU,
   * i.e. Win9x, Office, or MSDE
   */
   if( PLATFORM() & 0x100 = 0x100 )
   begin
      raiserror(14362, -1, -1)
      return 8
   end

   /* check if we have any MSXOperators defined */
   if not exists (SELECT * FROM msdb.dbo.sysoperators WHERE name = N'MSXOperator')
   begin
      raiserror(14363, -1, -1)
      return 9
   end

   /* all checks have passed, insert new row into systargetservers table */
   INSERT INTO msdb.dbo.systargetservers
   (
   server_name,
   location,
   time_zone_adjustment,
   enlist_date,
   last_poll_date,
   status,
   local_time_at_last_poll,
   enlisted_by_nt_user,
   poll_interval
   )
   VALUES
   (
   @ServerName,
   @Location,
   @TimeZoneAdjustment,
   GETDATE(),
   GETDATE(),
   1,
   @LocalTime,
   @NTUserName,
   @PollInterval
   )

   /* delete left behind rows from sysdownloadlist */
   DELETE FROM msdb.dbo.sysdownloadlist
   WHERE target_server = @ServerName
   end

   if @Action = 2 /* updating existing enlistment */
   begin
   /* check if we have any MSXOperators defined */
   if not exists (SELECT * FROM msdb.dbo.sysoperators WHERE name = N'MSXOperator')
   begin
      raiserror(14363, -1, -1)
      return 10
   end

   /* check if TSX machine is already enlisted */
   If not exists (SELECT * FROM msdb.dbo.systargetservers WHERE UPPER(server_name) = @ServerName)
   begin
      raiserror(14364, -1, -1)
      return 11
   end

   if @Location is null /* don't update the location if it is not supplied */
   begin
      UPDATE msdb.dbo.systargetservers SET
      time_zone_adjustment = @TimeZoneAdjustment,
      poll_interval = @PollInterval
      WHERE (UPPER(server_name) = @ServerName)
   end
   else
   begin
      UPDATE msdb.dbo.systargetservers SET
      location = @Location,
      time_zone_adjustment = @TimeZoneAdjustment,
      poll_interval = @PollInterval
      WHERE (UPPER(server_name) = @ServerName)
   end
   end

  if @Action = 1 /* defecting */
  begin
   if (exists (SELECT * FROM msdb.dbo.systargetservers WHERE UPPER(server_name) = @ServerName))
   begin
      execute msdb.dbo.sp_delete_targetserver
         @server_name = @ServerName,
         @post_defection = 0
   end
   else
   begin
      DELETE FROM msdb.dbo.sysdownloadlist
      WHERE (target_server = @ServerName)
   end
  end

  if @Action = 0 Or @Action = 2 /* enlisting or updating existing enlistment */
  begin
   /* select resultset to return to the caller */
   SELECT
   id,
   name,
   enabled,
   email_address,
   pager_address,
   netsend_address,
   weekday_pager_start_time,
   weekday_pager_end_time,
   saturday_pager_start_time,
   saturday_pager_end_time,
   sunday_pager_start_time,
   sunday_pager_end_time,
   pager_days
   FROM
   msdb.dbo.sysoperators WHERE (name = N'MSXOperator')
   end
end
go

/**************************************************************/
/* SP_GET_SQLAGENT_PROPERTIES                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_get_sqlagent_properties...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_get_sqlagent_properties')
              AND (type = 'P')))
  DROP PROCEDURE sp_get_sqlagent_properties
go
CREATE PROCEDURE sp_get_sqlagent_properties
AS
BEGIN
  DECLARE @auto_start                  INT
  DECLARE @startup_account             NVARCHAR(100)
  DECLARE @msx_server_name             SYSNAME

  -- Non-SQLDMO exposed properties
  DECLARE @sqlserver_restart           INT
  DECLARE @jobhistory_max_rows         INT
  DECLARE @jobhistory_max_rows_per_job INT
  DECLARE @errorlog_file               NVARCHAR(255)
  DECLARE @errorlogging_level          INT
  DECLARE @error_recipient             NVARCHAR(30)
  DECLARE @monitor_autostart           INT
  DECLARE @local_host_server           SYSNAME
  DECLARE @job_shutdown_timeout        INT
  DECLARE @cmdexec_account             VARBINARY(64)
  DECLARE @regular_connections         INT
  DECLARE @host_login_name             SYSNAME
  DECLARE @host_login_password         VARBINARY(512)
  DECLARE @login_timeout               INT
  DECLARE @idle_cpu_percent            INT
  DECLARE @idle_cpu_duration           INT
  DECLARE @oem_errorlog                INT
  DECLARE @email_profile               NVARCHAR(64)
  DECLARE @email_save_in_sent_folder   INT
  DECLARE @cpu_poller_enabled          INT
  DECLARE @alert_replace_runtime_tokens INT

  SET NOCOUNT ON

  -- NOTE: We return all SQLServerAgent properties at one go for performance reasons

  -- Read the values from the registry
  -- If this is Azure SQL Database - Managed Instance assign default values for @auto_start and @startup_account
  IF (SERVERPROPERTY('EngineEdition') = 8)
  BEGIN
    SELECT @auto_start = 1
    SELECT @startup_account = NULL
  END
  ELSE IF ((PLATFORM() & 0x1) = 0x1) -- NT
  BEGIN
    DECLARE @key NVARCHAR(200)

    SELECT @key = N'SYSTEM\CurrentControlSet\Services\'
    IF (SERVERPROPERTY('INSTANCENAME') IS NOT NULL)
      SELECT @key = @key + N'SQLAgent$' + CONVERT (sysname, SERVERPROPERTY('INSTANCENAME'))
    ELSE
      SELECT @key = @key + N'SQLServerAgent'

    EXECUTE master.dbo.xp_regread N'HKEY_LOCAL_MACHINE',
                                  @key,
                                  N'Start',
                                  @auto_start OUTPUT,
                                  N'no_output'
    EXECUTE master.dbo.xp_regread N'HKEY_LOCAL_MACHINE',
                                  @key,
                                  N'ObjectName',
                                  @startup_account OUTPUT,
                                  N'no_output'
  END
  ELSE
  BEGIN
    SELECT @auto_start = 3 -- Manual start
    SELECT @startup_account = NULL
  END
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'MSXServerName',
                                         @msx_server_name OUTPUT,
                                         N'no_output'

  -- Non-SQLDMO exposed properties
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'RestartSQLServer',
                                         @sqlserver_restart OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'JobHistoryMaxRows',
                                         @jobhistory_max_rows OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'JobHistoryMaxRowsPerJob',
                                         @jobhistory_max_rows_per_job OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
										 N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
										 N'ErrorLogFile',
										 @errorlog_file OUTPUT,
										 N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
										 N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
										 N'ErrorLoggingLevel',
										 @errorlogging_level OUTPUT,
										 N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'ErrorMonitor',
                                         @error_recipient OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'MonitorAutoStart',
                                         @monitor_autostart OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'ServerHost',
                                         @local_host_server OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'JobShutdownTimeout',
                                         @job_shutdown_timeout OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'CmdExecAccount',
                                         @cmdexec_account OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'LoginTimeout',
                                         @login_timeout OUTPUT,
                                         N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
										 N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
										 N'IdleCPUPercent',
										 @idle_cpu_percent OUTPUT,
										 N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
										 N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
										 N'IdleCPUDuration',
										 @idle_cpu_duration OUTPUT,
										 N'no_output'
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
										 N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
										 N'OemErrorLog',
										 @oem_errorlog OUTPUT,
										 N'no_output'

  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'AlertReplaceRuntimeTokens',
                                         @alert_replace_runtime_tokens OUTPUT,
                                         N'no_output'

  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'CoreEngineMask',
                                         @cpu_poller_enabled OUTPUT,
                                         N'no_output'
  IF (@cpu_poller_enabled IS NOT NULL)
    SELECT @cpu_poller_enabled = CASE WHEN (@cpu_poller_enabled & 32) = 32 THEN 0 ELSE 1 END

  -- Return the values to the client
  SELECT auto_start = CASE @auto_start
                        WHEN 2 THEN 1 -- 2 means auto-start
                        WHEN 3 THEN 0 -- 3 means don't auto-start
                        ELSE 0        -- Safety net
                      END,
         msx_server_name = @msx_server_name,
         sqlagent_type = (SELECT CASE
                                    WHEN (COUNT(*) = 0) AND (ISNULL(DATALENGTH(@msx_server_name), 0) = 0) THEN 1 -- Standalone
                                    WHEN (COUNT(*) = 0) AND (ISNULL(DATALENGTH(@msx_server_name), 0) > 0) THEN 2 -- TSX
                                    WHEN (COUNT(*) > 0) AND (ISNULL(DATALENGTH(@msx_server_name), 0) = 0) THEN 3 -- MSX
                                    WHEN (COUNT(*) > 0) AND (ISNULL(DATALENGTH(@msx_server_name), 0) > 0) THEN 0 -- Multi-Level MSX (currently invalid)
                                    ELSE 0 -- Invalid
                                  END
                           FROM msdb.dbo.systargetservers),
         startup_account = @startup_account,

         -- Non-SQLDMO exposed properties
         sqlserver_restart = ISNULL(@sqlserver_restart, 1),
         jobhistory_max_rows = @jobhistory_max_rows,
         jobhistory_max_rows_per_job = @jobhistory_max_rows_per_job,
         errorlog_file = @errorlog_file,
         errorlogging_level = ISNULL(@errorlogging_level, 7),
         error_recipient = @error_recipient,
         monitor_autostart = ISNULL(@monitor_autostart, 0),
         local_host_server = @local_host_server,
         job_shutdown_timeout = ISNULL(@job_shutdown_timeout, 15),
         cmdexec_account = @cmdexec_account,
         regular_connections = 0,         -- Obsolete
         host_login_name = NULL,          -- Obsolete
         host_login_password = NULL,      -- Obsolete
         login_timeout = ISNULL(@login_timeout, 30),
         idle_cpu_percent = ISNULL(@idle_cpu_percent, 10),
         idle_cpu_duration = ISNULL(@idle_cpu_duration, 600),
         oem_errorlog = ISNULL(@oem_errorlog, 0),
         sysadmin_only = NULL,            -- Obsolete
         email_profile = NULL,            -- Obsolete
         email_save_in_sent_folder = 0,   -- Obsolete
         cpu_poller_enabled = ISNULL(@cpu_poller_enabled, 0),
         alert_replace_runtime_tokens = ISNULL(@alert_replace_runtime_tokens, 0)
END
go


/**************************************************************/
/* SP_SET_SQLAGENT_PROPERTIES                                 */
/**************************************************************/
IF EXISTS (SELECT * FROM msdb.dbo.sysobjects WHERE name = N'sp_set_sqlagent_properties' AND type = 'P')
BEGIN
  DROP PROCEDURE dbo.sp_set_sqlagent_properties
END
go

PRINT ''
PRINT 'Create procedure sp_set_sqlagent_properties...'
go

CREATE PROCEDURE dbo.sp_set_sqlagent_properties
  @auto_start                  INT           = NULL, -- 1 or 0
  -- Non-SQLDMO exposed properties
  @sqlserver_restart           INT           = NULL, -- 1 or 0
  @jobhistory_max_rows         INT           = NULL, -- No maximum = -1, otherwise must be > 1
  @jobhistory_max_rows_per_job INT           = NULL, -- 1 to @jobhistory_max_rows
  @errorlog_file               NVARCHAR(255) = NULL, -- Full drive\path\name of errorlog file
  @errorlogging_level          INT           = NULL, -- 1 = error, 2 = warning, 4 = information
  @error_recipient             NVARCHAR(30)  = NULL, -- Network address of error popup recipient
  @monitor_autostart           INT           = NULL, -- 1 or 0
  @local_host_server           SYSNAME      = NULL, -- Alias of local host server
  @job_shutdown_timeout        INT           = NULL, -- 5 to 600 seconds
  @cmdexec_account             VARBINARY(64) = NULL, -- CmdExec account information
  @regular_connections         INT           = NULL, -- obsolete
  @host_login_name             SYSNAME       = NULL, -- obsolete
  @host_login_password         VARBINARY(512) = NULL, -- obsolete
  @login_timeout               INT           = NULL, -- 5 to 45 (seconds)
  @idle_cpu_percent            INT           = NULL, -- 1 to 100
  @idle_cpu_duration           INT           = NULL, -- 20 to 86400 seconds
  @oem_errorlog                INT           = NULL, -- 1 or 0
  @sysadmin_only               INT           = NULL, -- not applicable to Yukon server, for backwards compatibility only
  @email_profile               NVARCHAR(64)  = NULL, -- obsolete - SQLMail profile, Rely on DBMail for notifications
  @email_save_in_sent_folder   INT           = NULL, -- obsolete
  @cpu_poller_enabled          INT           = NULL, -- 1 or 0
  @alert_replace_runtime_tokens INT          = NULL, -- 1 or 0
  @use_databasemail            INT           = NULL,  -- 1 or 0
  @databasemail_profile        SYSNAME       = NULL

AS
BEGIN
  -- NOTE: We set all SQLServerAgent properties at one go for performance reasons.
  -- NOTE: You cannot set the value of the properties msx_server_name, is_msx or
  --       startup_account - they are all read only.

  DECLARE @res_valid_range           NVARCHAR(100)
  DECLARE @existing_core_engine_mask INT

  SET NOCOUNT ON

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported stored procedure
  IF (SERVERPROPERTY('EngineEdition') = 8)
  BEGIN
    RAISERROR(41905, -1, 1, 'sp_set_sqlagent_properties');
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @errorlog_file     = LTRIM(RTRIM(@errorlog_file))
  SELECT @error_recipient   = LTRIM(RTRIM(@error_recipient))
  SELECT @local_host_server = LTRIM(RTRIM(@local_host_server))

  -- Make sure values (if supplied) are good
  IF (@auto_start IS NOT NULL)
  BEGIN
    -- NOTE: When setting the the services start value, 2 == auto-start, 3 == Don't auto-start
    SELECT @auto_start = CASE @auto_start
                           WHEN 0 THEN 3
                           WHEN 1 THEN 2
                           ELSE 3 -- Assume non auto-start if passed a junk value
                          END
  END

  -- Non-SQLDMO exposed properties
  IF ((@sqlserver_restart IS NOT NULL) AND (@sqlserver_restart <> 0))
    SELECT @sqlserver_restart = 1

  IF (@jobhistory_max_rows IS NOT NULL)
  BEGIN
    SELECT @res_valid_range = FORMATMESSAGE(14207)
    IF ((@jobhistory_max_rows < -1) OR (@jobhistory_max_rows = 0))
    BEGIN
      RAISERROR(14266, -1, -1, '@jobhistory_max_rows', @res_valid_range)
      RETURN(1) -- Failure
    END
  END
  ELSE
  BEGIN
    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'JobHistoryMaxRows',
                                           @jobhistory_max_rows OUTPUT,
                                           N'no_output'
    SELECT @jobhistory_max_rows = ISNULL(@jobhistory_max_rows, -1)
  END

  IF (@jobhistory_max_rows_per_job IS NOT NULL)
  BEGIN
    IF (@jobhistory_max_rows = -1)
      SELECT @jobhistory_max_rows_per_job = 0
    ELSE
    BEGIN
      IF ((@jobhistory_max_rows_per_job < 1) OR (@jobhistory_max_rows_per_job > @jobhistory_max_rows))
      BEGIN
        SELECT @res_valid_range = N'1..' + CONVERT(NVARCHAR, @jobhistory_max_rows)
        RAISERROR(14266, -1, -1, '@jobhistory_max_rows', @res_valid_range)
        RETURN(1) -- Failure
      END
    END
  END

  IF (@errorlogging_level IS NOT NULL) AND ((@errorlogging_level < 1) OR (@errorlogging_level > 7))
  BEGIN
    RAISERROR(14266, -1, -1, '@errorlogging_level', '1..7')
    RETURN(1) -- Failure
  END

  IF (@monitor_autostart IS NOT NULL) AND ((@monitor_autostart < 0) OR (@monitor_autostart > 1))
  BEGIN
    RAISERROR(14266, -1, -1, '@monitor_autostart', '0, 1')
    RETURN(1) -- Failure
  END

  IF (@job_shutdown_timeout IS NOT NULL) AND ((@job_shutdown_timeout < 5) OR (@job_shutdown_timeout > 600))
  BEGIN
    RAISERROR(14266, -1, -1, '@job_shutdown_timeout', '5..600')
    RETURN(1) -- Failure
  END

  IF (@login_timeout IS NOT NULL) AND ((@login_timeout < 5) OR (@login_timeout > 45))
  BEGIN
    RAISERROR(14266, -1, -1, '@login_timeout', '5..45')
    RETURN(1) -- Failure
  END

  IF ((@idle_cpu_percent IS NOT NULL) AND ((@idle_cpu_percent < 1) OR (@idle_cpu_percent > 100)))
  BEGIN
    RAISERROR(14266, -1, -1, '@idle_cpu_percent', '10..100')
    RETURN(1) -- Failure
  END

  IF ((@idle_cpu_duration IS NOT NULL) AND ((@idle_cpu_duration < 20) OR (@idle_cpu_duration > 86400)))
  BEGIN
    RAISERROR(14266, -1, -1, '@idle_cpu_duration', '20..86400')
    RETURN(1) -- Failure
  END

  IF (@oem_errorlog IS NOT NULL) AND ((@oem_errorlog < 0) OR (@oem_errorlog > 1))
  BEGIN
    RAISERROR(14266, -1, -1, '@oem_errorlog', '0, 1')
    RETURN(1) -- Failure
  END

  IF (@sysadmin_only IS NOT NULL)
  BEGIN
    RAISERROR(14378, -1, -1)
    RETURN(1) -- Failure
  END

  IF (@cpu_poller_enabled IS NOT NULL) AND ((@cpu_poller_enabled < 0) OR (@cpu_poller_enabled > 1))
  BEGIN
    RAISERROR(14266, -1, -1, 'cpu_poller_enabled', '0, 1')
    RETURN(1) -- Failure
  END

  IF (@alert_replace_runtime_tokens IS NOT NULL) AND ((@alert_replace_runtime_tokens < 0) OR (@alert_replace_runtime_tokens > 1))
  BEGIN
    RAISERROR(14266, -1, -1, 'alert_replace_runtime_tokens', '0, 1')
    RETURN(1) -- Failure
  END

  -- Write out the values
  IF (@auto_start IS NOT NULL)
  BEGIN
    IF ((PLATFORM() & 0x1) = 0x1) -- NT
    BEGIN
      DECLARE @key NVARCHAR(200)

      SELECT @key = N'SYSTEM\CurrentControlSet\Services\'
      IF (SERVERPROPERTY('INSTANCENAME') IS NOT NULL)
        SELECT @key = @key + N'SQLAgent$' + CONVERT (sysname, SERVERPROPERTY('INSTANCENAME'))
      ELSE
        SELECT @key = @key + N'SQLServerAgent'

      EXECUTE master.dbo.xp_regwrite N'HKEY_LOCAL_MACHINE',
                                     @key,
                                     N'Start',
                                     N'REG_DWORD',
                                     @auto_start
    END
    ELSE
      RAISERROR(14546, 16, 1, '@auto_start')
  END

  -- Non-SQLDMO exposed properties
  IF (@sqlserver_restart IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'RestartSQLServer',
                                            N'REG_DWORD',
                                            @sqlserver_restart
  IF (@jobhistory_max_rows IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'JobHistoryMaxRows',
                                            N'REG_DWORD',
                                            @jobhistory_max_rows
  IF (@jobhistory_max_rows_per_job IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'JobHistoryMaxRowsPerJob',
                                            N'REG_DWORD',
                                            @jobhistory_max_rows_per_job
  IF (@errorlog_file IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'ErrorLogFile',
                                            N'REG_SZ',
                                            @errorlog_file
  IF (@errorlogging_level IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'ErrorLoggingLevel',
                                            N'REG_DWORD',
                                            @errorlogging_level
  IF (@error_recipient IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'ErrorMonitor',
                                            N'REG_SZ',
                                            @error_recipient
  IF (@monitor_autostart IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'MonitorAutoStart',
                                            N'REG_DWORD',
                                            @monitor_autostart
  IF (@local_host_server IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'ServerHost',
                                            N'REG_SZ',
                                            @local_host_server
  IF (@job_shutdown_timeout IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'JobShutdownTimeout',
                                            N'REG_DWORD',
                                            @job_shutdown_timeout
  IF (@cmdexec_account IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'CmdExecAccount',
                                            N'REG_BINARY',
                                            @cmdexec_account

  IF (@login_timeout IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'LoginTimeout',
                                            N'REG_DWORD',
                                            @login_timeout
  IF (@idle_cpu_percent IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'IdleCPUPercent',
                                            N'REG_DWORD',
                                            @idle_cpu_percent
  IF (@idle_cpu_duration IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'IdleCPUDuration',
                                            N'REG_DWORD',
                                            @idle_cpu_duration
  IF (@oem_errorlog IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'OemErrorLog',
                                            N'REG_DWORD',
                                            @oem_errorlog

  IF (@alert_replace_runtime_tokens IS NOT NULL)
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'AlertReplaceRuntimeTokens',
                                            N'REG_DWORD',
                                            @alert_replace_runtime_tokens
  IF (@cpu_poller_enabled IS NOT NULL)
  BEGIN
    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'CoreEngineMask',
                                           @existing_core_engine_mask OUTPUT,
                                           N'no_output'



    IF ((@existing_core_engine_mask IS NOT NULL) OR (@cpu_poller_enabled = 1))
    BEGIN
      IF (@cpu_poller_enabled = 1)
        SELECT @cpu_poller_enabled = (ISNULL(@existing_core_engine_mask, 0) & ~32)
      ELSE
        SELECT @cpu_poller_enabled = (ISNULL(@existing_core_engine_mask, 0) | 32)

      IF ((@existing_core_engine_mask IS NOT NULL) AND (@cpu_poller_enabled = 32))
        EXECUTE master.dbo.xp_instance_regdeletevalue N'HKEY_LOCAL_MACHINE',
                                                      N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                                      N'CoreEngineMask'
      ELSE
        EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                                N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                                N'CoreEngineMask',
                                                N'REG_DWORD',
                                                @cpu_poller_enabled
    END
  END

  DECLARE @notify_sqlagent_dbmail_settings_update BIT
  SET @notify_sqlagent_dbmail_settings_update = 0
  IF(@use_databasemail IS NOT NULL)
  BEGIN

     EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'UseDatabaseMail',
                                            N'REG_DWORD',
                                            @use_databasemail

      SET @notify_sqlagent_dbmail_settings_update = 1
  END

  IF(@databasemail_profile IS NOT NULL)
  BEGIN
     EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'DatabaseMailProfile',
                                            N'REG_SZ',
                                            @databasemail_profile

      SET @notify_sqlagent_dbmail_settings_update = 1
  END

  IF(@notify_sqlagent_dbmail_settings_update = 1 )
  BEGIN
        -- Notify SQL Agent that Databasemail settings for SQL Agent was changed. force a reload
        EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'M'
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_ADD_TARGETSERVERGROUP                                   */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_targetservergroup...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_targetservergroup')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_targetservergroup
go
CREATE PROCEDURE sp_add_targetservergroup
  @name sysname
AS
BEGIN
  SET NOCOUNT ON

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @name = LTRIM(RTRIM(@name))

  -- Check if the group already exists
  IF (EXISTS (SELECT *
              FROM msdb.dbo.systargetservergroups
              WHERE name = @name))
  BEGIN
    RAISERROR(14261, -1, -1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Disallow names with commas in them (since sp_apply_job_to_targets parses a comma-separated list of group names)
  IF (@name LIKE N'%,%')
  BEGIN
    RAISERROR(14289, -1, -1, '@name', ',')
    RETURN(1) -- Failure
  END

  INSERT INTO msdb.dbo.systargetservergroups (name)
  VALUES (@name)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_UPDATE_TARGETSERVERGROUP                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_targetservergroup...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_targetservergroup')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_targetservergroup
go
CREATE PROCEDURE sp_update_targetservergroup
  @name     sysname,
  @new_name sysname
AS
BEGIN
  SET NOCOUNT ON

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @name     = LTRIM(RTRIM(@name))
  SELECT @new_name = LTRIM(RTRIM(@new_name))

  -- Check if the group exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.systargetservergroups
                  WHERE (name = @name)))
  BEGIN
    RAISERROR(14262, -1, -1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Check if a group with the new name already exists
  IF (EXISTS (SELECT *
              FROM msdb.dbo.systargetservergroups
              WHERE (name = @new_name)))
  BEGIN
    RAISERROR(14261, -1, -1, '@new_name', @new_name)
    RETURN(1) -- Failure
  END

  -- Disallow names with commas in them (since sp_apply_job_to_targets parses a comma-separated list of group names)
  IF (@new_name LIKE N'%,%')
  BEGIN
    RAISERROR(14289, -1, -1, '@new_name', ',')
    RETURN(1) -- Failure
  END

  -- Update the group's name
  UPDATE msdb.dbo.systargetservergroups
  SET name = @new_name
  WHERE (name = @name)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_TARGETSERVERGROUP                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_targetservergroup...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_targetservergroup')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_targetservergroup
go
CREATE PROCEDURE sp_delete_targetservergroup
  @name sysname
AS
BEGIN
  DECLARE @servergroup_id INT

  SET NOCOUNT ON

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @name = LTRIM(RTRIM(@name))

  -- Check if the group exists
  SELECT @servergroup_id = servergroup_id
  FROM msdb.dbo.systargetservergroups
  WHERE (name = @name)

  IF (@servergroup_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Remove the group members
  DELETE FROM msdb.dbo.systargetservergroupmembers
  WHERE (servergroup_id = @servergroup_id)

  -- Remove the group
  DELETE FROM msdb.dbo.systargetservergroups
  WHERE (name = @name)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_HELP_TARGETSERVERGROUP                                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_targetservergroup...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_targetservergroup')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_targetservergroup
go
CREATE PROCEDURE sp_help_targetservergroup
  @name sysname = NULL
AS
BEGIN
  DECLARE @servergroup_id INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name = LTRIM(RTRIM(@name))

  IF (@name IS NULL)
  BEGIN
    -- Show all groups
    SELECT servergroup_id, name
    FROM msdb.dbo.systargetservergroups
    RETURN(@@error) -- 0 means success
  END
  ELSE
  BEGIN
    -- Check if the group exists
    SELECT @servergroup_id = servergroup_id
    FROM msdb.dbo.systargetservergroups
    WHERE (name = @name)

    IF (@servergroup_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@name', @name)
      RETURN(1) -- Failure
    END

    -- Return the members of the group
    SELECT sts.server_id,
           sts.server_name
    FROM msdb.dbo.systargetservers sts,
         msdb.dbo.systargetservergroupmembers stsgm
    WHERE (stsgm.servergroup_id = @servergroup_id)
      AND (stsgm.server_id = sts.server_id)

    RETURN(@@error) -- 0 means success
  END
END
go

/**************************************************************/
/* SP_ADD_TARGETSVRGRP_MEMBER                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_targetsvgrp_member...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_targetsvrgrp_member')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_targetsvrgrp_member
go
CREATE PROCEDURE sp_add_targetsvrgrp_member
  @group_name  sysname,
  @server_name sysname
AS
BEGIN
  DECLARE @servergroup_id INT
  DECLARE @server_id      INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @group_name = LTRIM(RTRIM(@group_name))
  SELECT @server_name = UPPER(LTRIM(RTRIM(@server_name)))

  -- Check if the group exists
  SELECT @servergroup_id = servergroup_id
  FROM msdb.dbo.systargetservergroups
  WHERE (name = @group_name)

  IF (@servergroup_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@group_name', @group_name)
    RETURN(1) -- Failure
  END

  -- Check if the server exists
  SELECT @server_id = server_id
  FROM msdb.dbo.systargetservers
  WHERE (UPPER(server_name) = @server_name)

  IF (@server_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@server_name', @server_name)
    RETURN(1) -- Failure
  END

  -- Check if the server is already in this group
  IF (EXISTS (SELECT *
              FROM msdb.dbo.systargetservergroupmembers
              WHERE (servergroup_id = @servergroup_id)
                AND (server_id = @server_id)))
  BEGIN
    RAISERROR(14263, -1, -1, @server_name, @group_name)
    RETURN(1) -- Failure
  END

  -- Add the row to systargetservergroupmembers
  INSERT INTO msdb.dbo.systargetservergroupmembers
  VALUES (@servergroup_id, @server_id)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_TARGETSVRGRP_MEMBER                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_targetsvrgrp_member...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_targetsvrgrp_member')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_targetsvrgrp_member
go
CREATE PROCEDURE sp_delete_targetsvrgrp_member
  @group_name  sysname,
  @server_name sysname
AS
BEGIN
  DECLARE @servergroup_id INT
  DECLARE @server_id      INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @group_name = LTRIM(RTRIM(@group_name))
  SELECT @server_name = UPPER(LTRIM(RTRIM(@server_name)))

  -- Check if the group exists
  SELECT @servergroup_id = servergroup_id
  FROM msdb.dbo.systargetservergroups
  WHERE (name = @group_name)

  IF (@servergroup_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@group_name', @group_name)
    RETURN(1) -- Failure
  END

  -- Check if the server exists
  SELECT @server_id = server_id
  FROM msdb.dbo.systargetservers
  WHERE (UPPER(server_name) = @server_name)

  IF (@server_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@server_name', @server_name)
    RETURN(1) -- Failure
  END

  -- Check if the server is in the group
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.systargetservergroupmembers
                  WHERE (servergroup_id = @servergroup_id)
                    AND (server_id = @server_id)))
  BEGIN
    RAISERROR(14264, -1, -1, @server_name, @group_name)
    RETURN(1) -- Failure
  END

  -- Delete the row from systargetservergroupmembers
  DELETE FROM msdb.dbo.systargetservergroupmembers
  WHERE (servergroup_id = @servergroup_id)
    AND (server_id = @server_id)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_VERIFY_CATEGORY                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_category...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_category')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_category
go
CREATE PROCEDURE sp_verify_category
  @class          VARCHAR(8),
  @type           VARCHAR(12)  = NULL, -- Supply NULL only if you don't want it checked
  @name           sysname      = NULL, -- Supply NULL only if you don't want it checked
  @category_class INT OUTPUT,
  @category_type  INT OUTPUT           -- Supply NULL only if you don't want the return value
AS
BEGIN
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @class = LTRIM(RTRIM(@class))
  SELECT @type  = LTRIM(RTRIM(@type))
  SELECT @name  = LTRIM(RTRIM(@name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@type = '') SELECT @type = NULL
  IF (@name = N'') SELECT @name = NULL

  -- Check class
  SELECT @class = UPPER(@class collate SQL_Latin1_General_CP1_CS_AS)
  SELECT @category_class = CASE @class
                             WHEN 'JOB'      THEN 1
                             WHEN 'ALERT'    THEN 2
                             WHEN 'OPERATOR' THEN 3
                             ELSE 0
                           END
  IF (@category_class = 0)
  BEGIN
    RAISERROR(14266, -1, -1, '@class', 'JOB, ALERT, OPERATOR')
    RETURN(1) -- Failure
  END

  -- Check name
  IF ((@name IS NOT NULL) AND (@name = N'[DEFAULT]'))
  BEGIN
    RAISERROR(14200, -1, -1, '@name')
    RETURN(1) -- Failure
  END

  -- Check type [optionally]
  IF (@type IS NOT NULL)
  BEGIN
    IF (@class = 'JOB')
    BEGIN
      SELECT @type = UPPER(@type collate SQL_Latin1_General_CP1_CS_AS)
      SELECT @category_type = CASE @type
                                WHEN 'LOCAL'        THEN 1
                                WHEN 'MULTI-SERVER' THEN 2
                                ELSE 0
                              END
      IF (@category_type = 0)
      BEGIN
        RAISERROR(14266, -1, -1, '@type', 'LOCAL, MULTI-SERVER')
        RETURN(1) -- Failure
      END
    END
    ELSE
    BEGIN
      IF (@type <> 'NONE')
      BEGIN
        RAISERROR(14266, -1, -1, '@type', 'NONE')
        RETURN(1) -- Failure
      END
      ELSE
        SELECT @category_type = 3
    END
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_ADD_CATEGORY                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_category...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_category')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_category
go
CREATE PROCEDURE sp_add_category
  @class VARCHAR(8)   = 'JOB',   -- JOB or ALERT or OPERATOR
  @type  VARCHAR(12)  = 'LOCAL', -- LOCAL or MULTI-SERVER (for JOB) or NONE otherwise
  @name  sysname
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @category_type  INT
  DECLARE @category_class INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @class = LTRIM(RTRIM(@class))
  SELECT @type  = LTRIM(RTRIM(@type))
  SELECT @name  = LTRIM(RTRIM(@name))

  EXECUTE @retval = sp_verify_category @class,
                                       @type,
                                       @name,
                                       @category_class OUTPUT,
                                       @category_type  OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check name
  IF (EXISTS (SELECT *
              FROM msdb.dbo.syscategories
              WHERE (category_class = @category_class)
                AND (name = @name)))
  BEGIN
    RAISERROR(14261, -1, -1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Add the row
  INSERT INTO msdb.dbo.syscategories (category_class, category_type, name)
  VALUES (@category_class, @category_type, @name)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_UPDATE_CATEGORY                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_category...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_category')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_category
go
CREATE PROCEDURE sp_update_category
  @class    VARCHAR(8),  -- JOB or ALERT or OPERATOR
  @name     sysname,
  @new_name sysname
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @category_id    INT
  DECLARE @category_class INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @class    = LTRIM(RTRIM(@class))
  SELECT @name     = LTRIM(RTRIM(@name))
  SELECT @new_name = LTRIM(RTRIM(@new_name))

  --turn empy parametrs tu null parameters
  IF @name = ''  SELECT @name = NULL

  EXECUTE @retval = sp_verify_category @class,
                                       NULL,
                                       @new_name,
                                       @category_class OUTPUT,
                                       NULL
  IF (@retval <> 0)
    RETURN(1) -- Failure

  --ID @name not null check id such a category exists
  --check name - it should exist if not null
  IF @name IS NOT NULL AND
     NOT EXISTS(SELECT * FROM msdb.dbo.syscategories WHERE name = @name
      AND category_class = @category_class)
  BEGIN
      RAISERROR(14526, -1, -1, @name, @category_class)
      RETURN(1) -- Failure
  END

  -- Check name
  SELECT @category_id = category_id
  FROM msdb.dbo.syscategories
  WHERE (category_class = @category_class)
    AND (name = @new_name)
  IF (@category_id IS NOT NULL)
  BEGIN
    RAISERROR(14261, -1, -1, '@new_name', @new_name)
    RETURN(1) -- Failure
  END

  -- Make sure that we're not updating one of the permanent categories (id's 0 - 99)
  IF (@category_id < 100)
  BEGIN
    RAISERROR(14276, -1, -1, @name, @class)
    RETURN(1) -- Failure
  END

  -- Update the category name
  UPDATE msdb.dbo.syscategories
  SET name = @new_name
  WHERE (category_class = @category_class)
    AND (name = @name)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_CATEGORY                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_category...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_category')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_category
go
CREATE PROCEDURE sp_delete_category
  @class VARCHAR(8),  -- JOB or ALERT or OPERATOR
  @name  sysname
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @category_id    INT
  DECLARE @category_class INT
  DECLARE @category_type  INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @class = LTRIM(RTRIM(@class))
  SELECT @name  = LTRIM(RTRIM(@name))

  EXECUTE @retval = sp_verify_category @class,
                                       NULL,
                                       NULL,
                                       @category_class OUTPUT,
                                       NULL
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check name
  SELECT @category_id = category_id,
         @category_type = category_type
  FROM msdb.dbo.syscategories
  WHERE (category_class = @category_class)
    AND (name = @name)
  IF (@category_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Make sure that we're not deleting one of the permanent categories (id's 0 - 99)
  IF (@category_id < 100)
  BEGIN
    RAISERROR(14276, -1, -1, @name, @class)
    RETURN(1) -- Failure
  END

  BEGIN TRANSACTION

    -- Clean-up any Jobs that reference the deleted category
    UPDATE msdb.dbo.sysjobs
    SET category_id = CASE @category_type
                        WHEN 1 THEN 0 -- [Uncategorized (Local)]
                        WHEN 2 THEN 2 -- [Uncategorized (Multi-Server)]
                      END
    WHERE (category_id = @category_id)

    -- Clean-up any Alerts that reference the deleted category
    UPDATE msdb.dbo.sysalerts
    SET category_id = 98
    WHERE (category_id = @category_id)

    -- Clean-up any Operators that reference the deleted category
    UPDATE msdb.dbo.sysoperators
    SET category_id = 99
    WHERE (category_id = @category_id)

    -- Finally, delete the category itself
    DELETE FROM msdb.dbo.syscategories
    WHERE (category_id = @category_id)

  COMMIT TRANSACTION

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_HELP_CATEGORY                                           */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_category...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_category')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_category
go
CREATE PROCEDURE sp_help_category
  @class  VARCHAR(8)   = 'JOB', -- JOB, ALERT or OPERATOR
  @type   VARCHAR(12)  = NULL,  -- LOCAL, MULTI-SERVER, or NONE
  @name   sysname      = NULL,
  @suffix BIT          = 0      -- 0 = no suffix, 1 = add suffix
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @type_in        VARCHAR(12)
  DECLARE @category_type  INT
  DECLARE @category_class INT
  DECLARE @where_clause   NVARCHAR(500)
  DECLARE @cmd            NVARCHAR(max)

  SET NOCOUNT ON

  -- Both name and type can be NULL (this is valid, indeed it is how SQLDMO populates
  -- the JobCategory collection)

  -- Remove any leading/trailing spaces from parameters
  SELECT @class = LTRIM(RTRIM(@class))
  SELECT @type  = LTRIM(RTRIM(@type))
  SELECT @name  = LTRIM(RTRIM(@name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@type = '') SELECT @type = NULL
  IF (@name = N'') SELECT @name = NULL

  -- Check the type and class
  IF (@class = 'JOB') AND (@type IS NULL)
    SELECT @type_in = 'LOCAL' -- This prevents sp_verify_category from failing
  ELSE
  IF (@class <> 'JOB') AND (@type IS NULL)
    SELECT @type_in = 'NONE'
  ELSE
    SELECT @type_in = @type

  EXECUTE @retval = sp_verify_category @class,
                                       @type_in,
                                       NULL,
                                       @category_class OUTPUT,
                                       @category_type  OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Make sure that 'suffix' is either 0 or 1
  IF (@suffix <> 0)
    SELECT @suffix = 1

  --check name - it should exist if not null
  IF @name IS NOT NULL AND
     NOT EXISTS(SELECT * FROM msdb.dbo.syscategories WHERE name = @name
      AND category_class = @category_class)
  BEGIN
      DECLARE @category_class_string NVARCHAR(25)
      SET @category_class_string = CAST(@category_class AS nvarchar(25))
      RAISERROR(14526, -1, -1, @name, @category_class_string)
      RETURN(1) -- Failure
  END


  -- Build the WHERE qualifier
  SELECT @where_clause = N'WHERE (category_class = ' + CONVERT(NVARCHAR, @category_class) + N') '
  IF (@name IS NOT NULL)
    SELECT @where_clause = @where_clause + N'AND (name = N' + QUOTENAME(@name, '''') + N') '
  IF (@type IS NOT NULL)
    SELECT @where_clause = @where_clause + N'AND (category_type = ' + CONVERT(NVARCHAR, @category_type) + N') '

  -- Construct the query
  SELECT @cmd = N'SELECT category_id, '
  IF (@suffix = 1)
  BEGIN
    SELECT @cmd = @cmd + N'''category_type'' = '
    SELECT @cmd = @cmd + N'CASE category_type '
    SELECT @cmd = @cmd + N'WHEN 0 THEN ''NONE'' '
    SELECT @cmd = @cmd + N'WHEN 1 THEN ''LOCAL'' '
    SELECT @cmd = @cmd + N'WHEN 2 THEN ''MULTI-SERVER'' '
    SELECT @cmd = @cmd + N'WHEN 3 THEN ''NONE'' '
    SELECT @cmd = @cmd + N'ELSE FORMATMESSAGE(14205) '
    SELECT @cmd = @cmd + N'END, '
  END
  ELSE
  BEGIN
    SELECT @cmd = @cmd + N'category_type, '
  END
  SELECT @cmd = @cmd + N'name '
  SELECT @cmd = @cmd + N'FROM msdb.dbo.syscategories '

  -- Execute the query
  EXECUTE (@cmd + @where_clause + N'ORDER BY category_type, name')

  RETURN(@@error) -- 0 means success
END
go


/**************************************************************/
/* SP_HELP_TARGETSERVER                                       */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_targetserver...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_targetserver')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_targetserver
go
CREATE PROCEDURE sp_help_targetserver
  @server_name sysname = NULL
AS
BEGIN
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @server_name = UPPER(LTRIM(RTRIM(@server_name)))

  IF (@server_name IS NOT NULL)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.systargetservers
                    WHERE (UPPER(server_name) = @server_name)))
    BEGIN
      RAISERROR(14262, -1, -1, '@server_name', @server_name)
      RETURN(1) -- Failure
    END
  END

  DECLARE @unread_instructions TABLE
  (
  target_server       sysname COLLATE database_default,
  unread_instructions INT
  )

  INSERT INTO @unread_instructions
  SELECT target_server, COUNT(*)
  FROM msdb.dbo.sysdownloadlist
  WHERE (status = 0)
  GROUP BY target_server

  SELECT sts.server_id,
         sts.server_name,
         sts.location,
         sts.time_zone_adjustment,
         sts.enlist_date,
         sts.last_poll_date,
        'status' = sts.status |
                   CASE WHEN DATEDIFF(ss, sts.last_poll_date, GETDATE()) > (3 * sts.poll_interval) THEN 0x2 ELSE 0 END |
                   CASE WHEN ((SELECT COUNT(*)
                               FROM msdb.dbo.sysdownloadlist sdl
                               WHERE (sdl.target_server = sts.server_name)
                                 AND (sdl.error_message IS NOT NULL)) > 0) THEN 0x4 ELSE 0 END,
        'unread_instructions' = ISNULL(ui.unread_instructions, 0),
        'local_time' = DATEADD(SS, DATEDIFF(SS, sts.last_poll_date, GETDATE()), sts.local_time_at_last_poll),
        sts.enlisted_by_nt_user,
        sts.poll_interval
  FROM msdb.dbo.systargetservers sts LEFT OUTER JOIN
       @unread_instructions      ui  ON (sts.server_name = ui.target_server)
  WHERE ((@server_name IS NULL) OR (UPPER(sts.server_name) = @server_name))
  ORDER BY server_name

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_RESYNC_TARGETSERVER                                     */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_resync_targetserver...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_resync_targetserver')
              AND (type = 'P')))
  DROP PROCEDURE sp_resync_targetserver
go
CREATE PROCEDURE sp_resync_targetserver
  @server_name sysname
AS
BEGIN
  SET NOCOUNT ON

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @server_name = LTRIM(RTRIM(@server_name))

  IF (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) <> N'ALL')
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.systargetservers
                    WHERE (UPPER(server_name) = UPPER(@server_name))))
    BEGIN
      RAISERROR(14262, -1, -1, '@server_name', @server_name)
      RETURN(1) -- Failure
    END

    -- We want the target server to:
    -- a) delete all their current MSX jobs, and
    -- b) download all their jobs again.
    -- So we delete all the current instructions and post a new set
    DELETE FROM msdb.dbo.sysdownloadlist
    WHERE (target_server = @server_name)
    EXECUTE msdb.dbo.sp_post_msx_operation 'DELETE', 'JOB', 0x00, @server_name
    EXECUTE msdb.dbo.sp_post_msx_operation 'INSERT', 'JOB', 0x00, @server_name
  END
  ELSE
  BEGIN
    -- We want ALL target servers to:
    -- a) delete all their current MSX jobs, and
    -- b) download all their jobs again.
    -- So we delete all the current instructions and post a new set
    TRUNCATE TABLE msdb.dbo.sysdownloadlist
    EXECUTE msdb.dbo.sp_post_msx_operation 'DELETE', 'JOB', 0x00, NULL
    EXECUTE msdb.dbo.sp_post_msx_operation 'INSERT', 'JOB', 0x00, NULL
  END

  RETURN(@@error) -- 0 means success
END
go

CHECKPOINT
go

/**************************************************************/
/* SP_PURGE_JOBHISTORY                                        */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_purge_jobhistory...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_purge_jobhistory')
              AND (type = 'P')))
  DROP PROCEDURE sp_purge_jobhistory
go
CREATE PROCEDURE sp_purge_jobhistory
  @job_name     sysname          = NULL,
  @job_id       UNIQUEIDENTIFIER = NULL,
  @oldest_date  DATETIME         = NULL
AS
BEGIN
  DECLARE @rows_affected INT
  DECLARE @total_rows    INT
  DECLARE @datepart      INT
  DECLARE @timepart      INT
  DECLARE @retval        INT
  DECLARE @job_owner_sid VARBINARY(85)

  SET NOCOUNT ON

  IF(@oldest_date IS NOT NULL)
  BEGIN
    SET @datepart = CONVERT(INT, CONVERT(VARCHAR, @oldest_date, 112))
    SET @timepart = (DATEPART(hh, @oldest_date) * 10000) + (DATEPART(mi, @oldest_date) * 100) + (DATEPART(ss, @oldest_date))
  END
  ELSE
  BEGIN
    SET @datepart = 99999999
    SET @timepart = 0
  END

  IF ((@job_name IS NOT NULL) OR (@job_id IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT,
                                                 @owner_sid = @job_owner_sid OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

    -- Check permissions beyond what's checked by the sysjobs_view
    -- SQLAgentReader role that can see all jobs but
    -- cannot purge history of jobs they do not own
    IF (@job_owner_sid <> SUSER_SID()                      -- does not own the job
       AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)       -- is not sysadmin
       AND (ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) <> 1)) -- is not SQLAgentOperatorRole
    BEGIN
     RAISERROR(14392, -1, -1);
     RETURN(1) -- Failure
    END

    -- Delete the histories for this job
    DELETE FROM msdb.dbo.sysjobhistory
    WHERE (job_id = @job_id) AND
          ((run_date < @datepart) OR
           (run_date <= @datepart AND run_time < @timepart))
    SELECT @rows_affected = @@rowcount
  END
  ELSE
  BEGIN
    -- Only a sysadmin or SQLAgentOperatorRole can do this
   IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)           -- is not sysadmin
       AND (ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) <> 1)) -- is not SQLAgentOperatorRole
    BEGIN
      RAISERROR(14392, -1, -1)
      RETURN(1) -- Failure
    END

    IF(@oldest_date IS NOT NULL)
    BEGIN
        DELETE FROM msdb.dbo.sysjobhistory
        WHERE ((run_date < @datepart) OR
               (run_date <= @datepart AND run_time < @timepart))
    END
    ELSE
    BEGIN
        DELETE FROM msdb.dbo.sysjobhistory
    END

   SELECT @rows_affected = @@rowcount
  END

  RAISERROR(14226, 0, 1, @rows_affected)

  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_HELP_JOBHISTORY_FULL                                    */
/**************************************************************/
use [msdb]

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobhistory_full')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobhistory_full
go
CREATE PROCEDURE sp_help_jobhistory_full
               @job_id               UNIQUEIDENTIFIER,
               @job_name             sysname,
               @step_id              INT,
               @sql_message_id       INT,
               @sql_severity         INT,
               @start_run_date       INT,
               @end_run_date         INT,
               @start_run_time       INT,
               @end_run_time         INT,
               @minimum_run_duration INT,
               @run_status           INT,
               @minimum_retries      INT,
               @oldest_first         INT,
               @server               sysname,
               @mode                 VARCHAR(7),
               @order_by             INT,
               @distributed_job_history BIT
AS
BEGIN
-- First save the current transaction isolation level
DECLARE @TRANSACTION_ISOLATION_LEVEL INT
SELECT @TRANSACTION_ISOLATION_LEVEL = transaction_isolation_level FROM sys.dm_exec_sessions where session_id = @@SPID
-- If the isolation level is not known, do nothing!
IF @TRANSACTION_ISOLATION_LEVEL >0 AND @TRANSACTION_ISOLATION_LEVEL < 6
BEGIN
  -- Set transaction isolation level to READ UNCOMMITTED
  --  This will ensure that we can still read the history even if the rows are locked by the TABLOCKX operation on the history row limiter
  SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
END

IF(@distributed_job_history = 1)
  SELECT null as instance_id,
     sj.job_id,
     job_name = sj.name,
     null as step_id,
     null as step_name,
     null as sql_message_id,
     null as sql_severity,
     sjh.last_outcome_message as message,
     sjh.last_run_outcome as run_status,
     sjh.last_run_date as run_date,
     sjh.last_run_time as run_time,
    sjh.last_run_duration as run_duration,
     null as operator_emailed,
     null as operator_netsentname,
     null as operator_paged,
     null as retries_attempted,
     sts.server_name as server
  FROM msdb.dbo.sysjobservers                sjh
  JOIN msdb.dbo.systargetservers sts ON (sts.server_id = sjh.server_id)
  JOIN msdb.dbo.sysjobs_view     sj  ON(sj.job_id = sjh.job_id)
  WHERE
  (@job_id = sjh.job_id)
  AND ((@start_run_date       IS NULL) OR (sjh.last_run_date >= @start_run_date))
  AND ((@end_run_date         IS NULL) OR (sjh.last_run_date <= @end_run_date))
  AND ((@start_run_time       IS NULL) OR (sjh.last_run_time >= @start_run_time))
  AND ((@minimum_run_duration IS NULL) OR (sjh.last_run_duration >= @minimum_run_duration))
  AND ((@run_status           IS NULL) OR (@run_status = sjh.last_run_outcome))
  AND ((@server               IS NULL) OR (sts.server_name = @server))
ELSE
  SELECT sjh.instance_id, -- This is included just for ordering purposes
     sj.job_id,
     job_name = sj.name,
     sjh.step_id,
     sjh.step_name,
     sjh.sql_message_id,
     sjh.sql_severity,
     sjh.message,
     sjh.run_status,
     sjh.run_date,
     sjh.run_time,
     sjh.run_duration,
     operator_emailed = so1.name,
     operator_netsent = so2.name,
     operator_paged = so3.name,
     sjh.retries_attempted,
     sjh.server
  FROM msdb.dbo.sysjobhistory                sjh
     LEFT OUTER JOIN msdb.dbo.sysoperators so1  ON (sjh.operator_id_emailed = so1.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so2  ON (sjh.operator_id_netsent = so2.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so3  ON (sjh.operator_id_paged = so3.id),
     msdb.dbo.sysjobs_view sj
  WHERE (sj.job_id = sjh.job_id)
  AND ((@job_id               IS NULL) OR (@job_id = sjh.job_id))
  AND ((@step_id              IS NULL) OR (@step_id = sjh.step_id))
  AND ((@sql_message_id       IS NULL) OR (@sql_message_id = sjh.sql_message_id))
  AND ((@sql_severity         IS NULL) OR (@sql_severity = sjh.sql_severity))
  AND ((@start_run_date       IS NULL) OR (sjh.run_date >= @start_run_date))
  AND ((@end_run_date         IS NULL) OR (sjh.run_date <= @end_run_date))
  AND ((@start_run_time       IS NULL) OR (sjh.run_time >= @start_run_time))
  AND ((@end_run_time         IS NULL) OR (sjh.run_time <= @end_run_time))
  AND ((@minimum_run_duration IS NULL) OR (sjh.run_duration >= @minimum_run_duration))
  AND ((@run_status           IS NULL) OR (@run_status = sjh.run_status))
  AND ((@minimum_retries      IS NULL) OR (sjh.retries_attempted >= @minimum_retries))
  AND ((@server               IS NULL) OR (sjh.server = @server))
  ORDER BY (sjh.instance_id * @order_by)

-- Revert the isolation level
IF @TRANSACTION_ISOLATION_LEVEL = 1
  SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
ELSE IF @TRANSACTION_ISOLATION_LEVEL = 2
  SET TRANSACTION ISOLATION LEVEL READ COMMITTED
ELSE IF @TRANSACTION_ISOLATION_LEVEL = 3
  SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
ELSE IF @TRANSACTION_ISOLATION_LEVEL = 4
  SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
ELSE IF @TRANSACTION_ISOLATION_LEVEL = 5
  SET TRANSACTION ISOLATION LEVEL SNAPSHOT

END

GO

/**************************************************************/
/* SP_HELP_JOBHISTORY_SUMMARY                                 */
/**************************************************************/
use [msdb]

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobhistory_summary')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobhistory_summary
go
CREATE PROCEDURE sp_help_jobhistory_summary
               @job_id               UNIQUEIDENTIFIER,
               @job_name             sysname,
               @step_id              INT,
               @sql_message_id       INT,
               @sql_severity         INT,
               @start_run_date       INT,
               @end_run_date         INT,
               @start_run_time       INT,
               @end_run_time         INT,
               @minimum_run_duration INT,
               @run_status           INT,
               @minimum_retries      INT,
               @oldest_first         INT,
               @server               sysname,
               @mode                 VARCHAR(7),
               @order_by             INT,
               @distributed_job_history BIT
AS
-- Summary format: same WHERE clause as for full, just a different SELECT list
IF(@distributed_job_history = 1)
  SELECT sj.job_id,
     job_name = sj.name,
     sjh.last_run_outcome as run_status,
     sjh.last_run_date as run_date,
     sjh.last_run_time as run_time,
     sjh.last_run_duration as run_duration,
     null as operator_emailed,
     null as operator_netsentname,
     null as operator_paged,
     null as retries_attempted,
     sts.server_name as server
  FROM msdb.dbo.sysjobservers                sjh
  JOIN msdb.dbo.systargetservers sts ON (sts.server_id = sjh.server_id)
  JOIN msdb.dbo.sysjobs_view     sj  ON(sj.job_id = sjh.job_id)
  WHERE
  (@job_id = sjh.job_id)
  AND ((@start_run_date       IS NULL) OR (sjh.last_run_date >= @start_run_date))
  AND ((@end_run_date         IS NULL) OR (sjh.last_run_date <= @end_run_date))
  AND ((@start_run_time       IS NULL) OR (sjh.last_run_time >= @start_run_time))
  AND ((@minimum_run_duration IS NULL) OR (sjh.last_run_duration >= @minimum_run_duration))
  AND ((@run_status           IS NULL) OR (@run_status = sjh.last_run_outcome))
  AND ((@server               IS NULL) OR (sts.server_name = @server))
ELSE
  SELECT sj.job_id,
     job_name = sj.name,
     sjh.run_status,
     sjh.run_date,
     sjh.run_time,
     sjh.run_duration,
     operator_emailed = substring(so1.name, 1, 20),
     operator_netsent = substring(so2.name, 1, 20),
     operator_paged = substring(so3.name, 1, 20),
     sjh.retries_attempted,
     sjh.server
  FROM msdb.dbo.sysjobhistory                sjh
     LEFT OUTER JOIN msdb.dbo.sysoperators so1  ON (sjh.operator_id_emailed = so1.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so2  ON (sjh.operator_id_netsent = so2.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so3  ON (sjh.operator_id_paged = so3.id),
     msdb.dbo.sysjobs_view                 sj
  WHERE (sj.job_id = sjh.job_id)
  AND ((@job_id               IS NULL) OR (@job_id = sjh.job_id))
  AND ((@step_id              IS NULL) OR (@step_id = sjh.step_id))
  AND ((@sql_message_id       IS NULL) OR (@sql_message_id = sjh.sql_message_id))
  AND ((@sql_severity         IS NULL) OR (@sql_severity = sjh.sql_severity))
  AND ((@start_run_date       IS NULL) OR (sjh.run_date >= @start_run_date))
  AND ((@end_run_date         IS NULL) OR (sjh.run_date <= @end_run_date))
  AND ((@start_run_time       IS NULL) OR (sjh.run_time >= @start_run_time))
  AND ((@end_run_time         IS NULL) OR (sjh.run_time <= @end_run_time))
  AND ((@minimum_run_duration IS NULL) OR (sjh.run_duration >= @minimum_run_duration))
  AND ((@run_status           IS NULL) OR (@run_status = sjh.run_status))
  AND ((@minimum_retries      IS NULL) OR (sjh.retries_attempted >= @minimum_retries))
  AND ((@server               IS NULL) OR (sjh.server = @server))
  ORDER BY (sjh.instance_id * @order_by)

GO

/**************************************************************/
/* SP_HELP_JOBHISTORY                                         */
/**************************************************************/
use [msdb]

PRINT ''
PRINT 'Creating procedure sp_help_jobhistory...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobhistory')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobhistory
go
CREATE PROCEDURE [dbo].[sp_help_jobhistory]
  @job_id               UNIQUEIDENTIFIER = NULL,
  @job_name             sysname          = NULL,
  @step_id              INT              = NULL,
  @sql_message_id       INT              = NULL,
  @sql_severity         INT              = NULL,
  @start_run_date       INT              = NULL,     -- YYYYMMDD
  @end_run_date         INT              = NULL,     -- YYYYMMDD
  @start_run_time       INT              = NULL,     -- HHMMSS
  @end_run_time         INT              = NULL,     -- HHMMSS
  @minimum_run_duration INT              = NULL,     -- HHMMSS
  @run_status           INT              = NULL,     -- SQLAGENT_EXEC_X code
  @minimum_retries      INT              = NULL,
  @oldest_first         INT              = 0,        -- Or 1
  @server               sysname          = NULL,
  @mode                 VARCHAR(7)       = 'SUMMARY' -- Or 'FULL' or 'SEM'
AS
BEGIN
  DECLARE @retval   INT
  DECLARE @order_by INT  -- Must be INT since it can be -1

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @server   = LTRIM(RTRIM(@server))
  SELECT @mode     = LTRIM(RTRIM(@mode))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@server = N'')   SELECT @server = NULL

  -- Check job id/name (if supplied)
  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check @start_run_date
  IF (@start_run_date IS NOT NULL)
  BEGIN
    EXECUTE @retval = sp_verify_job_date @start_run_date, '@start_run_date'
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check @end_run_date
  IF (@end_run_date IS NOT NULL)
  BEGIN
    EXECUTE @retval = sp_verify_job_date @end_run_date, '@end_run_date'
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check @start_run_time
  EXECUTE @retval = sp_verify_job_time @start_run_time, '@start_run_time'
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check @end_run_time
  EXECUTE @retval = sp_verify_job_time @end_run_time, '@end_run_time'
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check @run_status
  IF ((@run_status < 0) OR (@run_status > 5))
  BEGIN
    RAISERROR(14198, -1, -1, '@run_status', '0..5')
    RETURN(1) -- Failure
  END

  -- Check mode
  SELECT @mode = UPPER(@mode collate SQL_Latin1_General_CP1_CS_AS)
  IF (@mode NOT IN ('SUMMARY', 'FULL', 'SEM'))
  BEGIN
    RAISERROR(14266, -1, -1, '@mode', 'SUMMARY, FULL, SEM')
    RETURN(1) -- Failure
  END

  SELECT @order_by = -1
  IF (@oldest_first = 1)
    SELECT @order_by = 1

  DECLARE @distributed_job_history BIT
  SET @distributed_job_history = 0

  IF (@job_id IS NOT NULL) AND ( EXISTS (SELECT *
                              FROM msdb.dbo.sysjobs       sj,
                                 msdb.dbo.sysjobservers sjs
                              WHERE (sj.job_id = sjs.job_id)
                                 AND (sj.job_id = @job_id)
                                 AND (sjs.server_id <> 0)))
   SET @distributed_job_history = 1

  -- Return history information filtered by the supplied parameters.
  -- Having actual queries in subprocedures allows better query plans because query optimizer sniffs correct parameters
  IF (@mode = 'FULL')
  BEGIN
  -- NOTE: SQLDMO relies on the 'FULL' format; ** DO NOT CHANGE IT **
      EXECUTE sp_help_jobhistory_full
         @job_id,
         @job_name,
         @step_id,
         @sql_message_id,
         @sql_severity,
         @start_run_date,
         @end_run_date,
         @start_run_time,
         @end_run_time,
         @minimum_run_duration,
         @run_status,
         @minimum_retries,
         @oldest_first,
         @server,
         @mode,
         @order_by,
         @distributed_job_history
  END
  ELSE
  IF (@mode = 'SUMMARY')
  BEGIN
    -- Summary format: same WHERE clause as for full, just a different SELECT list
    EXECUTE sp_help_jobhistory_summary
         @job_id,
         @job_name,
         @step_id,
         @sql_message_id,
         @sql_severity,
         @start_run_date,
         @end_run_date,
         @start_run_time,
         @end_run_time,
         @minimum_run_duration,
         @run_status,
         @minimum_retries,
         @oldest_first,
         @server,
         @mode,
         @order_by,
         @distributed_job_history
  END
  ELSE
  IF (@mode = 'SEM')
  BEGIN
    -- SQL Enterprise Manager format
    EXECUTE sp_help_jobhistory_sem
         @job_id,
         @job_name,
         @step_id,
         @sql_message_id,
         @sql_severity,
         @start_run_date,
         @end_run_date,
         @start_run_time,
         @end_run_time,
         @minimum_run_duration,
         @run_status,
         @minimum_retries,
         @oldest_first,
         @server,
         @mode,
         @order_by,
         @distributed_job_history
  END
  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_ADD_JOBSERVER                                           */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_jobserver...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_jobserver')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_jobserver
go

CREATE PROCEDURE sp_add_jobserver
  @job_id         UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name       sysname          = NULL, -- Must provide either this or job_id
  @server_name    sysname         = NULL, -- if NULL will default to serverproperty('ServerName')
  @automatic_post BIT = 1                  -- Flag for SEM use only
AS
BEGIN
  DECLARE @retval                    INT
  DECLARE @server_id                 INT
  DECLARE @job_type                  VARCHAR(12)
  DECLARE @current_job_category_type VARCHAR(12)
  DECLARE @msx_operator_id           INT
  DECLARE @local_server_name         sysname
  DECLARE @is_sysadmin               INT
  DECLARE @job_owner                 sysname
  DECLARE @owner_sid                 VARBINARY(85)
  DECLARE @owner_name                sysname

  SET NOCOUNT ON

  IF (@server_name IS NULL) OR (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = N'(LOCAL)')
    SELECT @server_name = CONVERT(sysname, SERVERPROPERTY('ServerName'))

  -- Remove any leading/trailing spaces from parameters
  SELECT @server_name = UPPER(LTRIM(RTRIM(@server_name)))

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- First, check if the server is the local server
  SELECT @local_server_name = CONVERT(NVARCHAR,SERVERPROPERTY ('SERVERNAME'))

  IF (@server_name = UPPER(@local_server_name))
    SELECT @server_name = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

  -- For a multi-server job...
  IF (@server_name <> UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))))
  BEGIN
    -- 1) Only sysadmin can add a multi-server job
    IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0)
    BEGIN
       RAISERROR(14398, -1, -1);
       RETURN(1) -- Failure
    END

    -- 2) Job must be owned by sysadmin
    SELECT @owner_sid = owner_sid, @owner_name = dbo.SQLAGENT_SUSER_SNAME(owner_sid)
    FROM msdb.dbo.sysjobs
    WHERE (job_id = @job_id)

    IF @owner_sid = 0xFFFFFFFF
    BEGIN
      SELECT @is_sysadmin = 1
    END
    ELSE
    BEGIN
      SELECT @is_sysadmin = 0
      EXECUTE msdb.dbo.sp_sqlagent_has_server_access @login_name = @owner_name, @is_sysadmin_member = @is_sysadmin OUTPUT
    END

    IF (@is_sysadmin = 0)
    BEGIN
      RAISERROR(14544, -1, -1, @owner_name, N'sysadmin')
      RETURN(1) -- Failure
    END

    -- 3) Check if any of the TSQL steps have a non-null database_user_name
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobsteps
                WHERE (job_id = @job_id)
                  AND (subsystem = N'TSQL')
                  AND (database_user_name IS NOT NULL)))
    BEGIN
      RAISERROR(14542, -1, -1, N'database_user_name')
      RETURN(1) -- Failure
    END

    SELECT @server_id = server_id
    FROM msdb.dbo.systargetservers
    WHERE (UPPER(server_name) = @server_name)
    IF (@server_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@server_name', @server_name)
      RETURN(1) -- Failure
    END
  END
  ELSE
    SELECT @server_id = 0

  -- Check that this job has not already been targeted at this server
  IF (EXISTS (SELECT *
               FROM msdb.dbo.sysjobservers
               WHERE (job_id = @job_id)
                 AND (server_id = @server_id)))
  BEGIN
    RAISERROR(14269, -1, -1, @job_name, @server_name)
    RETURN(1) -- Failure
  END

  -- Prevent the job from being targeted at both the local AND remote servers
  SELECT @job_type = 'UNKNOWN'
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id = 0)))
    SELECT @job_type = 'LOCAL'
  ELSE
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
    SELECT @job_type = 'MULTI-SERVER'

  IF ((@server_id = 0) AND (@job_type = 'MULTI-SERVER'))
  BEGIN
    RAISERROR(14290, -1, -1)
    RETURN(1) -- Failure
  END
  IF ((@server_id <> 0) AND (@job_type = 'LOCAL'))
  BEGIN
    RAISERROR(14291, -1, -1)
    RETURN(1) -- Failure
  END

  -- For a multi-server job, check that any notifications are to the MSXOperator
  IF (@job_type = 'MULTI-SERVER')
  BEGIN
    SELECT @msx_operator_id = id
    FROM msdb.dbo.sysoperators
    WHERE (name = N'MSXOperator')

    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobs
                WHERE (job_id = @job_id)
                  AND (((notify_email_operator_id <> 0)   AND (notify_email_operator_id <> @msx_operator_id)) OR
                       ((notify_page_operator_id <> 0)    AND (notify_page_operator_id <> @msx_operator_id))  OR
                       ((notify_netsend_operator_id <> 0) AND (notify_netsend_operator_id <> @msx_operator_id)))))
    BEGIN
      RAISERROR(14221, -1, -1, 'MSXOperator')
      RETURN(1) -- Failure
    END
  END

  -- Insert the sysjobservers row
  INSERT INTO msdb.dbo.sysjobservers
         (job_id,
          server_id,
          last_run_outcome,
          last_outcome_message,
          last_run_date,
          last_run_time,
          last_run_duration)
  VALUES (@job_id,
          @server_id,
          5,  -- ie. SQLAGENT_EXEC_UNKNOWN (can't use 0 since this is SQLAGENT_EXEC_FAIL)
          NULL,
          0,
          0,
          0)

  -- Re-categorize the job (if necessary)
  SELECT @current_job_category_type = CASE category_type
                                        WHEN 1 THEN 'LOCAL'
                                        WHEN 2 THEN 'MULTI-SERVER'
                                      END
  FROM msdb.dbo.sysjobs_view  sjv,
       msdb.dbo.syscategories sc
  WHERE (sjv.category_id = sc.category_id)
    AND (sjv.job_id = @job_id)

  IF (@server_id = 0) AND (@current_job_category_type = 'MULTI-SERVER')
  BEGIN
    UPDATE msdb.dbo.sysjobs
    SET category_id = 0 -- [Uncategorized (Local)]
    WHERE (job_id = @job_id)
  END
  IF (@server_id <> 0) AND (@current_job_category_type = 'LOCAL')
  BEGIN
    UPDATE msdb.dbo.sysjobs
    SET category_id = 2 -- [Uncategorized (Multi-Server)]
    WHERE (job_id = @job_id)
  END

  -- Instruct the new server to pick up the job
  IF (@automatic_post = 1)
    EXECUTE @retval = sp_post_msx_operation 'INSERT', 'JOB', @job_id, @server_name

  -- If the job is local, make sure that SQLServerAgent caches it
  IF (@server_id = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                        @job_id      = @job_id,
                                        @action_type = N'I'
  END

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_JOBSERVER                                        */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_jobserver...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_jobserver')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_jobserver
go
CREATE PROCEDURE sp_delete_jobserver
  @job_id      UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name    sysname          = NULL, -- Must provide either this or job_id
  @server_name sysname
AS
BEGIN
  DECLARE @retval             INT
  DECLARE @server_id          INT

  SET NOCOUNT ON

  /* check input parameters */
  IF @server_name IS NULL
  BEGIN
  RAISERROR(14043, -1, -1, '@server_name')
  RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  -- and convert it to upper for future comparisons
  SELECT @server_name = UPPER(LTRIM(RTRIM(@server_name)))

  IF (@server_name collate SQL_Latin1_General_CP1_CS_AS = '(LOCAL)')
  BEGIN
    SELECT @server_name = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))
  END

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
  BEGIN
    RETURN(1) -- Failure
  END

  -- If a msx jobserver is being removed,
  -- get server_id is 0
  -- else server_id record matching the server_name from systargetservers
  IF (@server_name <> UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))))
  BEGIN
    SELECT @server_id = server_id
    FROM msdb.dbo.systargetservers
    WHERE (UPPER(server_name) = @server_name)
    IF (@server_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@server_name', @server_name)
      RETURN(1) -- Failure
    END
  END
  ELSE
  BEGIN
    SELECT @server_id = 0
  END

  -- Check that the job is actually targeted at the server
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysjobservers
                  WHERE (job_id = @job_id)
                    AND (server_id = @server_id)))
  BEGIN
    RAISERROR(14270, -1, -1, @job_name, @server_name)
    RETURN(1) -- Failure
  END

  -- Instruct the deleted server to purge the job
  -- NOTE: We must do this BEFORE we delete the sysjobservers row
  EXECUTE @retval = sp_post_msx_operation 'DELETE', 'JOB', @job_id, @server_name

  -- Delete the sysjobservers row
  DELETE FROM msdb.dbo.sysjobservers
  WHERE (job_id = @job_id)
    AND (server_id = @server_id)

  -- If the job is local, make sure that SQLServerAgent removes it from cache
  IF (@server_id = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                        @job_id      = @job_id,
                                        @action_type = N'D'
  END

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_HELP_JOBSERVER                                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_jobserver...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobserver')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobserver
go
CREATE PROCEDURE sp_help_jobserver
  @job_id                UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name              sysname          = NULL, -- Must provide either this or job_id
  @show_last_run_details TINYINT          = 0     -- Controls if last-run execution information is part of the result set (1 = yes, 0 = no)
AS
BEGIN
  DECLARE @retval INT

  SET NOCOUNT ON

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- The show-last-run-details flag must be either 1 or 0
  IF (@show_last_run_details <> 0)
    SELECT @show_last_run_details = 1

  IF (@show_last_run_details = 1)
  BEGIN
    -- List the servers that @job_name has been targeted at (INCLUDING last-run details)
    SELECT stsv.server_id,
           stsv.server_name,
           stsv.enlist_date,
           stsv.last_poll_date,
           sjs.last_run_date,
           sjs.last_run_time,
           sjs.last_run_duration,
           sjs.last_run_outcome,  -- Same as JOB_OUTCOME_CODE (SQLAGENT_EXEC_x)
           sjs.last_outcome_message
    FROM msdb.dbo.sysjobservers         sjs  LEFT OUTER JOIN
         msdb.dbo.systargetservers_view stsv ON (sjs.server_id = stsv.server_id)
    WHERE (sjs.job_id = @job_id)
  END
  ELSE
  BEGIN
    -- List the servers that @job_name has been targeted at (EXCLUDING last-run details)
    SELECT stsv.server_id,
           stsv.server_name,
           stsv.enlist_date,
           stsv.last_poll_date
    FROM msdb.dbo.sysjobservers         sjs  LEFT OUTER JOIN
         msdb.dbo.systargetservers_view stsv ON (sjs.server_id = stsv.server_id)
    WHERE (sjs.job_id = @job_id)
  END

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_HELP_DOWNLOADLIST                                       */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_downloadlist...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_downloadlist')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_downloadlist
go
CREATE PROCEDURE sp_help_downloadlist
  @job_id          UNIQUEIDENTIFIER = NULL, -- If provided must NOT also provide job_name
  @job_name        sysname          = NULL, -- If provided must NOT also provide job_id
  @operation       VARCHAR(64)      = NULL,
  @object_type     VARCHAR(64)      = NULL, -- Only 'JOB' or 'SERVER' are valid in 7.0
  @object_name     sysname          = NULL,
  @target_server   sysname         = NULL,
  @has_error       TINYINT          = NULL, -- NULL or 1
  @status          TINYINT          = NULL,
  @date_posted     DATETIME         = NULL  -- Include all entries made on OR AFTER this date
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @operation_code INT
  DECLARE @object_type_id TINYINT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @operation     = LTRIM(RTRIM(@operation))
  SELECT @object_type   = LTRIM(RTRIM(@object_type))
  SELECT @object_name   = LTRIM(RTRIM(@object_name))
  SELECT @target_server = UPPER(LTRIM(RTRIM(@target_server)))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@operation     = '') SELECT @operation = NULL
  IF (@object_type   = '') SELECT @object_type = NULL
  IF (@object_name   = N'') SELECT @object_name = NULL
  IF (@target_server = N'') SELECT @target_server = NULL

  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- Check operation
  IF (@operation IS NOT NULL)
  BEGIN
    SELECT @operation = UPPER(@operation collate SQL_Latin1_General_CP1_CS_AS)
    SELECT @operation_code = CASE @operation
                               WHEN 'INSERT'    THEN 1
                               WHEN 'UPDATE'    THEN 2
                               WHEN 'DELETE'    THEN 3
                               WHEN 'START'     THEN 4
                               WHEN 'STOP'      THEN 5
                               WHEN 'RE-ENLIST' THEN 6
                               WHEN 'DEFECT'    THEN 7
                               WHEN 'SYNC-TIME' THEN 8
                               WHEN 'SET-POLL'  THEN 9
                               ELSE 0
                             END
    IF (@operation_code = 0)
    BEGIN
      RAISERROR(14266, -1, -1, '@operation_code', 'INSERT, UPDATE, DELETE, START, STOP, RE-ENLIST, DEFECT, SYNC-TIME, SET-POLL')
      RETURN(1) -- Failure
    END
  END

  -- Check object type (in 7.0 only 'JOB' and 'SERVER' are valid)
  IF (@object_type IS NOT NULL)
  BEGIN
    SELECT @object_type = UPPER(@object_type collate SQL_Latin1_General_CP1_CS_AS)
    IF ((@object_type <> 'JOB') AND (@object_type <> 'SERVER'))
    BEGIN
      RAISERROR(14266, -1, -1, '@object_type', 'JOB, SERVER')
      RETURN(1) -- Failure
    END
    ELSE
      SELECT @object_type_id = CASE @object_type
                                 WHEN 'JOB'    THEN 1
                                 WHEN 'SERVER' THEN 2
                                 ELSE 0
                               END
  END

  -- If object-type is supplied then object-name must also be supplied
  IF ((@object_type IS NOT NULL) AND (@object_name IS NULL)) OR
     ((@object_type IS NULL)     AND (@object_name IS NOT NULL))
  BEGIN
    RAISERROR(14272, -1, -1)
    RETURN(1) -- Failure
  END

  -- Check target server
  IF (@target_server IS NOT NULL) AND NOT EXISTS (SELECT *
                                                  FROM msdb.dbo.systargetservers
                                                  WHERE UPPER(server_name) = @target_server)
  BEGIN
    RAISERROR(14262, -1, -1, '@target_server', @target_server)
    RETURN(1) -- Failure
  END

  -- Check has-error
  IF (@has_error IS NOT NULL) AND (@has_error <> 1)
  BEGIN
    RAISERROR(14266, -1, -1, '@has_error', '1, NULL')
    RETURN(1) -- Failure
  END

  -- Check status
  IF (@status IS NOT NULL) AND (@status <> 0) AND (@status <> 1)
  BEGIN
    RAISERROR(14266, -1, -1, '@status', '0, 1')
    RETURN(1) -- Failure
  END

  -- Return the result set
  SELECT sdl.instance_id,
         sdl.source_server,
        'operation_code' = CASE sdl.operation_code
                             WHEN 1 THEN '1 (INSERT)'
                             WHEN 2 THEN '2 (UPDATE)'
                             WHEN 3 THEN '3 (DELETE)'
                             WHEN 4 THEN '4 (START)'
                             WHEN 5 THEN '5 (STOP)'
                             WHEN 6 THEN '6 (RE-ENLIST)'
                             WHEN 7 THEN '7 (DEFECT)'
                             WHEN 8 THEN '8 (SYNC-TIME)'
                             WHEN 9 THEN '9 (SET-POLL)'
                             ELSE CONVERT(VARCHAR, sdl.operation_code) + ' ' + FORMATMESSAGE(14205)
                           END,
        'object_name' = ISNULL(sjv.name, CASE
                                           WHEN (sdl.operation_code >= 1) AND (sdl.operation_code <= 5) AND (sdl.object_id = CONVERT(UNIQUEIDENTIFIER, 0x00)) THEN FORMATMESSAGE(14212) -- '(all jobs)'
                                           WHEN (sdl.operation_code  = 3) AND (sdl.object_id <> CONVERT(UNIQUEIDENTIFIER, 0x00)) THEN sdl.deleted_object_name -- Special case handling for a deleted job
                                           WHEN (sdl.operation_code >= 1) AND (sdl.operation_code <= 5) AND (sdl.object_id <> CONVERT(UNIQUEIDENTIFIER, 0x00)) THEN FORMATMESSAGE(14580) -- 'job' (safety belt: should never appear)
                                           WHEN (sdl.operation_code >= 6) AND (sdl.operation_code <= 9) THEN sdl.target_server
                                           ELSE FORMATMESSAGE(14205)
                                         END),
        'object_id' = ISNULL(sjv.job_id, CASE sdl.object_id
                                           WHEN CONVERT(UNIQUEIDENTIFIER, 0x00) THEN CONVERT(UNIQUEIDENTIFIER, 0x00)
                                           ELSE sdl.object_id
                                         END),
         sdl.target_server,
         sdl.error_message,
         sdl.date_posted,
         sdl.date_downloaded,
         sdl.status
  FROM msdb.dbo.sysdownloadlist sdl LEFT OUTER JOIN
       msdb.dbo.sysjobs_view    sjv ON (sdl.object_id = sjv.job_id)
  WHERE ((@operation_code IS NULL) OR (operation_code = @operation_code))
    AND ((@object_type_id IS NULL) OR (object_type = @object_type_id))
    AND ((@job_id         IS NULL) OR (object_id = @job_id))
    AND ((@target_server  IS NULL) OR (target_server = @target_server))
    AND ((@has_error      IS NULL) OR (DATALENGTH(error_message) >= 1 * @has_error))
    AND ((@status         IS NULL) OR (status = @status))
    AND ((@date_posted    IS NULL) OR (date_posted >= @date_posted))
  ORDER BY sdl.instance_id

  RETURN(@@error) -- 0 means success

END
GO

/**************************************************************/
/* SP_ENUM_SQLAGENT_SUBSYSTEMS_INTERNAL                       */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_enum_sqlagent_subsystems_internal...'
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_enum_sqlagent_subsystems_internal')
              AND (type = 'P')))
  DROP PROCEDURE sp_enum_sqlagent_subsystems_internal
go
CREATE PROCEDURE sp_enum_sqlagent_subsystems_internal
   @syssubsytems_refresh_needed BIT = 0
AS
BEGIN
  DECLARE @retval INT
  SET NOCOUNT ON
  -- this call will populate subsystems table if necessary
  EXEC @retval = msdb.dbo.sp_verify_subsystems @syssubsytems_refresh_needed
  IF @retval <> 0
     RETURN(@retval)

  -- Check if replication is installed
  DECLARE @replication_installed INT
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\Replication',
                                         N'IsInstalled',
                                         @replication_installed OUTPUT,
                                         N'no_output'
  SELECT @replication_installed = ISNULL(@replication_installed, 0)

  DECLARE @xplat int = IIF((EXISTS (SELECT 1 FROM master.sys.dm_os_windows_info WHERE windows_release<>N'')), 0, 1)

   DECLARE @subsystems TABLE
   (
      subsystem_id       INT         NOT NULL,
      subsystem          NVARCHAR(40)  NOT NULL,
      description_id     INT         NULL,
      subsystem_dll      NVARCHAR(255)  NULL,
      agent_exe          NVARCHAR(255)  NULL,
      start_entry_point  NVARCHAR(30)   NULL,
      event_entry_point  NVARCHAR(30)   NULL,
      stop_entry_point   NVARCHAR(30)   NULL,
      max_worker_threads INT           NULL
   )

   -- @syssubsytems_refresh_needed is set when SQL Agent calls this proc on agent startup
   -- all other scenarios in SMO does not set @syssubsytems_refresh_needed
   IF(@syssubsytems_refresh_needed = 1)
   BEGIN
       -- system subsystems
       INSERT INTO @subsystems
       SELECT subsystem_id,
              subsystem,
              description_id,
              subsystem_dll,
              agent_exe,
              start_entry_point,
              event_entry_point,
              stop_entry_point,
              max_worker_threads
       FROM sys.fn_sqlagent_subsystems()
   END

   -- user subsytems. Note that if we are running xplat, we filter out subsystems unimplemented
   -- cross plat. When @xplat is 0, we filter nothing.
   --
   -- The following subsystems are generally available
   --   TSQL             Transact-SQL Subsystem	                         subsystem_id = 1, available xplat
   --   CmdExec          Command-Line Subsystem	                         subsystem_id = 3, not available xplat
   --   Snapshot         Replication Snapshot Subsystem	                 subsystem_id = 4, availability in registry
   --   LogReader        Replication Transaction-Log Reader Subsystem    subsystem_id = 5, availability in registry
   --   Distribution     Replication Distribution Subsystem              subsystem_id = 6, availability in registry
   --   Merge            Replication Merge Subsystem	                 subsystem_id = 7, availability in registry
   --   QueueReader      Replication Transaction Queue Reader Subsystem  subsystem_id = 8, availability in registry
   --   ANALYSISQUERY    Analysis query subsystem	                     subsystem_id = 9, not available xplat
   --   ANALYSISCOMMAND  Analysis command subsystem	                     subsystem_id = 10, not available xplat
   --   PowerShell	     PowerShell Subsystem	                         subsystem_id = 12, not available xplat
   --
   -- We, therefore, filter out anything subsystem_id >= 9 and subsystem_id = 3 (CmdExec), which are not implemented.
   -- @syssubsytems_refresh_needed is 1 when SQLAgent (not the user) gets the list. In that case, we need to return CmdExec
   -- since it's needed for logshipping. When new subsystems are implemented cross-platform, the WHERE clause below needs
   -- to be updated (Replication is special since it's availability is controlled by a value in the registry)
   INSERT INTO @subsystems
   SELECT subsystem_id,
            subsystem,
            description_id,
            subsystem_dll,
            agent_exe,
            start_entry_point,
            event_entry_point,
            stop_entry_point,
            max_worker_threads
    FROM syssubsystems
    WHERE ((subsystem_id < 9 AND subsystem_id <> 3) OR (subsystem_id = 3 AND @syssubsytems_refresh_needed = 1)) OR @xplat = 0

    IF (@replication_installed = 0)
    BEGIN
        SELECT  subsystem,
            description = FORMATMESSAGE(description_id),
            subsystem_dll,
            agent_exe,
            start_entry_point,
            event_entry_point,
            stop_entry_point,
            max_worker_threads,
            subsystem_id
        FROM @subsystems
        WHERE (subsystem NOT IN (N'Distribution', N'LogReader', N'Merge', N'Snapshot', N'QueueReader'))
        ORDER by subsystem
    END
    ELSE
    BEGIN
        SELECT  subsystem,
            description = FORMATMESSAGE(description_id),
            subsystem_dll,
            agent_exe,
            start_entry_point,
            event_entry_point,
            stop_entry_point,
            max_worker_threads,
            subsystem_id
        FROM @subsystems
        ORDER by subsystem_id
    END

  RETURN(0)
END
GO

/**************************************************************/
/* SP_ENUM_SQLAGENT_SUBSYSTEMS                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_enum_sqlagent_subsystems...'
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_enum_sqlagent_subsystems')
              AND (type = 'P')))
  DROP PROCEDURE sp_enum_sqlagent_subsystems
go
CREATE PROCEDURE sp_enum_sqlagent_subsystems
AS
BEGIN
  DECLARE @retval         INT
  EXEC @retval = msdb.dbo.sp_enum_sqlagent_subsystems_internal
  RETURN(@retval)
END
go


/**************************************************************/
/* SP_VERIFY_SUBSYSTEM                                        */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_subsystem...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_subsystem')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_subsystem
go
CREATE PROCEDURE sp_verify_subsystem
  @subsystem NVARCHAR(40)
AS
BEGIN
  DECLARE @retval         INT
  SET NOCOUNT ON

  -- this call will populate subsystems table if necessary
  EXEC @retval = msdb.dbo.sp_verify_subsystems
  IF @retval <> 0
     RETURN(@retval)

  -- Remove any leading/trailing spaces from parameters
  SELECT @subsystem = LTRIM(RTRIM(@subsystem))

  -- Make sure Dts is translated into new subsystem's name SSIS
  IF (@subsystem IS NOT NULL AND UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = N'DTS')
  BEGIN
    SET @subsystem = N'SSIS'
  END

  IF EXISTS (SELECT * FROM syssubsystems
          WHERE  UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) =
                 UPPER(subsystem collate SQL_Latin1_General_CP1_CS_AS))
    RETURN(0) -- Success
  ELSE
  BEGIN
    RAISERROR(14234, -1, -1, '@subsystem', 'sp_enum_sqlagent_subsystems')
    RETURN(1) -- Failure
  END
END
go

/**************************************************************/
/* SP_VERIFY_SCHEDULE                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_schedule...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_schedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_schedule
go
CREATE PROCEDURE sp_verify_schedule
  @schedule_id            INT,
  @name                   sysname,
  @enabled                TINYINT,
  @freq_type              INT,
  @freq_interval          INT OUTPUT,   -- Output because we may set it to 0 if Frequency Type is one-time or auto-start
  @freq_subday_type       INT OUTPUT,   -- As above
  @freq_subday_interval   INT OUTPUT,   -- As above
  @freq_relative_interval INT OUTPUT,   -- As above
  @freq_recurrence_factor INT OUTPUT,   -- As above
  @active_start_date      INT OUTPUT,
  @active_start_time      INT OUTPUT,
  @active_end_date        INT OUTPUT,
  @active_end_time        INT OUTPUT,
  @owner_sid              VARBINARY(85) --Must be a valid sid. Will fail if this is NULL
AS
BEGIN
  DECLARE @return_code             INT
  DECLARE @res_valid_range         NVARCHAR(100)
  DECLARE @reason                  NVARCHAR(200)
  DECLARE @isAdmin                 INT

  SET NOCOUNT ON

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (SERVERPROPERTY('EngineEdition') = 8 AND @freq_type = 0x80)
  BEGIN
    RAISERROR(41914, -1, 3, 'Schedule job ONIDLE')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @name = LTRIM(RTRIM(@name))

  -- Make sure that NULL input/output parameters - if NULL - are initialized to 0
  SELECT @freq_interval          = ISNULL(@freq_interval, 0)
  SELECT @freq_subday_type       = ISNULL(@freq_subday_type, 0)
  SELECT @freq_subday_interval   = ISNULL(@freq_subday_interval, 0)
  SELECT @freq_relative_interval = ISNULL(@freq_relative_interval, 0)
  SELECT @freq_recurrence_factor = ISNULL(@freq_recurrence_factor, 0)
  SELECT @active_start_date      = ISNULL(@active_start_date, 0)
  SELECT @active_start_time      = ISNULL(@active_start_time, 0)
  SELECT @active_end_date        = ISNULL(@active_end_date, 0)
  SELECT @active_end_time        = ISNULL(@active_end_time, 0)


  -- Check owner
  IF(ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
    SELECT @isAdmin = 1
  ELSE
    SELECT @isAdmin = 0


  -- If a non-sa is [illegally] trying to create a schedule for another user then raise an error
  IF ((@isAdmin <> 1) AND
      (ISNULL(IS_MEMBER('SQLAgentOperatorRole'),0) <> 1 AND @schedule_id IS NULL) AND
      (@owner_sid <> SUSER_SID()))
  BEGIN
     RAISERROR(14366, -1, -1)
     RETURN(1) -- Failure
  END


  -- Now just check that the login id is valid (ie. it exists and isn't an NT group)
  IF (@owner_sid <> 0x010100000000000512000000) AND -- NT AUTHORITY\SYSTEM sid
     (@owner_sid <> 0x010100000000000514000000)     -- NT AUTHORITY\NETWORK SERVICE sid
  BEGIN
     IF (@owner_sid IS NULL) OR (EXISTS (SELECT *
                                      FROM master.dbo.syslogins
                                      WHERE (sid = @owner_sid)
                                      AND (isntgroup <> 0)))
     BEGIN
       -- NOTE: In the following message we quote @owner_login_name instead of @owner_sid
       --       since this is the parameter the user passed to the calling SP (ie. either
       --       sp_add_schedule, sp_add_job and sp_update_job)
       SELECT @res_valid_range = FORMATMESSAGE(14203)
       RAISERROR(14234, -1, -1, '@owner_login_name', @res_valid_range)
       RETURN(1) -- Failure
     END
  END

  -- Verify name (we disallow schedules called 'ALL' since this has special meaning in sp_delete_jobschedules)
  IF (UPPER(@name collate SQL_Latin1_General_CP1_CS_AS) = N'ALL')
  BEGIN
    RAISERROR(14200, -1, -1, '@name')
    RETURN(1) -- Failure
  END

  -- Verify enabled state
  IF (@enabled <> 0) AND (@enabled <> 1)
  BEGIN
    RAISERROR(14266, -1, -1, '@enabled', '0, 1')
    RETURN(1) -- Failure
  END

  -- Verify frequency type
  IF (@freq_type = 0x2) -- OnDemand is no longer supported
  BEGIN
    RAISERROR(14295, -1, -1)
    RETURN(1) -- Failure
  END
  IF (@freq_type NOT IN (0x1, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80))
  BEGIN
    RAISERROR(14266, -1, -1, '@freq_type', '1, 4, 8, 16, 32, 64, 128')
    RETURN(1) -- Failure
  END

  -- Verify frequency sub-day type
  IF (@freq_subday_type <> 0) AND (@freq_subday_type NOT IN (0x1, 0x2, 0x4, 0x8))
  BEGIN
    RAISERROR(14266, -1, -1, '@freq_subday_type', '0x1, 0x2, 0x4, 0x8')
    RETURN(1) -- Failure
  END

  -- Default active start/end date/times (if not supplied, or supplied as NULLs or 0)
  IF (@active_start_date = 0)
    SELECT @active_start_date = DATEPART(yy, GETDATE()) * 10000 +
                                DATEPART(mm, GETDATE()) * 100 +
                                DATEPART(dd, GETDATE()) -- This is an ISO format: "yyyymmdd"
  IF (@active_end_date = 0)
    SELECT @active_end_date = 99991231  -- December 31st 9999
  IF (@active_start_time = 0)
    SELECT @active_start_time = 000000  -- 12:00:00 am
  IF (@active_end_time = 0)
    SELECT @active_end_time = 235959    -- 11:59:59 pm

  -- Verify active start/end dates
  IF (@active_end_date = 0)
    SELECT @active_end_date = 99991231

  EXECUTE @return_code = sp_verify_job_date @active_end_date, '@active_end_date'
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  EXECUTE @return_code = sp_verify_job_date @active_start_date, '@active_start_date'
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  IF (@active_end_date < @active_start_date)
  BEGIN
    RAISERROR(14288, -1, -1, '@active_end_date', '@active_start_date')
    RETURN(1) -- Failure
  END

  EXECUTE @return_code = sp_verify_job_time @active_end_time, '@active_end_time'
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  EXECUTE @return_code = sp_verify_job_time @active_start_time, '@active_start_time'
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  -- NOTE: It's valid for active_end_time to be less than active_start_time since in this
  --       case we assume that the user wants the active time zone to span midnight.
  --       But it's not valid for active_start_date and active_end_date to be the same for recurring sec/hour/minute schedules

  IF (@active_start_time = @active_end_time and (@freq_subday_type in (0x2, 0x4, 0x8)))
  BEGIN
    SELECT @res_valid_range = FORMATMESSAGE(14202)
    RAISERROR(14266, -1, -1, '@active_end_time', @res_valid_range)
    RETURN(1) -- Failure
  END

  -- NOTE: The rest of this procedure is a SQL implementation of VerifySchedule in job.c

  IF ((@freq_type = 0x1) OR  -- FREQTYPE_ONETIME
     (@freq_type = 0x40) OR -- FREQTYPE_AUTOSTART
     (@freq_type = 0x80))   -- FREQTYPE_ONIDLE
  BEGIN
    -- Set standard defaults for non-required parameters
    SELECT @freq_interval          = 0
    SELECT @freq_subday_type       = 0
    SELECT @freq_subday_interval   = 0
    SELECT @freq_relative_interval = 0
    SELECT @freq_recurrence_factor = 0

    -- Check that a one-time schedule isn't already in the past
    -- Bug 442883: let the creation of the one-time schedule succeed but leave a disabled schedule
    /*
    IF (@freq_type = 0x1) -- FREQTYPE_ONETIME
    BEGIN
      DECLARE @current_date INT
      DECLARE @current_time INT

      -- This is an ISO format: "yyyymmdd"
      SELECT @current_date = CONVERT(INT, CONVERT(VARCHAR, GETDATE(), 112))
      SELECT @current_time = (DATEPART(hh, GETDATE()) * 10000) + (DATEPART(mi, GETDATE()) * 100) + DATEPART(ss, GETDATE())
      IF (@active_start_date < @current_date) OR ((@active_start_date = @current_date) AND (@active_start_time <= @current_time))
      BEGIN
        SELECT @res_valid_range = '> ' + CONVERT(VARCHAR, @current_date) + ' / ' + CONVERT(VARCHAR, @current_time)
        SELECT @reason = '@active_start_date = ' + CONVERT(VARCHAR, @active_start_date) + ' / @active_start_time = ' + CONVERT(VARCHAR, @active_start_time)
        RAISERROR(14266, -1, -1, @reason, @res_valid_range)
        RETURN(1) -- Failure
      END
    END
    */

    GOTO ExitProc
  END

  -- Safety net: If the sub-day-type is 0 (and we know that the schedule is not a one-time or
  --             auto-start) then set it to 1 (FREQSUBTYPE_ONCE).  If the user wanted something
  --             other than ONCE then they should have explicitly set @freq_subday_type.
  IF (@freq_subday_type = 0)
    SELECT @freq_subday_type = 0x1 -- FREQSUBTYPE_ONCE

  IF ((@freq_subday_type <> 0x1) AND  -- FREQSUBTYPE_ONCE   (see qsched.h)
      (@freq_subday_type <> 0x2) AND  -- FREQSUBTYPE_SECOND (see qsched.h)
      (@freq_subday_type <> 0x4) AND  -- FREQSUBTYPE_MINUTE (see qsched.h)
      (@freq_subday_type <> 0x8))     -- FREQSUBTYPE_HOUR   (see qsched.h)
  BEGIN
    SELECT @reason = FORMATMESSAGE(14266, '@freq_subday_type', '0x1, 0x2, 0x4, 0x8')
    RAISERROR(14278, -1, -1, @reason)
    RETURN(1) -- Failure
  END

  IF ((@freq_subday_type <> 0x1) AND (@freq_subday_interval < 1)) -- FREQSUBTYPE_ONCE and less than 1 interval
     OR
     ((@freq_subday_type = 0x2) AND (@freq_subday_interval < 10)) -- FREQSUBTYPE_SECOND and less than 10 seconds (see MIN_SCHEDULE_GRANULARITY in SqlAgent source code)
  BEGIN
    SELECT @reason = FORMATMESSAGE(14200, '@freq_subday_interval')
    RAISERROR(14278, -1, -1, @reason)
    RETURN(1) -- Failure
  END

  IF (@freq_type = 0x4)      -- FREQTYPE_DAILY
  BEGIN
    SELECT @freq_recurrence_factor = 0
    IF (@freq_interval < 1)
    BEGIN
      SELECT @reason = FORMATMESSAGE(14572)
      RAISERROR(14278, -1, -1, @reason)
      RETURN(1) -- Failure
    END
  END

  IF (@freq_type = 0x8)      -- FREQTYPE_WEEKLY
  BEGIN
    IF (@freq_interval < 1)   OR
       (@freq_interval > 127) -- (2^7)-1 [freq_interval is a bitmap (Sun=1..Sat=64)]
    BEGIN
      SELECT @reason = FORMATMESSAGE(14573)
      RAISERROR(14278, -1, -1, @reason)
      RETURN(1) -- Failure
    END

  END

  IF (@freq_type = 0x10)    -- FREQTYPE_MONTHLY
  BEGIN
    IF (@freq_interval < 1)  OR
       (@freq_interval > 31)
    BEGIN
      SELECT @reason = FORMATMESSAGE(14574)
      RAISERROR(14278, -1, -1, @reason)
      RETURN(1) -- Failure
    END

  END

  IF (@freq_type = 0x20)     -- FREQTYPE_MONTHLYRELATIVE
  BEGIN
    IF (@freq_relative_interval <> 0x01) AND  -- RELINT_1ST
       (@freq_relative_interval <> 0x02) AND  -- RELINT_2ND
       (@freq_relative_interval <> 0x04) AND  -- RELINT_3RD
       (@freq_relative_interval <> 0x08) AND  -- RELINT_4TH
       (@freq_relative_interval <> 0x10)      -- RELINT_LAST
    BEGIN
      SELECT @reason = FORMATMESSAGE(14575)
      RAISERROR(14278, -1, -1, @reason)
      RETURN(1) -- Failure
    END
  END

  IF (@freq_type = 0x20)     -- FREQTYPE_MONTHLYRELATIVE
  BEGIN
    IF (@freq_interval <> 01) AND -- RELATIVE_SUN
       (@freq_interval <> 02) AND -- RELATIVE_MON
       (@freq_interval <> 03) AND -- RELATIVE_TUE
       (@freq_interval <> 04) AND -- RELATIVE_WED
       (@freq_interval <> 05) AND -- RELATIVE_THU
       (@freq_interval <> 06) AND -- RELATIVE_FRI
       (@freq_interval <> 07) AND -- RELATIVE_SAT
       (@freq_interval <> 08) AND -- RELATIVE_DAY
       (@freq_interval <> 09) AND -- RELATIVE_WEEKDAY
       (@freq_interval <> 10)     -- RELATIVE_WEEKENDDAY
    BEGIN
      SELECT @reason = FORMATMESSAGE(14576)
      RAISERROR(14278, -1, -1, @reason)
      RETURN(1) -- Failure
    END
  END

  IF ((@freq_type = 0x08)  OR   -- FREQTYPE_WEEKLY
      (@freq_type = 0x10)  OR   -- FREQTYPE_MONTHLY
      (@freq_type = 0x20)) AND  -- FREQTYPE_MONTHLYRELATIVE
      (@freq_recurrence_factor < 1)
  BEGIN
    SELECT @reason = FORMATMESSAGE(14577)
    RAISERROR(14278, -1, -1, @reason)
    RETURN(1) -- Failure
  END

ExitProc:
  -- If we made it this far the schedule is good
  RETURN(0) -- Success

END
go



/**************************************************************/
/* SP_ADD_SCHEDULE                                            */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_add_schedule...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_schedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_schedule
go

CREATE PROCEDURE sp_add_schedule
(
  @schedule_name        sysname,
  @enabled              TINYINT         = 1,            -- Name does not have to be unique
  @freq_type            INT             = 0,
  @freq_interval        INT             = 0,
  @freq_subday_type        INT             = 0,
  @freq_subday_interval    INT             = 0,
  @freq_relative_interval  INT             = 0,
  @freq_recurrence_factor  INT             = 0,
  @active_start_date    INT             = NULL,         -- sp_verify_schedule assigns a default
  @active_end_date         INT             = 99991231,     -- December 31st 9999
  @active_start_time    INT             = 000000,       -- 12:00:00 am
  @active_end_time         INT             = 235959,       -- 11:59:59 pm
  @owner_login_name        sysname         = NULL,
  @schedule_uid             UNIQUEIDENTIFIER= NULL  OUTPUT, -- Used by a TSX machine when inserting a schedule
  @schedule_id              INT             = NULL  OUTPUT,
  @originating_server       sysname        = NULL
)
AS
BEGIN
  DECLARE @retval           INT
  DECLARE @owner_sid        VARBINARY(85)
  DECLARE @orig_server_id   INT

  SET NOCOUNT ON

 -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (SERVERPROPERTY('EngineEdition') = 8 AND @freq_type = 0x80)
  BEGIN
    RAISERROR(41914, -1, 4, 'Schedule job ONIDLE')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @schedule_name         = LTRIM(RTRIM(@schedule_name)),
         @owner_login_name      = LTRIM(RTRIM(@owner_login_name)),
         @originating_server    = UPPER(LTRIM(RTRIM(@originating_server))),
         @schedule_id           = 0


   -- If the owner isn't supplied make if the current user
  IF(@owner_login_name IS NULL OR @owner_login_name = '')
  BEGIN
    --Get the current users sid
    SELECT @owner_sid = SUSER_SID()
  END
  ELSE
  BEGIN
    -- Get the sid for @owner_login_name SID
    --force case insensitive comparation for NT users
    SELECT @owner_sid = dbo.SQLAGENT_SUSER_SID(@owner_login_name)
    -- Cannot proceed if @owner_login_name doesn't exist
    IF(@owner_sid IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@owner_login_name', @owner_login_name)
      RETURN(1) -- Failure
    END
  END

  -- Check schedule (frequency and owner) parameters
  EXECUTE @retval = sp_verify_schedule NULL,   -- schedule_id does not exist for the new schedule
                                       @name                    = @schedule_name,
                                       @enabled                 = @enabled,
                                       @freq_type               = @freq_type,
                                       @freq_interval           = @freq_interval            OUTPUT,
                                       @freq_subday_type        = @freq_subday_type         OUTPUT,
                                       @freq_subday_interval    = @freq_subday_interval     OUTPUT,
                                       @freq_relative_interval  = @freq_relative_interval   OUTPUT,
                                       @freq_recurrence_factor  = @freq_recurrence_factor   OUTPUT,
                                       @active_start_date       = @active_start_date        OUTPUT,
                                       @active_start_time       = @active_start_time        OUTPUT,
                                       @active_end_date         = @active_end_date          OUTPUT,
                                       @active_end_time         = @active_end_time          OUTPUT,
                                       @owner_sid               = @owner_sid
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- ignore @originating_server unless SQLAgent is calling
  if((@originating_server IS NULL) OR (@originating_server = N'') OR (PROGRAM_NAME() NOT LIKE N'SQLAgent%'))
  BEGIN
    --Get the local originating_server_id
    SELECT @orig_server_id = originating_server_id
    FROM msdb.dbo.sysoriginatingservers_view
    WHERE master_server = 0
  END
  ELSE
  BEGIN
    --Get the MSX originating_server_id. If @originating_server isn't the msx server error out
    SELECT @orig_server_id = originating_server_id
    FROM msdb.dbo.sysoriginatingservers_view
    WHERE (originating_server = @originating_server)

    IF (@orig_server_id IS NULL)
    BEGIN
      RAISERROR(14370, -1, -1)
      RETURN(1) -- Failure
    END
  END

  IF (@schedule_uid IS NULL)
  BEGIN
    -- Assign the GUID
    SELECT @schedule_uid = NEWID()
  END
  ELSE IF (@schedule_uid <> CONVERT(UNIQUEIDENTIFIER, 0x00))
  BEGIN
    --Try and find the schedule if a @schedule_uid is provided.
    --A TSX server uses the @schedule_uid to identify a schedule downloaded from the MSX
   SELECT @schedule_id = schedule_id
    FROM msdb.dbo.sysschedules
    WHERE schedule_uid = @schedule_uid

   IF((@schedule_id IS NOT NULL) AND (@schedule_id <> 0))
   BEGIN
      --If found update the fields
      UPDATE msdb.dbo.sysschedules
        SET name              = ISNULL(@schedule_name, name),
            enabled              = ISNULL(@enabled, enabled),
         freq_type            = ISNULL(@freq_type, freq_type),
         freq_interval        = ISNULL(@freq_interval, freq_interval),
         freq_subday_type     = ISNULL(@freq_subday_type, freq_subday_type),
         freq_subday_interval = ISNULL(@freq_subday_interval, freq_subday_interval),
         freq_relative_interval  = ISNULL(@freq_relative_interval, freq_relative_interval),
         freq_recurrence_factor  = ISNULL(@freq_recurrence_factor, freq_recurrence_factor),
         active_start_date    = ISNULL(@active_start_date, active_start_date),
         active_end_date         = ISNULL(@active_end_date, active_end_date),
         active_start_time    = ISNULL(@active_start_time, active_start_time),
         active_end_time         = ISNULL(@active_end_time, active_end_time)
      WHERE schedule_uid = @schedule_uid

      RETURN(@@ERROR)
   END
  END

  --MSX not found so add a record to sysschedules
  INSERT INTO msdb.dbo.sysschedules
         (schedule_uid,
          originating_server_id,
          name,
          owner_sid,
          enabled,
          freq_type,
          freq_interval,
          freq_subday_type,
          freq_subday_interval,
          freq_relative_interval,
          freq_recurrence_factor,
          active_start_date,
          active_end_date,
          active_start_time,
          active_end_time)
  select @schedule_uid,
         @orig_server_id,
         @schedule_name,
         @owner_sid,
         @enabled,
         @freq_type,
         @freq_interval,
         @freq_subday_type,
         @freq_subday_interval,
         @freq_relative_interval,
         @freq_recurrence_factor,
         @active_start_date,
         @active_end_date,
         @active_start_time,
         @active_end_time

  SELECT @retval = @@ERROR,
         @schedule_id = @@IDENTITY

  RETURN(@retval) -- 0 means success
END
GO


/**************************************************************/
/* SP_ATTACH_SCHEDULE                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_attach_schedule ...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_attach_schedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_attach_schedule
go

CREATE PROCEDURE sp_attach_schedule
(
  @job_id               UNIQUEIDENTIFIER    = NULL,     -- Must provide either this or job_name
  @job_name             sysname             = NULL,     -- Must provide either this or job_id
  @schedule_id          INT                 = NULL,     -- Must provide either this or schedule_name
  @schedule_name        sysname             = NULL,     -- Must provide either this or schedule_id
  @automatic_post       BIT                 = 1         -- If 1 will post notifications to all tsx servers to that run this job
)
AS
BEGIN
  DECLARE @retval           INT
  DECLARE @sched_owner_sid  VARBINARY(85)
  DECLARE @job_owner_sid    VARBINARY(85)


  SET NOCOUNT ON

  -- Check that we can uniquely identify the job
  EXECUTE @retval = msdb.dbo.sp_verify_job_identifiers '@job_name',
                                                       '@job_id',
                                                        @job_name                   OUTPUT,
                                                        @job_id                     OUTPUT,
                                                        @owner_sid = @job_owner_sid OUTPUT
    IF (@retval <> 0)
        RETURN(1) -- Failure

  -- Check authority (only SQLServerAgent can add a schedule to a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Check that we can uniquely identify the schedule
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @schedule_name    OUTPUT,
                                                            @schedule_id            = @schedule_id      OUTPUT,
                                                            @owner_sid              = @sched_owner_sid  OUTPUT,
                                                            @orig_server_id         = NULL
  IF (@retval <> 0)
      RETURN(1) -- Failure

  --Schedules can only be attached to a job if the caller owns the job
  --or the caller is a sysadmin
  IF ((@job_owner_sid <> SUSER_SID()) AND
      (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
     RAISERROR(14377, -1, -1)
     RETURN(1) -- Failure
  END

  -- If the record doesn't already exist create it
  IF( NOT EXISTS(SELECT *
                 FROM msdb.dbo.sysjobschedules
                 WHERE (schedule_id = @schedule_id)
                   AND (job_id = @job_id)) )
  BEGIN
    INSERT INTO msdb.dbo.sysjobschedules (schedule_id, job_id)
    SELECT @schedule_id, @job_id

    SELECT @retval = @@ERROR

    -- Notify SQLServerAgent of the change, but only if we know the job has been cached
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                    AND (server_id = 0)))
    BEGIN
        EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                            @job_id      = @job_id,
                                            @schedule_id = @schedule_id,
                                            @action_type = N'I'
    END

    -- For a multi-server job, remind the user that they need to call sp_post_msx_operation
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                    AND (server_id <> 0)))
      -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines
      IF (@automatic_post = 1)
        EXECUTE sp_post_msx_operation @operation = 'INSERT', @object_type = 'JOB', @job_id = @job_id
      ELSE
        RAISERROR(14547, 0, 1, N'INSERT', N'sp_post_msx_operation')

    -- update this job's subplan to point to this schedule
    UPDATE msdb.dbo.sysmaintplan_subplans
      SET schedule_id = @schedule_id
    WHERE (job_id = @job_id)
      AND (schedule_id IS NULL)
  END

  RETURN(@retval) -- 0 means success
END
GO


/**************************************************************/
/* SP_DETACH_SCHEDULE                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_detach_schedule ...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_detach_schedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_detach_schedule
go

CREATE PROCEDURE sp_detach_schedule
(
  @job_id               UNIQUEIDENTIFIER    = NULL,     -- Must provide either this or job_name
  @job_name             sysname             = NULL,     -- Must provide either this or job_id
  @schedule_id          INT                 = NULL,     -- Must provide either this or schedule_name
  @schedule_name        sysname             = NULL,     -- Must provide either this or schedule_id
  @delete_unused_schedule BIT               = 0,        -- Can optionally delete schedule if it isn't referenced.
                                                        -- The default is to keep schedules
  @automatic_post       BIT                 = 1         -- If 1 will post notifications to all tsx servers to that run this job
)
AS
BEGIN
  DECLARE @retval   INT
  DECLARE @sched_owner_sid VARBINARY(85)
  DECLARE @job_owner_sid    VARBINARY(85)

  SET NOCOUNT ON

  -- Check that we can uniquely identify the job
  EXECUTE @retval = msdb.dbo.sp_verify_job_identifiers '@job_name',
                                                       '@job_id',
                                                        @job_name OUTPUT,
                                                        @job_id   OUTPUT,
                                                        @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check authority (only SQLServerAgent can add a schedule to a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Check that we can uniquely identify the schedule
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @schedule_name OUTPUT,
                                                            @schedule_id            = @schedule_id   OUTPUT,
                                                            @owner_sid              = @sched_owner_sid OUTPUT,
                                                            @orig_server_id         = NULL,
                                                            @job_id_filter          = @job_id
  IF (@retval <> 0)
      RETURN(1) -- Failure

  -- If the record doesn't exist raise an error
  IF( NOT EXISTS(SELECT *
                 FROM msdb.dbo.sysjobschedules
                 WHERE (schedule_id = @schedule_id)
                   AND (job_id = @job_id)) )
  BEGIN
    RAISERROR(14374, 0, 1, @schedule_name, @job_name)
    RETURN(1) -- Failure
  END
  ELSE
  BEGIN

   -- Permissions check:
   --  If sysadmin continue (sysadmin can detach schedules they don't own)
   --  Otherwise if the caller owns the job, we can detach it
   --  Except If @delete_unused_schedule = 1 then the caller has to own both the job and the schedule
   IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
   BEGIN
    IF (@job_owner_sid = SUSER_SID())
    BEGIN
      IF ((@delete_unused_schedule = 1) AND (@sched_owner_sid <> SUSER_SID()))
      BEGIN
        -- Cannot delete the schedule
        RAISERROR(14394, -1, -1)
        RETURN(1) -- Failure
      END
    END
    ELSE -- the caller is not sysadmin and it does not own the job -> throw
    BEGIN
      RAISERROR(14391, -1, -1)
      RETURN(1) -- Failure
    END
   END

    DELETE FROM msdb.dbo.sysjobschedules
    WHERE (job_id = @job_id)
      AND (schedule_id = @schedule_id)

    SELECT @retval = @@ERROR

    --delete the schedule if requested and it isn't referenced
    IF(@retval = 0 AND @delete_unused_schedule = 1)
    BEGIN
        IF(NOT EXISTS(SELECT *
                      FROM msdb.dbo.sysjobschedules
                      WHERE (schedule_id = @schedule_id)))
        BEGIN
            DELETE FROM msdb.dbo.sysschedules
            WHERE (schedule_id = @schedule_id)
        END
    END

    -- Update the job's version/last-modified information
    UPDATE msdb.dbo.sysjobs
    SET version_number = version_number + 1,
        date_modified = GETDATE()
    WHERE (job_id = @job_id)

    -- Notify SQLServerAgent of the change, but only if we know the job has been cached
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                    AND (server_id = 0)))
    BEGIN
        EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                            @job_id      = @job_id,
                                            @schedule_id = @schedule_id,
                                            @action_type = N'D'
    END

    -- For a multi-server job, remind the user that they need to call sp_post_msx_operation
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                    AND (server_id <> 0)))
      -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines
      IF (@automatic_post = 1)
        EXECUTE sp_post_msx_operation @operation = 'INSERT', @object_type = 'JOB', @job_id = @job_id
      ELSE
        RAISERROR(14547, 0, 1, N'INSERT', N'sp_post_msx_operation')

    -- set this job's subplan to the first schedule in sysjobschedules or NULL if there is none
    UPDATE msdb.dbo.sysmaintplan_subplans
    SET schedule_id = (    SELECT TOP(1) schedule_id
                        FROM msdb.dbo.sysjobschedules
                        WHERE (job_id = @job_id) )
    WHERE (job_id = @job_id)
      AND (schedule_id = @schedule_id)
  END

  RETURN(@retval) -- 0 means success
END
GO

/**************************************************************/
/* SP_UPDATE_REPLICATION_JOB_PARAMETER                        */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_replication_job_parameter...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_update_replication_job_parameter')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_replication_job_parameter
go
CREATE PROCEDURE sp_update_replication_job_parameter
  @job_id        UNIQUEIDENTIFIER,
  @old_freq_type INT,
  @new_freq_type INT
AS
BEGIN
  DECLARE @category_id INT
  DECLARE @pattern     NVARCHAR(50)
  DECLARE @patternidx  INT
  DECLARE @cmdline     NVARCHAR(3200)
  DECLARE @step_id     INT

  SET NOCOUNT ON
  SELECT @pattern = N'%[-/][Cc][Oo][Nn][Tt][Ii][Nn][Uu][Oo][Uu][Ss]%'

  -- Make sure that we are dealing with relevant replication jobs
  SELECT @category_id = category_id
  FROM msdb.dbo.sysjobs
  WHERE (@job_id = job_id)

  -- @category_id = 10 (REPL-Distribution), 13 (REPL-LogReader), 14 (REPL-Merge),
  --  19 (REPL-QueueReader)
  IF @category_id IN (10, 13, 14, 19)
  BEGIN
    -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
    IF (SERVERPROPERTY('EngineEdition') = 8 AND @new_freq_type = 0x80)
    BEGIN
      RAISERROR(41914, -1, 18, 'Schedule job ONIDLE')
      RETURN(1) -- Failure
    END
    -- Adding the -Continuous parameter (non auto-start to auto-start)
    IF ((@old_freq_type <> 0x40) AND (@new_freq_type = 0x40))
    BEGIN
      -- Use a cursor to handle multiple replication agent job steps
      DECLARE step_cursor CURSOR LOCAL FOR
      SELECT command, step_id
      FROM msdb.dbo.sysjobsteps
      WHERE (@job_id = job_id)
        AND (UPPER(subsystem collate SQL_Latin1_General_CP1_CS_AS) IN (N'MERGE', N'LOGREADER', N'DISTRIBUTION', N'QUEUEREADER'))
      OPEN step_cursor
      FETCH step_cursor INTO @cmdline, @step_id

      WHILE (@@FETCH_STATUS <> -1)
      BEGIN
        SELECT @patternidx = PATINDEX(@pattern, @cmdline)
        -- Make sure that the -Continuous parameter has not been specified already
        IF (@patternidx = 0)
        BEGIN
          SELECT @cmdline = @cmdline + N' -Continuous'
          UPDATE msdb.dbo.sysjobsteps
          SET command = @cmdline
          WHERE (@job_id = job_id)
            AND (@step_id = step_id)
        END -- IF (@patternidx = 0)
        FETCH NEXT FROM step_cursor into @cmdline, @step_id
      END -- WHILE (@@FETCH_STATUS <> -1)
      CLOSE step_cursor
      DEALLOCATE step_cursor
    END -- IF ((@old_freq_type...
    -- Removing the -Continuous parameter (auto-start to non auto-start)
    ELSE
    IF ((@old_freq_type = 0x40) AND (@new_freq_type <> 0x40))
    BEGIN
      DECLARE step_cursor CURSOR LOCAL FOR
      SELECT command, step_id
      FROM msdb.dbo.sysjobsteps
      WHERE (@job_id = job_id)
        AND (UPPER(subsystem collate SQL_Latin1_General_CP1_CS_AS) IN (N'MERGE', N'LOGREADER', N'DISTRIBUTION', N'QUEUEREADER'))
      OPEN step_cursor
      FETCH step_cursor INTO @cmdline, @step_id

      WHILE (@@FETCH_STATUS <> -1)
      BEGIN
        SELECT @patternidx = PATINDEX(@pattern, @cmdline)
        IF (@patternidx <> 0)
        BEGIN
          -- Handle multiple instances of -Continuous in the commandline
          WHILE (@patternidx <> 0)
          BEGIN
            SELECT @cmdline = STUFF(@cmdline, @patternidx, 11, N'')
            IF (@patternidx > 1)
            BEGIN
              -- Remove the preceding space if -Continuous does not start at the beginning of the commandline
              SELECT @cmdline = stuff(@cmdline, @patternidx - 1, 1, N'')
            END
            SELECT @patternidx = PATINDEX(@pattern, @cmdline)
          END -- WHILE (@patternidx <> 0)
          UPDATE msdb.dbo.sysjobsteps
          SET command = @cmdline
          WHERE (@job_id = job_id)
            AND (@step_id = step_id)
        END -- IF (@patternidx <> -1)
        FETCH NEXT FROM step_cursor INTO @cmdline, @step_id
      END -- WHILE (@@FETCH_STATUS <> -1)
      CLOSE step_cursor
      DEALLOCATE step_cursor
    END -- ELSE IF ((@old_freq_type = 0x40)...
  END -- IF @category_id IN (10, 13, 14)

  RETURN 0
END
go

/**************************************************************/
/* SP_UPDATE_SCHEDULE                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_update_schedule ...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_schedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_schedule
go

CREATE PROCEDURE sp_update_schedule
(
  @schedule_id              INT             = NULL,     -- Must provide either this or schedule_name
  @name                     sysname         = NULL,     -- Must provide either this or schedule_id
  @new_name                 sysname         = NULL,
  @enabled                  TINYINT         = NULL,
  @freq_type                INT             = NULL,
  @freq_interval            INT             = NULL,
  @freq_subday_type         INT             = NULL,
  @freq_subday_interval     INT             = NULL,
  @freq_relative_interval   INT             = NULL,
  @freq_recurrence_factor   INT             = NULL,
  @active_start_date        INT             = NULL,
  @active_end_date          INT             = NULL,
  @active_start_time        INT             = NULL,
  @active_end_time          INT             = NULL,
  @owner_login_name         sysname         = NULL,
  @automatic_post           BIT             = 1         -- If 1 will post notifications to all tsx servers to
                                                        -- update all jobs that use this schedule
)
AS
BEGIN
  DECLARE @retval                   INT
  DECLARE @owner_sid                VARBINARY(85)
  DECLARE @cur_owner_sid            VARBINARY(85)
  DECLARE @x_name                   sysname
  DECLARE @enable_only_used         INT

  DECLARE @x_enabled                TINYINT
  DECLARE @x_freq_type              INT
  DECLARE @x_freq_interval          INT
  DECLARE @x_freq_subday_type       INT
  DECLARE @x_freq_subday_interval   INT
  DECLARE @x_freq_relative_interval INT
  DECLARE @x_freq_recurrence_factor INT
  DECLARE @x_active_start_date      INT
  DECLARE @x_active_end_date        INT
  DECLARE @x_active_start_time      INT
  DECLARE @x_active_end_time        INT
  DECLARE @schedule_uid             UNIQUEIDENTIFIER

  SET NOCOUNT ON

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (SERVERPROPERTY('EngineEdition') = 8 AND @freq_type = 0x80)
  BEGIN
    RAISERROR(41914, -1, 5, 'Schedule job ONIDLE')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @name              = LTRIM(RTRIM(@name))
  SELECT @new_name          = LTRIM(RTRIM(@new_name))
  SELECT @owner_login_name  = LTRIM(RTRIM(@owner_login_name))
  -- Turn [nullable] empty string parameters into NULLs
  IF (@new_name = N'') SELECT @new_name = NULL

   -- If the owner is supplied get the sid and check it
  IF(@owner_login_name IS NOT NULL AND @owner_login_name <> '')
  BEGIN
      -- Get the sid for @owner_login_name SID
      --force case insensitive comparation for NT users
      SELECT @owner_sid = dbo.SQLAGENT_SUSER_SID(@owner_login_name)
    -- Cannot proceed if @owner_login_name doesn't exist
    IF(@owner_sid IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@owner_login_name', @owner_login_name)
      RETURN(1) -- Failure
    END
  END

  -- Check that we can uniquely identify the schedule. This only returns a schedule that is visible to this user
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @name             OUTPUT,
                                                            @schedule_id            = @schedule_id      OUTPUT,
                                                            @owner_sid              = @cur_owner_sid    OUTPUT,
                                                            @orig_server_id         = NULL
  IF (@retval <> 0)
      RETURN(1) -- Failure

  -- Is @enable the only parameter used beside jobname and jobid?
  IF ((@enabled                   IS NOT NULL) AND
       (@new_name                 IS NULL) AND
      (@freq_type                 IS NULL) AND
      (@freq_interval             IS NULL) AND
      (@freq_subday_type          IS NULL) AND
      (@freq_subday_interval      IS NULL) AND
      (@freq_relative_interval    IS NULL) AND
      (@freq_recurrence_factor    IS NULL) AND
      (@active_start_date         IS NULL) AND
      (@active_end_date           IS NULL) AND
      (@active_start_time         IS NULL) AND
      (@active_end_time           IS NULL) AND
      (@owner_login_name          IS NULL))
    SELECT @enable_only_used = 1
  ELSE
    SELECT @enable_only_used = 0

  -- Non-sysadmins can only update jobs schedules they own.
  -- Members of SQLAgentReaderRole and SQLAgentOperatorRole can view job schedules,
  -- but they should not be able to delete them
  IF ((@cur_owner_sid <> SUSER_SID())
       AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'),0) <> 1)
      AND (@enable_only_used <> 1 OR ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) <> 1))
  BEGIN
   RAISERROR(14394, -1, -1)
   RETURN(1) -- Failure
  END

  -- If the param @owner_login_name is null or doesn't get resolved by SUSER_SID() set it to the current owner of the schedule
  if(@owner_sid IS NULL)
      SELECT @owner_sid = @cur_owner_sid

   -- Set the x_ (existing) variables
  SELECT @x_name                   = name,
         @x_enabled                = enabled,
         @x_freq_type              = freq_type,
         @x_freq_interval          = freq_interval,
         @x_freq_subday_type       = freq_subday_type,
         @x_freq_subday_interval   = freq_subday_interval,
         @x_freq_relative_interval = freq_relative_interval,
         @x_freq_recurrence_factor = freq_recurrence_factor,
         @x_active_start_date      = active_start_date,
         @x_active_end_date        = active_end_date,
         @x_active_start_time      = active_start_time,
         @x_active_end_time        = active_end_time
  FROM msdb.dbo.sysschedules
  WHERE (schedule_id = @schedule_id )


    -- Fill out the values for all non-supplied parameters from the existing values
  IF (@new_name               IS NULL) SELECT @new_name               = @x_name
  IF (@enabled                IS NULL) SELECT @enabled                = @x_enabled
  IF (@freq_type              IS NULL) SELECT @freq_type              = @x_freq_type
  IF (@freq_interval          IS NULL) SELECT @freq_interval          = @x_freq_interval
  IF (@freq_subday_type       IS NULL) SELECT @freq_subday_type       = @x_freq_subday_type
  IF (@freq_subday_interval   IS NULL) SELECT @freq_subday_interval   = @x_freq_subday_interval
  IF (@freq_relative_interval IS NULL) SELECT @freq_relative_interval = @x_freq_relative_interval
  IF (@freq_recurrence_factor IS NULL) SELECT @freq_recurrence_factor = @x_freq_recurrence_factor
  IF (@active_start_date      IS NULL) SELECT @active_start_date      = @x_active_start_date
  IF (@active_end_date        IS NULL) SELECT @active_end_date        = @x_active_end_date
  IF (@active_start_time      IS NULL) SELECT @active_start_time      = @x_active_start_time
  IF (@active_end_time        IS NULL) SELECT @active_end_time        = @x_active_end_time

  -- Check schedule (frequency and owner) parameters
  EXECUTE @retval = sp_verify_schedule @schedule_id             = @schedule_id,
                                       @name                    = @new_name,
                                       @enabled                 = @enabled,
                                       @freq_type               = @freq_type,
                                       @freq_interval           = @freq_interval            OUTPUT,
                                       @freq_subday_type        = @freq_subday_type         OUTPUT,
                                       @freq_subday_interval    = @freq_subday_interval     OUTPUT,
                                       @freq_relative_interval  = @freq_relative_interval   OUTPUT,
                                       @freq_recurrence_factor  = @freq_recurrence_factor   OUTPUT,
                                       @active_start_date       = @active_start_date        OUTPUT,
                                       @active_start_time       = @active_start_time        OUTPUT,
                                       @active_end_date         = @active_end_date          OUTPUT,
                                       @active_end_time         = @active_end_time          OUTPUT,
                                       @owner_sid               = @owner_sid
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Update the sysschedules table
  UPDATE msdb.dbo.sysschedules
  SET name                   = @new_name,
      owner_sid              = @owner_sid,
      enabled                = @enabled,
      freq_type              = @freq_type,
      freq_interval          = @freq_interval,
      freq_subday_type       = @freq_subday_type,
      freq_subday_interval   = @freq_subday_interval,
      freq_relative_interval = @freq_relative_interval,
      freq_recurrence_factor = @freq_recurrence_factor,
      active_start_date      = @active_start_date,
      active_end_date        = @active_end_date,
      active_start_time      = @active_start_time,
      active_end_time        = @active_end_time,
      date_modified          = GETDATE(),
      version_number         = version_number + 1
  WHERE (schedule_id = @schedule_id)

  SELECT @retval = @@error

 -- update any job that has repl steps
  DECLARE @job_id UNIQUEIDENTIFIER
  DECLARE jobsschedule_cursor CURSOR LOCAL FOR
  SELECT job_id
  FROM msdb.dbo.sysjobschedules
  WHERE (schedule_id = @schedule_id)

  IF @x_freq_type <> @freq_type
  BEGIN
    OPEN jobsschedule_cursor
    FETCH NEXT FROM jobsschedule_cursor INTO @job_id

    WHILE (@@FETCH_STATUS = 0)
    BEGIN
      EXEC  sp_update_replication_job_parameter @job_id = @job_id,
                                                @old_freq_type = @x_freq_type,
                                                @new_freq_type = @freq_type
      FETCH NEXT FROM jobsschedule_cursor INTO @job_id
    END
    CLOSE jobsschedule_cursor
  END
  DEALLOCATE jobsschedule_cursor

  -- Notify SQLServerAgent of the change if this is attached to a local job
  IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobschedules AS jsched
              JOIN msdb.dbo.sysjobservers AS jsvr
                    ON jsched.job_id = jsvr.job_id
                WHERE (jsched.schedule_id = @schedule_id)
                  AND (jsvr.server_id = 0)) )
  BEGIN
      EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                          @schedule_id = @schedule_id,
                                          @action_type = N'U'
  END


  -- Instruct the tsx servers to pick up the altered schedule
  IF (@automatic_post = 1)
  BEGIN
      SELECT @schedule_uid = schedule_uid
      FROM sysschedules
      WHERE schedule_id = @schedule_id

      IF(NOT @schedule_uid IS NULL)
      BEGIN
          -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines
          EXECUTE @retval = sp_post_msx_operation @operation = 'INSERT', @object_type = 'SCHEDULE', @schedule_uid = @schedule_uid
      END
  END

  RETURN(@retval) -- 0 means success
END
GO


/**************************************************************/
/* SP_DELETE_SCHEDULE                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_delete_schedule ...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_schedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_schedule
go

CREATE PROCEDURE sp_delete_schedule
(
  @schedule_id          INT                 = NULL,     -- Must provide either this or schedule_name
  @schedule_name        sysname             = NULL,     -- Must provide either this or schedule_id
  @force_delete         bit                 = 0,
  @automatic_post       BIT                 = 1         -- If 1 will post notifications to all tsx servers to that run this schedule
)
AS
BEGIN
  DECLARE @retval           INT
  DECLARE @owner_sid        VARBINARY(85)
  DECLARE @job_count        INT
  DECLARE @targ_server_id   INT

  SET NOCOUNT ON
  --Get the owners sid
  SELECT @job_count = 0

  -- Check that we can uniquely identify the schedule. This only returns a schedule that is visible to this user
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @schedule_name    OUTPUT,
                                                            @schedule_id            = @schedule_id      OUTPUT,
                                                            @owner_sid              = @owner_sid        OUTPUT,
                                                            @orig_server_id         = NULL
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Non-sysadmins can only update jobs schedules they own.
  -- Members of SQLAgentReaderRole and SQLAgentOperatorRole can view job schedules,
  -- but they should not be able to delete them
  IF ((@owner_sid <> SUSER_SID()) AND
     (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'),0) <> 1))
  BEGIN
   RAISERROR(14394, -1, -1)
   RETURN(1) -- Failure
  END

  --check if there are jobs using this schedule
  SELECT @job_count = count(*)
  FROM sysjobschedules
  WHERE (schedule_id = @schedule_id)

  -- If we aren't force deleting the schedule make sure no jobs are using it
  IF ((@force_delete = 0) AND (@job_count > 0))
  BEGIN
    RAISERROR(14372, -1, -1)
    RETURN (1) -- Failure
  END

  -- Get the one of the terget server_id's.
  -- Getting MIN(jsvr.server_id) works here because we are only interested in this ID
  -- to determine if the schedule ID is for local jobs or MSX jobs.
  -- Note, an MSX job can't be run on the local server
  SELECT @targ_server_id = MIN(jsvr.server_id)
  FROM msdb.dbo.sysjobschedules AS jsched
   JOIN msdb.dbo.sysjobservers AS jsvr
      ON jsched.job_id = jsvr.job_id
  WHERE (jsched.schedule_id = @schedule_id)

  --OK to delete the job - schedule link
  DELETE sysjobschedules
  WHERE schedule_id = @schedule_id

  --OK to delete the schedule
  DELETE sysschedules
  WHERE schedule_id = @schedule_id

  -- @targ_server_id would be null if no jobs use this schedule
  IF (@targ_server_id IS NOT NULL)
  BEGIN
   -- Notify SQLServerAgent of the change but only if it the schedule was used by a local job
   IF (@targ_server_id = 0)
   BEGIN
      -- Only send a notification if the schedule is force deleted. If it isn't force deleted
      -- a notification would have already been sent while detaching the schedule (sp_detach_schedule)
      IF (@force_delete = 1)
      BEGIN
        EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                   @schedule_id = @schedule_id,
                                   @action_type = N'D'
      END
   END
   ELSE
   BEGIN
    -- Instruct the tsx servers to pick up the altered schedule
    IF (@automatic_post = 1)
    BEGIN
      DECLARE @schedule_uid UNIQUEIDENTIFIER
      SELECT @schedule_uid = schedule_uid
      FROM sysschedules
      WHERE schedule_id = @schedule_id

      IF(NOT @schedule_uid IS NULL)
      BEGIN
        -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines
        EXECUTE sp_post_msx_operation @operation = 'INSERT', @object_type = 'SCHEDULE', @schedule_uid = @schedule_uid
      END
    END
    ELSE
      RAISERROR(14547, 0, 1, N'INSERT', N'sp_post_msx_operation')
   END
  END

  RETURN(@retval) -- 0 means success
END
GO



/**************************************************************/
/* SP_GET_JOBSTEP_DB_USERNAME                                 */
/*                                                            */
/* NOTE: For NT login names this procedure can take several   */
/*       seconds to return as it hits the PDC/BDC.            */
/*       SQLServerAgent calls this at runtime.                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_get_jobstep_db_username...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_get_jobstep_db_username')
              AND (type = 'P')))
  DROP PROCEDURE sp_get_jobstep_db_username
go
CREATE PROCEDURE sp_get_jobstep_db_username
  @database_name        sysname,
  @login_name           sysname = NULL,
  @username_in_targetdb sysname OUTPUT
AS
BEGIN
  DECLARE @suser_sid_clause NVARCHAR(512)

  -- Check the database name
  IF (DB_ID(@database_name) IS NULL)
  BEGIN
    RAISERROR(14262, 16, 1, 'database', @database_name)
    RETURN(1) -- Failure
  END

  -- Initialize return value
  SELECT @username_in_targetdb = NULL

  -- Make sure login name is never NULL
  IF (@login_name IS NULL)
    SELECT @login_name = SUSER_SNAME()
  IF (@login_name IS NULL)
    RETURN(1) -- Failure

  -- Handle an NT login name
  IF (@login_name LIKE N'%\%')
  BEGIN
    -- Special case...
    IF (UPPER(@login_name collate SQL_Latin1_General_CP1_CS_AS) = N'NT AUTHORITY\SYSTEM')
      SELECT @username_in_targetdb = N'dbo'
    ELSE
      SELECT @username_in_targetdb = @login_name

    RETURN(0) -- Success
  END

  -- Handle a SQL login name
  SELECT @suser_sid_clause = N'SUSER_SID(N' + QUOTENAME(@login_name, '''') + N')'
  IF (SUSER_SID(@login_name) IS NULL)
    RETURN(1) -- Failure

  DECLARE @quoted_database_name NVARCHAR(258)
  SELECT @quoted_database_name = QUOTENAME(@database_name, N'[')

  DECLARE @temp_username TABLE (user_name sysname COLLATE database_default NOT NULL, is_aliased BIT)

  -- 1) Look for the user name of the current login in the target database
  INSERT INTO @temp_username
  EXECUTE (N'SET NOCOUNT ON
             SELECT name, isaliased
             FROM '+ @quoted_database_name + N'.[dbo].[sysusers]
             WHERE (sid = ' + @suser_sid_clause + N')
               AND (hasdbaccess = 1)')

  -- 2) Look for the alias user name of the current login in the target database
  IF (EXISTS (SELECT *
              FROM @temp_username
              WHERE (is_aliased = 1)))
  BEGIN
    DELETE FROM @temp_username
    INSERT INTO @temp_username
    EXECUTE (N'SET NOCOUNT ON
               SELECT name, 0
               FROM '+ @quoted_database_name + N'.[dbo].[sysusers]
               WHERE uid = (SELECT altuid
                            FROM ' + @quoted_database_name + N'.[dbo].[sysusers]
                            WHERE (sid = ' + @suser_sid_clause + N'))
                 AND (hasdbaccess = 1)')
  END

  -- 3) Look for the guest user name in the target database
  IF (NOT EXISTS (SELECT *
                  FROM @temp_username))
    INSERT INTO @temp_username
    EXECUTE (N'SET NOCOUNT ON
               SELECT name, 0
               FROM '+ @quoted_database_name + N'.[dbo].[sysusers]
               WHERE (name = N''guest'')
                 AND (hasdbaccess = 1)')

  SELECT @username_in_targetdb = user_name
  FROM @temp_username

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_VERIFY_JOBSTEP                                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_jobstep...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_jobstep')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_jobstep
go
CREATE PROCEDURE sp_verify_jobstep
  @job_id             UNIQUEIDENTIFIER,
  @step_id            INT,
  @step_name          sysname,
  @subsystem          NVARCHAR(40),
  @command            NVARCHAR(max),
  @server             sysname,
  @on_success_action  TINYINT,
  @on_success_step_id INT,
  @on_fail_action     TINYINT,
  @on_fail_step_id    INT,
  @os_run_priority    INT,
  @database_name      sysname OUTPUT,
  @database_user_name sysname OUTPUT,
  @flags              INT,
  @output_file_name   NVARCHAR(200),
  @proxy_id         INT
AS
BEGIN
  DECLARE @max_step_id             INT
  DECLARE @retval                  INT
  DECLARE @valid_values            VARCHAR(50)
  DECLARE @database_name_temp      NVARCHAR(258)
  DECLARE @database_user_name_temp NVARCHAR(256)
  DECLARE @temp_command            NVARCHAR(max)
  DECLARE @iPos                    INT
  DECLARE @create_count            INT
  DECLARE @destroy_count           INT
  DECLARE @is_olap_subsystem       BIT
  DECLARE @owner_sid               VARBINARY(85)
  DECLARE @owner_name              sysname
  -- Remove any leading/trailing spaces from parameters
  SELECT @subsystem        = LTRIM(RTRIM(@subsystem))
  SELECT @server           = LTRIM(RTRIM(@server))
  SELECT @output_file_name = LTRIM(RTRIM(@output_file_name))

  -- Get current maximum step id
  SELECT @max_step_id = ISNULL(MAX(step_id), 0)
  FROM msdb.dbo.sysjobsteps
  WHERE (job_id = @job_id)

  -- Check step id
  IF (@step_id < 1) OR (@step_id > @max_step_id + 1)
  BEGIN
    SELECT @valid_values = '1..' + CONVERT(VARCHAR, @max_step_id + 1)
    RAISERROR(14266, -1, -1, '@step_id', @valid_values)
    RETURN(1) -- Failure
  END

  -- Check subsystem
  EXECUTE @retval = sp_verify_subsystem @subsystem
  IF (@retval <> 0)
    RETURN(1) -- Failure

  --check if proxy is allowed for this subsystem for current user
  IF (@proxy_id IS NOT NULL)
  BEGIN
    --get the job owner
    SELECT @owner_sid = owner_sid FROM sysjobs
    WHERE  job_id = @job_id
    IF @owner_sid = 0xFFFFFFFF
    BEGIN
      --ask to verify for the special account
      EXECUTE @retval = sp_verify_proxy_permissions
        @subsystem_name = @subsystem,
        @proxy_id = @proxy_id,
        @name = NULL,
        @raise_error = 1,
        @allow_disable_proxy = 1,
        @verify_special_account = 1
      IF (@retval <> 0)
        RETURN(1) -- Failure
    END
    ELSE
    BEGIN
      SELECT @owner_name = SUSER_SNAME(@owner_sid)
      EXECUTE @retval = sp_verify_proxy_permissions
      @subsystem_name = @subsystem,
      @proxy_id = @proxy_id,
      @name = @owner_name,
      @raise_error = 1,
      @allow_disable_proxy = 1
      IF (@retval <> 0)
        RETURN(1) -- Failure
    END
  END

  --Only sysadmin can specify @output_file_name
  IF (@output_file_name IS NOT NULL) AND  (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(14582, -1, -1)
    RETURN(1) -- Failure
  END

  --Determmine if this is a olap subsystem jobstep
  IF ( UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) in (N'ANALYSISQUERY', N'ANALYSISCOMMAND') )
    SELECT @is_olap_subsystem = 1
  ELSE
    SELECT @is_olap_subsystem = 0

  -- Check step name
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobsteps
              WHERE (job_id = @job_id)
                AND (step_name = @step_name)))
  BEGIN
    RAISERROR(14261, -1, -1, '@step_name', @step_name)
    RETURN(1) -- Failure
  END

  -- Check on-success action/step
  IF (@on_success_action <> 1) AND -- Quit Qith Success
     (@on_success_action <> 2) AND -- Quit Qith Failure
     (@on_success_action <> 3) AND -- Goto Next Step
     (@on_success_action <> 4)     -- Goto Step
  BEGIN
    RAISERROR(14266, -1, -1, '@on_success_action', '1, 2, 3, 4')
    RETURN(1) -- Failure
  END
  IF (@on_success_action = 4) AND
     ((@on_success_step_id < 1) OR (@on_success_step_id = @step_id))
  BEGIN
    -- NOTE: We allow forward references to non-existant steps to prevent the user from
    --       having to make a second update pass to fix up the flow
    RAISERROR(14235, -1, -1, '@on_success_step', @step_id)
    RETURN(1) -- Failure
  END

  -- Check on-fail action/step
  IF (@on_fail_action <> 1) AND -- Quit With Success
     (@on_fail_action <> 2) AND -- Quit With Failure
     (@on_fail_action <> 3) AND -- Goto Next Step
     (@on_fail_action <> 4)     -- Goto Step
  BEGIN
    RAISERROR(14266, -1, -1, '@on_failure_action', '1, 2, 3, 4')
    RETURN(1) -- Failure
  END
  IF (@on_fail_action = 4) AND
     ((@on_fail_step_id < 1) OR (@on_fail_step_id = @step_id))
  BEGIN
    -- NOTE: We allow forward references to non-existant steps to prevent the user from
    --       having to make a second update pass to fix up the flow
    RAISERROR(14235, -1, -1, '@on_failure_step', @step_id)
    RETURN(1) -- Failure
  END

  -- Warn the user about forward references
  IF ((@on_success_action = 4) AND (@on_success_step_id > @max_step_id))
    RAISERROR(14236, 0, 1, '@on_success_step_id')
  IF ((@on_fail_action = 4) AND (@on_fail_step_id > @max_step_id))
    RAISERROR(14236, 0, 1, '@on_fail_step_id')

  --Special case the olap subsystem. It can have any server name.
  --Default it to the local server if @server is null
  IF(@is_olap_subsystem = 1)
  BEGIN
    IF(@server IS NULL)
    BEGIN
    --TODO: needs error better message ? >> 'Specify the OLAP server name in the %s parameter'
      --Must specify the olap server name
      RAISERROR(14262, -1, -1, '@server', @server)
      RETURN(1) -- Failure
    END
  END
  ELSE
  BEGIN
    -- Check server (this is the replication server, NOT the job-target server)
    -- @server may contain port number remove it before looking up.
    declare @srv sysname
    declare @index int
    set @index = PATINDEX('%,%', @server)

    IF @index <> 0
      set @srv = substring(@server, 0, @index)
    ELSE
      set @srv = @server

    IF (@server IS NOT NULL) AND (NOT EXISTS (SELECT *
                                              FROM master.dbo.sysservers
                                              WHERE (UPPER(srvname) = UPPER(@srv))))
    BEGIN
      RAISERROR(14234, -1, -1, '@srv', 'sp_helpserver')
      RETURN(1) -- Failure
    END
  END

  -- Check run priority: must be a valid value to pass to SetThreadPriority:
  -- [-15 = IDLE, -1 = BELOW_NORMAL, 0 = NORMAL, 1 = ABOVE_NORMAL, 15 = TIME_CRITICAL]
  IF (@os_run_priority NOT IN (-15, -1, 0, 1, 15))
  BEGIN
    RAISERROR(14266, -1, -1, '@os_run_priority', '-15, -1, 0, 1, 15')
    RETURN(1) -- Failure
  END

  -- Check flags
  IF ((@flags < 0) OR (@flags > 114))
  BEGIN
    RAISERROR(14266, -1, -1, '@flags', '0..114')
    RETURN(1) -- Failure
  END

  -- @flags=4 is valid only for TSQL subsystem
  IF (((@flags & 4) <> 0) AND (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) NOT IN ('TSQL')))
  BEGIN
    RAISERROR(14545, -1, -1, '@flags', @subsystem)
    RETURN(1) -- Failure
  END

  -- values 8 and 16 for @flags cannot be combined
  IF (((@flags & 8) <> 0) AND ((@flags & 16) <> 0))
  BEGIN
    RAISERROR(14545, -1, -1, '@flags', @subsystem)
    RETURN(1) -- Failure
  END

  IF (((@flags & 64) <> 0) AND (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) NOT IN ('CMDEXEC')))
  BEGIN
    RAISERROR(14545, -1, -1, '@flags', @subsystem)
    RETURN(1) -- Failure
  END

  -- Check output file
  IF (@output_file_name IS NOT NULL) AND (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) NOT IN ('TSQL', 'CMDEXEC', 'ANALYSISQUERY', 'ANALYSISCOMMAND', 'SSIS', 'POWERSHELL'))
  BEGIN
    RAISERROR(14545, -1, -1, '@output_file_name', @subsystem)
    RETURN(1) -- Failure
  END

  -- Check writing to table flags
  -- Note: explicit check for null is required here
  IF (@flags IS NOT NULL) AND (((@flags & 8) <> 0) OR ((@flags & 16) <> 0)) AND (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) NOT IN ('TSQL', 'CMDEXEC', 'ANALYSISQUERY', 'ANALYSISCOMMAND', 'SSIS', 'POWERSHELL'))
  BEGIN
    RAISERROR(14545, -1, -1, '@flags', @subsystem)
    RETURN(1) -- Failure
  END

  -- For CmdExec steps database-name and database-user-name should both be null
  IF (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = N'CMDEXEC')
    SELECT @database_name = NULL,
           @database_user_name = NULL

  -- For non-TSQL steps, database-user-name should be null
  IF (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) <> 'TSQL')
    SELECT @database_user_name = NULL

  -- For a TSQL step, get (and check) the username of the caller in the target database.
  IF (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = 'TSQL')
  BEGIN
    SET NOCOUNT ON

    -- But first check if remote server name has been supplied
    IF (@server IS NOT NULL)
      SELECT @server = NULL

    -- Default database to 'master' if not supplied
    IF (LTRIM(RTRIM(@database_name)) IS NULL)
      SELECT @database_name = N'master'

    -- Check the database (although this is no guarantee that @database_user_name can access it)
    IF (DB_ID(@database_name) IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@database_name', @database_name)
      RETURN(1) -- Failure
    END

    SELECT @database_user_name = LTRIM(RTRIM(@database_user_name))

    -- Only if a SysAdmin is creating the job can the database user name be non-NULL [since only
    -- SysAdmin's can call SETUSER].
    -- NOTE: In this case we don't try to validate the user name (it's too costly to do so)
    --       so if it's bad we'll get a runtime error when the job executes.
    IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
    BEGIN
      -- If this is a multi-server job then @database_user_name must be null
      IF (@database_user_name IS NOT NULL)
      BEGIN
        IF (EXISTS (SELECT *
                    FROM msdb.dbo.sysjobs       sj,
                         msdb.dbo.sysjobservers sjs
                    WHERE (sj.job_id = sjs.job_id)
                      AND (sj.job_id = @job_id)
                      AND (sjs.server_id <> 0)))
        BEGIN
          RAISERROR(14542, -1, -1, N'database_user_name')
          RETURN(1) -- Failure
        END
      END

      -- For a SQL-user, check if it exists
      IF (@database_user_name NOT LIKE N'%\%')
      BEGIN
        SELECT @database_user_name_temp = replace(@database_user_name, N'''', N'''''')
        SELECT @database_name_temp = QUOTENAME(@database_name)

        EXECUTE(N'DECLARE @ret INT
                  SELECT @ret = COUNT(*)
                  FROM ' + @database_name_temp + N'.dbo.sysusers
                  WHERE (name = N''' + @database_user_name_temp + N''')
                  HAVING (COUNT(*) > 0)')
        IF (@@ROWCOUNT = 0)
        BEGIN
          RAISERROR(14262, -1, -1, '@database_user_name', @database_user_name)
          RETURN(1) -- Failure
        END
      END
    END
    ELSE
      SELECT @database_user_name = NULL

  END  -- End of TSQL property verification

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_ADD_JOBSTEP_INTERNAL                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_jobstep_internal...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_jobstep_internal')
              AND (type = 'P')))
  DROP PROCEDURE dbo.sp_add_jobstep_internal
go
CREATE PROCEDURE dbo.sp_add_jobstep_internal
  @job_id                UNIQUEIDENTIFIER = NULL,   -- Must provide either this or job_name
  @job_name              sysname          = NULL,   -- Must provide either this or job_id
  @step_id               INT              = NULL,   -- The proc assigns a default
  @step_name             sysname,
  @subsystem             NVARCHAR(40)     = N'TSQL',
  @command               NVARCHAR(max)    = NULL,
  @additional_parameters NVARCHAR(max)    = NULL,
  @cmdexec_success_code  INT              = 0,
  @on_success_action     TINYINT          = 1,      -- 1 = Quit With Success, 2 = Quit With Failure, 3 = Goto Next Step, 4 = Goto Step
  @on_success_step_id    INT              = 0,
  @on_fail_action        TINYINT          = 2,      -- 1 = Quit With Success, 2 = Quit With Failure, 3 = Goto Next Step, 4 = Goto Step
  @on_fail_step_id       INT              = 0,
  @server                sysname          = NULL,
  @database_name         sysname          = NULL,
  @database_user_name    sysname          = NULL,
  @retry_attempts        INT              = 0,      -- No retries
  @retry_interval        INT              = 0,      -- 0 minute interval
  @os_run_priority       INT              = 0,      -- -15 = Idle, -1 = Below Normal, 0 = Normal, 1 = Above Normal, 15 = Time Critical)
  @output_file_name      NVARCHAR(200)    = NULL,
  @flags                 INT              = 0,       --  0 = Normal,
                                                     --  1 = Encrypted command (read only),
                                                     --  2 = Append output files (if any),
                                                     --  4 = Write TSQL step output to step history
                                                     --  8 = Write log to table (overwrite existing history)
                                                     -- 16 = Write log to table (append to existing history)
                                                     -- 32 = Write all output to job history
                                                     -- 64 = Create a Windows event to use as a signal for the Cmd jobstep to abort
  @proxy_id               int               = NULL,
  @proxy_name              sysname           = NULL,
  -- mutual exclusive; must specify only one of above 2 parameters to
  -- identify the proxy.
  @step_uid UNIQUEIDENTIFIER              = NULL OUTPUT
AS
BEGIN
  DECLARE @retval         INT
  DECLARE @max_step_id    INT
  DECLARE @job_owner_sid  VARBINARY(85)
  DECLARE @subsystem_id   INT
  DECLARE @auto_proxy_name sysname
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @step_name          = LTRIM(RTRIM(@step_name))
  SELECT @subsystem          = LTRIM(RTRIM(@subsystem))
  SELECT @server             = LTRIM(RTRIM(@server))
  SELECT @database_name      = LTRIM(RTRIM(@database_name))
  SELECT @database_user_name = LTRIM(RTRIM(@database_user_name))
  SELECT @output_file_name   = LTRIM(RTRIM(@output_file_name))
  SELECT @proxy_name         = LTRIM(RTRIM(@proxy_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@server             = N'') SELECT @server             = NULL
  IF (@database_name      = N'') SELECT @database_name      = NULL
  IF (@database_user_name = N'') SELECT @database_user_name = NULL
  IF (@output_file_name   = N'') SELECT @output_file_name   = NULL
  IF (@proxy_name         = N'') SELECT @proxy_name         = NULL

  -- Check authority (only SQLServerAgent can add a step to a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                               @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) AND
      (SUSER_SID() <> @job_owner_sid))
  BEGIN
     RAISERROR(14525, -1, -1)
     RETURN(1) -- Failure
  END


  -- check proxy identifiers only if a proxy has been provided
  IF (@proxy_id IS NOT NULL) or (@proxy_name IS NOT NULL)
  BEGIN
    EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                  '@proxy_id',
                                                   @proxy_name OUTPUT,
                                                   @proxy_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
   END

  -- Default step id (if not supplied)
  IF (@step_id IS NULL)
  BEGIN
    SELECT @step_id = ISNULL(MAX(step_id), 0) + 1
    FROM msdb.dbo.sysjobsteps
    WHERE (job_id = @job_id)
  END

  -- Check parameters
  EXECUTE @retval = sp_verify_jobstep @job_id,
                                      @step_id,
                                      @step_name,
                                      @subsystem,
                                      @command,
                                      @server,
                                      @on_success_action,
                                      @on_success_step_id,
                                      @on_fail_action,
                                      @on_fail_step_id,
                                      @os_run_priority,
                                      @database_name      OUTPUT,
                                      @database_user_name OUTPUT,
                                      @flags,
                                      @output_file_name,
                                               @proxy_id

  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Get current maximum step id
  SELECT @max_step_id = ISNULL(MAX(step_id), 0)
  FROM msdb.dbo.sysjobsteps
  WHERE (job_id = @job_id)

  DECLARE @TranCounter INT;
  SET @TranCounter = @@TRANCOUNT;
  IF @TranCounter = 0
  BEGIN
      -- start our own transaction if there is no outer transaction
      BEGIN TRANSACTION;
  END

  -- Modify database.
  BEGIN TRY
    -- Update the job's version/last-modified information
    UPDATE msdb.dbo.sysjobs
    SET version_number = version_number + 1,
        date_modified = GETDATE()
    WHERE (job_id = @job_id)

    -- Adjust step id's (unless the new step is being inserted at the 'end')
    -- NOTE: We MUST do this before inserting the step.
    IF (@step_id <= @max_step_id)
    BEGIN
      UPDATE msdb.dbo.sysjobsteps
      SET step_id = step_id + 1
      WHERE (step_id >= @step_id)
        AND (job_id = @job_id)

      -- Clean up OnSuccess/OnFail references
      UPDATE msdb.dbo.sysjobsteps
      SET on_success_step_id = on_success_step_id + 1
      WHERE (on_success_step_id >= @step_id)
        AND (job_id = @job_id)

      UPDATE msdb.dbo.sysjobsteps
      SET on_fail_step_id = on_fail_step_id + 1
      WHERE (on_fail_step_id >= @step_id)
        AND (job_id = @job_id)

      UPDATE msdb.dbo.sysjobsteps
      SET on_success_step_id = 0,
          on_success_action = 1  -- Quit With Success
      WHERE (on_success_step_id = @step_id)
        AND (job_id = @job_id)

      UPDATE msdb.dbo.sysjobsteps
      SET on_fail_step_id = 0,
          on_fail_action = 2     -- Quit With Failure
      WHERE (on_fail_step_id = @step_id)
        AND (job_id = @job_id)
    END

    SELECT @step_uid = NEWID()

    -- Insert the step
    INSERT INTO msdb.dbo.sysjobsteps
           (job_id,
            step_id,
            step_name,
            subsystem,
            command,
            flags,
            additional_parameters,
            cmdexec_success_code,
            on_success_action,
            on_success_step_id,
            on_fail_action,
            on_fail_step_id,
            server,
            database_name,
            database_user_name,
            retry_attempts,
            retry_interval,
            os_run_priority,
            output_file_name,
            last_run_outcome,
            last_run_duration,
            last_run_retries,
            last_run_date,
            last_run_time,
            proxy_id,
         step_uid)
    VALUES (@job_id,
            @step_id,
            @step_name,
            @subsystem,
            @command,
            @flags,
            @additional_parameters,
            @cmdexec_success_code,
            @on_success_action,
            @on_success_step_id,
            @on_fail_action,
            @on_fail_step_id,
            @server,
            @database_name,
            @database_user_name,
            @retry_attempts,
            @retry_interval,
            @os_run_priority,
            @output_file_name,
            0,
            0,
            0,
            0,
            0,
         @proxy_id,
         @step_uid)

  IF @TranCounter = 0
  BEGIN
      -- start our own transaction if there is no outer transaction
      COMMIT TRANSACTION;
  END

  END TRY
  BEGIN CATCH

      -- Prepare tp echo error information to the caller.
      DECLARE @ErrorMessage NVARCHAR(400)
      DECLARE @ErrorSeverity INT
      DECLARE @ErrorState INT

      SELECT @ErrorMessage = ERROR_MESSAGE()
      SELECT @ErrorSeverity = ERROR_SEVERITY()
      SELECT @ErrorState = ERROR_STATE()

      IF @TranCounter = 0
      BEGIN
          -- Transaction started in procedure.
          -- Roll back complete transaction.
          ROLLBACK TRANSACTION;
      END
      RAISERROR (@ErrorMessage, -- Message text.
                  @ErrorSeverity, -- Severity.
                  @ErrorState -- State.
                  )
      RETURN (1)
  END CATCH

  -- Make sure that SQLServerAgent refreshes the job if the 'Has Steps' property has changed
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.sysjobsteps
       WHERE (job_id = @job_id)) = 1)
  BEGIN
    -- NOTE: We only notify SQLServerAgent if we know the job has been cached
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                  AND (server_id = 0)))
      EXECUTE msdb.dbo.sp_sqlagent_notify @op_type       = N'J',
                                            @job_id      = @job_id,
                                            @action_type = N'U'
  END

  -- For a multi-server job, push changes to the target servers
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
  BEGIN
    EXECUTE msdb.dbo.sp_post_msx_operation 'INSERT', 'JOB', @job_id
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_ADD_JOBSTEP                                             */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_jobstep...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_jobstep')
              AND (type = 'P')))
  DROP PROCEDURE dbo.sp_add_jobstep
go
CREATE PROCEDURE dbo.sp_add_jobstep
  @job_id                UNIQUEIDENTIFIER = NULL,   -- Must provide either this or job_name
  @job_name              sysname          = NULL,   -- Must provide either this or job_id
  @step_id               INT              = NULL,   -- The proc assigns a default
  @step_name             sysname,
  @subsystem             NVARCHAR(40)     = N'TSQL',
  @command               NVARCHAR(max)   = NULL,
  @additional_parameters NVARCHAR(max)    = NULL,
  @cmdexec_success_code  INT              = 0,
  @on_success_action     TINYINT          = 1,      -- 1 = Quit With Success, 2 = Quit With Failure, 3 = Goto Next Step, 4 = Goto Step
  @on_success_step_id    INT              = 0,
  @on_fail_action        TINYINT          = 2,      -- 1 = Quit With Success, 2 = Quit With Failure, 3 = Goto Next Step, 4 = Goto Step
  @on_fail_step_id       INT              = 0,
  @server                sysname      = NULL,
  @database_name         sysname          = NULL,
  @database_user_name    sysname          = NULL,
  @retry_attempts        INT              = 0,      -- No retries
  @retry_interval        INT              = 0,      -- 0 minute interval
  @os_run_priority       INT              = 0,      -- -15 = Idle, -1 = Below Normal, 0 = Normal, 1 = Above Normal, 15 = Time Critical)
  @output_file_name      NVARCHAR(200)    = NULL,
  @flags                 INT              = 0,       -- 0 = Normal,
                                                     -- 1 = Encrypted command (read only),
                                                     -- 2 = Append output files (if any),
                                                     -- 4 = Write TSQL step output to step history,
                                                     -- 8 = Write log to table (overwrite existing history),
                                                     -- 16 = Write log to table (append to existing history)
                                                     -- 32 = Write all output to job history
                                                     -- 64 = Create a Windows event to use as a signal for the Cmd jobstep to abort
  @proxy_id                 INT                = NULL,
  @proxy_name               sysname          = NULL,
  -- mutual exclusive; must specify only one of above 2 parameters to
  -- identify the proxy.
  @step_uid UNIQUEIDENTIFIER = NULL OUTPUT
AS
BEGIN
  DECLARE @retval      INT

  SET NOCOUNT ON
  -- Only sysadmin's or db_owner's of msdb can add replication job steps directly
  IF (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) IN
                        (N'DISTRIBUTION',
                         N'SNAPSHOT',
                         N'LOGREADER',
                         N'MERGE',
                         N'QUEUEREADER'))
  BEGIN
    IF NOT ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) OR
            (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1) OR
            (UPPER(USER_NAME() collate SQL_Latin1_General_CP1_CS_AS) = N'DBO'))
    BEGIN
      RAISERROR(14260, -1, -1)
      RETURN(1) -- Failure
    END
  END

  --Only sysadmin can specify @database_user_name
  IF (@database_user_name IS NOT NULL) AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(14583, -1, -1)
    RETURN(1) -- Failure
  END

  -- Make sure Dts is translated into new subsystem's name SSIS
  IF UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = N'DTS'
  BEGIN
    SET @subsystem = N'SSIS'
  END

  EXECUTE @retval = dbo.sp_add_jobstep_internal @job_id = @job_id,
                                                @job_name = @job_name,
                                                @step_id = @step_id,
                                                @step_name = @step_name,
                                                @subsystem = @subsystem,
                                                @command = @command,
                                                @additional_parameters = @additional_parameters,
                                                @cmdexec_success_code = @cmdexec_success_code,
                                                @on_success_action = @on_success_action,
                                                @on_success_step_id = @on_success_step_id,
                                                @on_fail_action = @on_fail_action,
                                                @on_fail_step_id = @on_fail_step_id,
                                                @server = @server,
                                                @database_name = @database_name,
                                                @database_user_name = @database_user_name,
                                                @retry_attempts = @retry_attempts,
                                                @retry_interval = @retry_interval,
                                                @os_run_priority = @os_run_priority,
                                                @output_file_name = @output_file_name,
                                                @flags = @flags,
                                                            @proxy_id = @proxy_id,
                                                @proxy_name = @proxy_name,
                                                            @step_uid = @step_uid OUTPUT


  RETURN(@retval)
END
GO

/**************************************************************/
/* SP_UPDATE_JOBSTEP                                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_jobstep...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_jobstep')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_jobstep
go
CREATE PROCEDURE sp_update_jobstep
  @job_id                 UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name               sysname          = NULL, -- Not updatable (provided for identification purposes only)
  @step_id                INT,                     -- Not updatable (provided for identification purposes only)
  @step_name              sysname          = NULL,
  @subsystem              NVARCHAR(40)     = NULL,
  @command                NVARCHAR(max)    = NULL,
  @additional_parameters  NVARCHAR(max)    = NULL,
  @cmdexec_success_code   INT              = NULL,
  @on_success_action      TINYINT          = NULL,
  @on_success_step_id     INT              = NULL,
  @on_fail_action         TINYINT          = NULL,
  @on_fail_step_id        INT              = NULL,
  @server                 sysname          = NULL,
  @database_name          sysname          = NULL,
  @database_user_name     sysname          = NULL,
  @retry_attempts         INT              = NULL,
  @retry_interval         INT              = NULL,
  @os_run_priority        INT              = NULL,
  @output_file_name       NVARCHAR(200)    = NULL,
  @flags                  INT              = NULL,
  @proxy_id            int          = NULL,
  @proxy_name          sysname         = NULL
  -- mutual exclusive; must specify only one of above 2 parameters to
  -- identify the proxy.
AS
BEGIN
  DECLARE @retval                 INT
  DECLARE @os_run_priority_code   INT
  DECLARE @step_id_as_char        VARCHAR(10)
  DECLARE @new_step_name          sysname
  DECLARE @x_step_name            sysname
  DECLARE @x_subsystem            NVARCHAR(40)
  DECLARE @x_command              NVARCHAR(max)
  DECLARE @x_flags                INT
  DECLARE @x_cmdexec_success_code INT
  DECLARE @x_on_success_action    TINYINT
  DECLARE @x_on_success_step_id   INT
  DECLARE @x_on_fail_action       TINYINT
  DECLARE @x_on_fail_step_id      INT
  DECLARE @x_server               sysname
  DECLARE @x_database_name        sysname
  DECLARE @x_database_user_name   sysname
  DECLARE @x_retry_attempts       INT
  DECLARE @x_retry_interval       INT
  DECLARE @x_os_run_priority      INT
  DECLARE @x_output_file_name     NVARCHAR(200)
  DECLARE @x_proxy_id             INT
  DECLARE @x_last_run_outcome     TINYINT      -- Not updatable (but may be in future)
  DECLARE @x_last_run_duration    INT          -- Not updatable (but may be in future)
  DECLARE @x_last_run_retries     INT          -- Not updatable (but may be in future)
  DECLARE @x_last_run_date        INT          -- Not updatable (but may be in future)
  DECLARE @x_last_run_time        INT          -- Not updatable (but may be in future)

  DECLARE @new_proxy_id           INT
  DECLARE @subsystem_id           INT
  DECLARE @auto_proxy_name        sysname
  DECLARE @job_owner_sid        VARBINARY(85)

  SET NOCOUNT ON

  SELECT @new_proxy_id = NULL

  -- Remove any leading/trailing spaces from parameters
  SELECT @step_name          = LTRIM(RTRIM(@step_name))
  SELECT @subsystem          = LTRIM(RTRIM(@subsystem))
  SELECT @command            = LTRIM(RTRIM(@command))
  SELECT @server             = LTRIM(RTRIM(@server))
  SELECT @database_name      = LTRIM(RTRIM(@database_name))
  SELECT @database_user_name = LTRIM(RTRIM(@database_user_name))
  SELECT @output_file_name   = LTRIM(RTRIM(@output_file_name))
  SELECT @proxy_name         = LTRIM(RTRIM(@proxy_name))

  -- Make sure Dts is translated into new subsystem's name SSIS
  IF (@subsystem IS NOT NULL AND UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = N'DTS')
  BEGIN
    SET @subsystem = N'SSIS'
  END

  -- Only sysadmin's or db_owner's of msdb can directly change
  -- an existing job step to use one of the replication
  -- subsystems
  IF (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) IN
                        (N'DISTRIBUTION',
                         N'SNAPSHOT',
                         N'LOGREADER',
                         N'MERGE',
                         N'QUEUEREADER'))
  BEGIN
    IF NOT ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) OR
            (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1) OR
            (UPPER(USER_NAME() collate SQL_Latin1_General_CP1_CS_AS) = N'DBO'))
    BEGIN
      RAISERROR(14260, -1, -1)
      RETURN(1) -- Failure
    END
  END

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                               @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check permissions beyond what's checked by the sysjobs_view
  -- SQLAgentReader and SQLAgentOperator roles that can see all jobs
  -- cannot modify jobs they do not own
  IF (@job_owner_sid <> SUSER_SID()                   -- does not own the job
     AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))   -- is not sysadmin
  BEGIN
   RAISERROR(14525, -1, -1);
   RETURN(1) -- Failure
  END

  -- Check that the step exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysjobsteps
                  WHERE (job_id = @job_id)
                    AND (step_id = @step_id)))
  BEGIN
    SELECT @step_id_as_char = CONVERT(VARCHAR(10), @step_id)
    RAISERROR(14262, -1, -1, '@step_id', @step_id_as_char)
    RETURN(1) -- Failure
  END

  -- check proxy identifiers only if a proxy has been provided
  -- @proxy_name = N'' is a special case to allow change of an existing proxy with NULL
  IF (@proxy_id IS NOT NULL) OR (@proxy_name IS NOT NULL AND @proxy_name <> N'')
  BEGIN
    EXECUTE @retval = sp_verify_proxy_identifiers '@proxy_name',
                                                  '@proxy_id',
                                                   @proxy_name OUTPUT,
                                                   @proxy_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

     SELECT @new_proxy_id  = @proxy_id

  END

  -- Check authority (only SQLServerAgent can modify a step of a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Set the x_ (existing) variables
  SELECT @x_step_name            = step_name,
         @x_subsystem            = subsystem,
         @x_command              = command,
         @x_flags                = flags,
         @x_cmdexec_success_code = cmdexec_success_code,
         @x_on_success_action    = on_success_action,
         @x_on_success_step_id   = on_success_step_id,
         @x_on_fail_action       = on_fail_action,
         @x_on_fail_step_id      = on_fail_step_id,
         @x_server               = server,
         @x_database_name        = database_name,
         @x_database_user_name   = database_user_name,
         @x_retry_attempts       = retry_attempts,
         @x_retry_interval       = retry_interval,
         @x_os_run_priority      = os_run_priority,
         @x_output_file_name     = output_file_name,
         @x_proxy_id             = proxy_id,
         @x_last_run_outcome     = last_run_outcome,
         @x_last_run_duration    = last_run_duration,
         @x_last_run_retries     = last_run_retries,
         @x_last_run_date        = last_run_date,
         @x_last_run_time        = last_run_time
  FROM msdb.dbo.sysjobsteps
  WHERE (job_id = @job_id)
    AND (step_id = @step_id)

  IF ((@step_name IS NOT NULL) AND (@step_name <> @x_step_name))
    SELECT @new_step_name = @step_name

  -- Fill out the values for all non-supplied parameters from the existing values
  IF (@step_name            IS NULL) SELECT @step_name            = @x_step_name
  IF (@subsystem            IS NULL) SELECT @subsystem            = @x_subsystem
  IF (@command              IS NULL) SELECT @command              = @x_command
  IF (@flags                IS NULL) SELECT @flags                = @x_flags
  IF (@cmdexec_success_code IS NULL) SELECT @cmdexec_success_code = @x_cmdexec_success_code
  IF (@on_success_action    IS NULL) SELECT @on_success_action    = @x_on_success_action
  IF (@on_success_step_id   IS NULL) SELECT @on_success_step_id   = @x_on_success_step_id
  IF (@on_fail_action       IS NULL) SELECT @on_fail_action       = @x_on_fail_action
  IF (@on_fail_step_id      IS NULL) SELECT @on_fail_step_id      = @x_on_fail_step_id
  IF (@server               IS NULL) SELECT @server               = @x_server
  IF (@database_name        IS NULL) SELECT @database_name        = @x_database_name
  IF (@database_user_name   IS NULL) SELECT @database_user_name   = @x_database_user_name
  IF (@retry_attempts       IS NULL) SELECT @retry_attempts       = @x_retry_attempts
  IF (@retry_interval       IS NULL) SELECT @retry_interval       = @x_retry_interval
  IF (@os_run_priority      IS NULL) SELECT @os_run_priority      = @x_os_run_priority
  IF (@output_file_name     IS NULL) SELECT @output_file_name     = @x_output_file_name
  IF (@proxy_id             IS NULL) SELECT @new_proxy_id         = @x_proxy_id

  --if an empty proxy_name is supplied the proxy is removed
  IF @proxy_name = N'' SELECT @new_proxy_id = NULL
  -- Turn [nullable] empty string parameters into NULLs
  IF (@command            = N'') SELECT @command            = NULL
  IF (@server             = N'') SELECT @server             = NULL
  IF (@database_name      = N'') SELECT @database_name      = NULL
  IF (@database_user_name = N'') SELECT @database_user_name = NULL
  IF (@output_file_name   = N'') SELECT @output_file_name   = NULL


  -- Check new values
  EXECUTE @retval = sp_verify_jobstep @job_id,
                                      @step_id,
                                      @new_step_name,
                                      @subsystem,
                                      @command,
                                      @server,
                                      @on_success_action,
                                      @on_success_step_id,
                                      @on_fail_action,
                                      @on_fail_step_id,
                                      @os_run_priority,
                                      @database_name      OUTPUT,
                                      @database_user_name OUTPUT,
                                      @flags,
                                      @output_file_name,
                                               @new_proxy_id
  IF (@retval <> 0)
    RETURN(1) -- Failure

  BEGIN TRANSACTION

    -- Update the job's version/last-modified information
    UPDATE msdb.dbo.sysjobs
    SET version_number = version_number + 1,
        date_modified = GETDATE()
    WHERE (job_id = @job_id)

    -- Update the step
    UPDATE msdb.dbo.sysjobsteps
    SET step_name             = @step_name,
        subsystem             = @subsystem,
        command               = @command,
        flags                 = @flags,
        additional_parameters = @additional_parameters,
        cmdexec_success_code  = @cmdexec_success_code,
        on_success_action     = @on_success_action,
        on_success_step_id    = @on_success_step_id,
        on_fail_action        = @on_fail_action,
        on_fail_step_id       = @on_fail_step_id,
        server                = @server,
        database_name         = @database_name,
        database_user_name    = @database_user_name,
        retry_attempts        = @retry_attempts,
        retry_interval        = @retry_interval,
        os_run_priority       = @os_run_priority,
        output_file_name      = @output_file_name,
        last_run_outcome      = @x_last_run_outcome,
        last_run_duration     = @x_last_run_duration,
        last_run_retries      = @x_last_run_retries,
        last_run_date         = @x_last_run_date,
        last_run_time         = @x_last_run_time,
          proxy_id                 = @new_proxy_id
    WHERE (job_id = @job_id)
      AND (step_id = @step_id)


  COMMIT TRANSACTION

  -- For a multi-server job, push changes to the target servers
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
  BEGIN
    EXECUTE msdb.dbo.sp_post_msx_operation 'INSERT', 'JOB', @job_id
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_DELETE_JOBSTEP                                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_jobstep...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_jobstep')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_jobstep
go
CREATE PROCEDURE sp_delete_jobstep
  @job_id   UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name sysname          = NULL, -- Must provide either this or job_id
  @step_id  INT
AS
BEGIN
  DECLARE @retval      INT
  DECLARE @max_step_id INT
  DECLARE @valid_range VARCHAR(50)
  DECLARE @job_owner_sid VARBINARY(85)

  SET NOCOUNT ON

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                               @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check authority (only SQLServerAgent can delete a step of a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = 'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- SQLAgentReader and SQLAgentOperator roles that can see all jobs
  -- cannot modify jobs they do not own
  IF (@job_owner_sid <> SUSER_SID()                   -- does not own the job
     AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))   -- is not sysadmin
  BEGIN
   RAISERROR(14525, -1, -1);
   RETURN(1) -- Failure
  END

  -- Get current maximum step id
  SELECT @max_step_id = ISNULL(MAX(step_id), 0)
  FROM msdb.dbo.sysjobsteps
  WHERE (job_id = @job_id)

  -- Check step id
  IF (@step_id < 0) OR (@step_id > @max_step_id)
  BEGIN
    SELECT @valid_range = FORMATMESSAGE(14201) + CONVERT(VARCHAR, @max_step_id)
    RAISERROR(14266, -1, -1, '@step_id', @valid_range)
    RETURN(1) -- Failure
  END

  BEGIN TRANSACTION

    -- Delete either the specified step or ALL the steps (if step id is 0)
    IF (@step_id = 0)
    BEGIN
      DELETE FROM msdb.dbo.sysjobsteps
      WHERE (job_id = @job_id)
     END
      ELSE
     BEGIN
      DELETE FROM msdb.dbo.sysjobsteps
      WHERE (job_id = @job_id)
        AND (step_id = @step_id)
    END

    IF (@step_id <> 0)
    BEGIN
      -- Adjust step id's
      UPDATE msdb.dbo.sysjobsteps
      SET step_id = step_id - 1
      WHERE (step_id > @step_id)
        AND (job_id = @job_id)

      -- Clean up OnSuccess/OnFail references
      UPDATE msdb.dbo.sysjobsteps
      SET on_success_step_id = on_success_step_id - 1
      WHERE (on_success_step_id > @step_id)
        AND (job_id = @job_id)

      UPDATE msdb.dbo.sysjobsteps
      SET on_fail_step_id = on_fail_step_id - 1
      WHERE (on_fail_step_id > @step_id)
        AND (job_id = @job_id)

      UPDATE msdb.dbo.sysjobsteps
      SET on_success_step_id = 0,
          on_success_action = 1   -- Quit With Success
      WHERE (on_success_step_id = @step_id)
        AND (job_id = @job_id)

      UPDATE msdb.dbo.sysjobsteps
      SET on_fail_step_id = 0,
          on_fail_action = 2   -- Quit With Failure
      WHERE (on_fail_step_id = @step_id)
        AND (job_id = @job_id)

    END


    -- Update the job's version/last-modified information
    UPDATE msdb.dbo.sysjobs
    SET version_number = version_number + 1,
        date_modified = GETDATE()
    WHERE (job_id = @job_id)

  COMMIT TRANSACTION

  -- Make sure that SQLServerAgent refreshes the job if the 'Has Steps' property has changed
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.sysjobsteps
       WHERE (job_id = @job_id)) = 0)
  BEGIN
    -- NOTE: We only notify SQLServerAgent if we know the job has been cached
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                  AND (server_id = 0)))
      EXECUTE msdb.dbo.sp_sqlagent_notify @op_type       = N'J',
                                            @job_id      = @job_id,
                                            @action_type = N'U'
  END

  -- For a multi-server job, push changes to the target servers
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
  BEGIN
    EXECUTE msdb.dbo.sp_post_msx_operation 'INSERT', 'JOB', @job_id
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_HELP_JOBSTEP                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_jobstep...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobstep')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobstep
go
CREATE PROCEDURE sp_help_jobstep
  @job_id    UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name  sysname          = NULL, -- Must provide either this or job_id
  @step_id   INT              = NULL,
  @step_name sysname          = NULL,
  @suffix    BIT              = 0     -- A flag to control how the result set is formatted
AS
BEGIN
  DECLARE @retval      INT
  DECLARE @max_step_id INT
  DECLARE @valid_range VARCHAR(50)

  SET NOCOUNT ON

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                              'NO_TEST'
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- The suffix flag must be either 0 (ie. no suffix) or 1 (ie. add suffix). 0 is the default.
  IF (@suffix <> 0)
    SELECT @suffix = 1

  -- Check step id (if supplied)
  IF (@step_id IS NOT NULL)
  BEGIN
    -- Get current maximum step id
    SELECT @max_step_id = ISNULL(MAX(step_id), 0)
    FROM msdb.dbo.sysjobsteps
    WHERE job_id = @job_id
   IF @max_step_id = 0
   BEGIN
      RAISERROR(14528, -1, -1, @job_name)
      RETURN(1) -- Failure
   END
    ELSE IF (@step_id < 1) OR (@step_id > @max_step_id)
    BEGIN
      SELECT @valid_range = '1..' + CONVERT(VARCHAR, @max_step_id)
      RAISERROR(14266, -1, -1, '@step_id', @valid_range)
      RETURN(1) -- Failure
    END
  END

  -- Check step name (if supplied)
  -- NOTE: A supplied step id overrides a supplied step name
  IF ((@step_id IS NULL) AND (@step_name IS NOT NULL))
  BEGIN
    SELECT @step_id = step_id
    FROM msdb.dbo.sysjobsteps
    WHERE (step_name = @step_name)
      AND (job_id = @job_id)

    IF (@step_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@step_name', @step_name)
      RETURN(1) -- Failure
    END
  END

  -- Return the job steps for this job (or just return the specific step)
  IF (@suffix = 0)
  BEGIN
    SELECT step_id,
           step_name,
           subsystem,
           command,
           flags,
           cmdexec_success_code,
           on_success_action,
           on_success_step_id,
           on_fail_action,
           on_fail_step_id,
           server,
           database_name,
           database_user_name,
           retry_attempts,
           retry_interval,
           os_run_priority,
           output_file_name,
           last_run_outcome,
           last_run_duration,
           last_run_retries,
           last_run_date,
           last_run_time,
         proxy_id
    FROM msdb.dbo.sysjobsteps
    WHERE (job_id = @job_id)
      AND ((@step_id IS NULL) OR (step_id = @step_id))
    ORDER BY job_id, step_id
  END
  ELSE
  BEGIN
    SELECT step_id,
           step_name,
           subsystem,
           command,
          'flags' = CONVERT(NVARCHAR, flags) + N' (' +
                    ISNULL(CASE WHEN (flags = 0)     THEN FORMATMESSAGE(14561) END, '') +
                    ISNULL(CASE WHEN (flags & 1) = 1 THEN FORMATMESSAGE(14558) + ISNULL(CASE WHEN (flags > 1) THEN N', ' END, '') END, '') +
                    ISNULL(CASE WHEN (flags & 2) = 2 THEN FORMATMESSAGE(14559) + ISNULL(CASE WHEN (flags > 3) THEN N', ' END, '') END, '') +
                    ISNULL(CASE WHEN (flags & 4) = 4 THEN FORMATMESSAGE(14560) END, '') + N')',
           cmdexec_success_code,
          'on_success_action' = CASE on_success_action
                                  WHEN 1 THEN CONVERT(NVARCHAR, on_success_action) + N' ' + FORMATMESSAGE(14562)
                                  WHEN 2 THEN CONVERT(NVARCHAR, on_success_action) + N' ' + FORMATMESSAGE(14563)
                                  WHEN 3 THEN CONVERT(NVARCHAR, on_success_action) + N' ' + FORMATMESSAGE(14564)
                                  WHEN 4 THEN CONVERT(NVARCHAR, on_success_action) + N' ' + FORMATMESSAGE(14565)
                                  ELSE        CONVERT(NVARCHAR, on_success_action) + N' ' + FORMATMESSAGE(14205)
                                END,
           on_success_step_id,
          'on_fail_action' = CASE on_fail_action
                               WHEN 1 THEN CONVERT(NVARCHAR, on_fail_action) + N' ' + FORMATMESSAGE(14562)
                               WHEN 2 THEN CONVERT(NVARCHAR, on_fail_action) + N' ' + FORMATMESSAGE(14563)
                               WHEN 3 THEN CONVERT(NVARCHAR, on_fail_action) + N' ' + FORMATMESSAGE(14564)
                               WHEN 4 THEN CONVERT(NVARCHAR, on_fail_action) + N' ' + FORMATMESSAGE(14565)
                               ELSE        CONVERT(NVARCHAR, on_fail_action) + N' ' + FORMATMESSAGE(14205)
                             END,
           on_fail_step_id,
           server,
           database_name,
           database_user_name,
           retry_attempts,
           retry_interval,
          'os_run_priority' = CASE os_run_priority
                                WHEN -15 THEN CONVERT(NVARCHAR, os_run_priority) + N' ' + FORMATMESSAGE(14566)
                                WHEN -1  THEN CONVERT(NVARCHAR, os_run_priority) + N' ' + FORMATMESSAGE(14567)
                                WHEN  0  THEN CONVERT(NVARCHAR, os_run_priority) + N' ' + FORMATMESSAGE(14561)
                                WHEN  1  THEN CONVERT(NVARCHAR, os_run_priority) + N' ' + FORMATMESSAGE(14568)
                                WHEN  15 THEN CONVERT(NVARCHAR, os_run_priority) + N' ' + FORMATMESSAGE(14569)
                                ELSE          CONVERT(NVARCHAR, os_run_priority) + N' ' + FORMATMESSAGE(14205)
                              END,
           output_file_name,
           last_run_outcome,
           last_run_duration,
           last_run_retries,
           last_run_date,
           last_run_time,
         proxy_id
    FROM msdb.dbo.sysjobsteps
    WHERE (job_id = @job_id)
      AND ((@step_id IS NULL) OR (step_id = @step_id))
    ORDER BY job_id, step_id
  END

  RETURN(@@error) -- 0 means success

END
go


/**************************************************************/
/* SP_WRITE_SYSJOBSTEP_LOG                                    */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_write_sysjobstep_log...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_write_sysjobstep_log')
              AND (type = 'P')))
  DROP PROCEDURE sp_write_sysjobstep_log
go
CREATE PROCEDURE sp_write_sysjobstep_log
  @job_id    UNIQUEIDENTIFIER,
  @step_id   INT,
  @log_text  NVARCHAR(MAX),
  @append_to_last INT = 0
AS
BEGIN
  DECLARE @step_uid UNIQUEIDENTIFIER
  DECLARE @log_already_exists int
  SET @log_already_exists = 0

  SET @step_uid = ( SELECT step_uid FROM  msdb.dbo.sysjobsteps
      WHERE (job_id = @job_id)
        AND (step_id = @step_id) )


  IF(EXISTS(SELECT * FROM msdb.dbo.sysjobstepslogs
                      WHERE step_uid = @step_uid ))
  BEGIN
     SET @log_already_exists = 1
  END

  --Need create log if "overwrite is selected or log does not exists.
  IF (@append_to_last = 0) OR (@log_already_exists = 0)
  BEGIN
     -- flag is overwrite

     --if overwrite and log exists, delete it
     IF (@append_to_last = 0 AND @log_already_exists = 1)
     BEGIN
        -- remove previous logs entries
        EXEC sp_delete_jobsteplog @job_id, NULL, @step_id, NULL
     END

     INSERT INTO msdb.dbo.sysjobstepslogs
      (
         log,
         log_size,
         step_uid
      )
      VALUES
      (
         @log_text,
         DATALENGTH(@log_text),
         @step_uid
      )
  END
  ELSE
  BEGIN
     DECLARE @log_id   INT
     --Selecting TOP is just a safety net - there is only one log entry row per step.
     SET @log_id = ( SELECT TOP 1 log_id FROM msdb.dbo.sysjobstepslogs
         WHERE (step_uid = @step_uid)
           ORDER BY log_id DESC )

      -- Append @log_text to the existing log record. Note that if this
      -- action would make the value of the log column longer than
      -- nvarchar(max), then the engine will raise error 599.
      UPDATE msdb.dbo.sysjobstepslogs
        SET
             log .WRITE(@log_text,NULL,0),
             log_size = DATALENGTH(log) + DATALENGTH(@log_text) ,
             date_modified = getdate()
      WHERE log_id = @log_id
  END

  RETURN(@@error) -- 0 means success

END
go

/**************************************************************/
/* SP_HELP_JOBSTEPLOG                                    */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_help_jobsteplog...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobsteplog')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobsteplog
go
CREATE PROCEDURE sp_help_jobsteplog
  @job_id    UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name  sysname          = NULL, -- Must provide either this or job_id
  @step_id   INT              = NULL,
  @step_name sysname          = NULL
AS
BEGIN
  DECLARE @retval      INT
  DECLARE @max_step_id INT
  DECLARE @valid_range VARCHAR(50)

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                              'NO_TEST'
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check step id (if supplied)
  IF (@step_id IS NOT NULL)
  BEGIN
    -- Get current maximum step id
    SELECT @max_step_id = ISNULL(MAX(step_id), 0)
    FROM msdb.dbo.sysjobsteps
    WHERE job_id = @job_id
   IF @max_step_id = 0
   BEGIN
      RAISERROR(14528, -1, -1, @job_name)
      RETURN(1) -- Failure
   END
    ELSE IF (@step_id < 1) OR (@step_id > @max_step_id)
    BEGIN
      SELECT @valid_range = '1..' + CONVERT(VARCHAR, @max_step_id)
      RAISERROR(14266, -1, -1, '@step_id', @valid_range)
      RETURN(1) -- Failure
    END
  END

  -- Check step name (if supplied)
  -- NOTE: A supplied step id overrides a supplied step name
  IF ((@step_id IS NULL) AND (@step_name IS NOT NULL))
  BEGIN
    SELECT @step_id = step_id
    FROM msdb.dbo.sysjobsteps
    WHERE (step_name = @step_name)
      AND (job_id = @job_id)

    IF (@step_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@step_name', @step_name)
      RETURN(1) -- Failure
    END
  END


    SELECT sjv.job_id,
           @job_name as job_name,
           steps.step_id,
           steps.step_name,
           steps.step_uid,
           logs.date_created,
           logs.date_modified,
           logs.log_size,
           logs.log
    FROM msdb.dbo.sysjobs_view sjv, msdb.dbo.sysjobsteps as steps, msdb.dbo.sysjobstepslogs as logs
    WHERE (sjv.job_id = @job_id)
      AND (steps.job_id = @job_id)
      AND ((@step_id IS NULL) OR (step_id = @step_id))
      AND (steps.step_uid = logs.step_uid)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_JOBSTEPLOG                                    */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_delete_jobsteplog...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_jobsteplog')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_jobsteplog
go
CREATE PROCEDURE sp_delete_jobsteplog
  @job_id      UNIQUEIDENTIFIER = NULL, -- Must provide either this or job_name
  @job_name    sysname          = NULL, -- Must provide either this or job_id
  @step_id     INT              = NULL,
  @step_name   sysname          = NULL,
  @older_than  datetime         = NULL,
  @larger_than int      = NULL   -- (in megabytes)
AS
BEGIN
  DECLARE @retval      INT
  DECLARE @max_step_id INT
  DECLARE @valid_range VARCHAR(50)

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                              'NO_TEST'
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check step id (if supplied)
  IF (@step_id IS NOT NULL)
  BEGIN
    -- Get current maximum step id
    SELECT @max_step_id = ISNULL(MAX(step_id), 0)
    FROM msdb.dbo.sysjobsteps
    WHERE job_id = @job_id
   IF @max_step_id = 0
   BEGIN
      RAISERROR(14528, -1, -1, @job_name)
      RETURN(1) -- Failure
   END
    ELSE IF (@step_id < 1) OR (@step_id > @max_step_id)
    BEGIN
      SELECT @valid_range = '1..' + CONVERT(VARCHAR, @max_step_id)
      RAISERROR(14266, -1, -1, '@step_id', @valid_range)
      RETURN(1) -- Failure
    END
  END

  -- Check step name (if supplied)
  -- NOTE: A supplied step id overrides a supplied step name
  IF ((@step_id IS NULL) AND (@step_name IS NOT NULL))
  BEGIN
    SELECT @step_id = step_id
    FROM msdb.dbo.sysjobsteps
    WHERE (step_name = @step_name)
      AND (job_id = @job_id)

    IF (@step_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@step_name', @step_name)
      RETURN(1) -- Failure
    END
  END


   -- Delete either the specified step or ALL the steps (if step id is NULL)

   DELETE FROM msdb.dbo.sysjobstepslogs
   WHERE (step_uid IN (SELECT DISTINCT step_uid
                        FROM   msdb.dbo.sysjobsteps js, msdb.dbo.sysjobs_view jv
                        WHERE (  @job_id = jv.job_id )
                          AND (js.job_id = jv.job_id )
                          AND ((@step_id IS NULL) OR (@step_id = step_id))))
    AND ((@older_than IS NULL) OR (date_modified < @older_than))
    AND ((@larger_than IS NULL) OR (log_size > @larger_than))

  RETURN(@retval) -- 0 means success

END
go


/**************************************************************/
/* SP_GET_SCHEDULE_DESCRIPTION                                */
/*                                                            */
/* NOTE: This SP only returns an English description of the   */
/*       schedule due to the piecemeal nature of the          */
/*       description's construction.                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_get_schedule_description...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_get_schedule_description')
              AND (type = 'P')))
  DROP PROCEDURE sp_get_schedule_description
go
CREATE PROCEDURE sp_get_schedule_description
  @freq_type              INT          = NULL,
  @freq_interval          INT          = NULL,
  @freq_subday_type       INT          = NULL,
  @freq_subday_interval   INT          = NULL,
  @freq_relative_interval INT          = NULL,
  @freq_recurrence_factor INT          = NULL,
  @active_start_date      INT          = NULL,
  @active_end_date        INT          = NULL,
  @active_start_time      INT          = NULL,
  @active_end_time        INT          = NULL,
  @schedule_description   NVARCHAR(255) OUTPUT
AS
BEGIN
  DECLARE @loop              INT
  DECLARE @idle_cpu_percent  INT
  DECLARE @idle_cpu_duration INT

  SET NOCOUNT ON

  IF (@freq_type = 0x1) -- OneTime
  BEGIN
    SELECT @schedule_description = N'Once on ' + CONVERT(NVARCHAR, @active_start_date) + N' at ' + CONVERT(NVARCHAR, @active_start_time)
    RETURN
  END

  IF (@freq_type = 0x4) -- Daily
  BEGIN
    SELECT @schedule_description = N'Every day '
  END

  IF (@freq_type = 0x8) -- Weekly
  BEGIN
    SELECT @schedule_description = N'Every ' + CONVERT(NVARCHAR, @freq_recurrence_factor) + N' week(s) on '
    SELECT @loop = 1
    WHILE (@loop <= 7)
    BEGIN
      IF (@freq_interval & POWER(2, @loop - 1) = POWER(2, @loop - 1))
        SELECT @schedule_description = @schedule_description + DATENAME(dw, N'1996120' + CONVERT(NVARCHAR, @loop)) + N', '
      SELECT @loop = @loop + 1
    END
    IF (RIGHT(@schedule_description, 2) = N', ')
      SELECT @schedule_description = SUBSTRING(@schedule_description, 1, (DATALENGTH(@schedule_description) / 2) - 2) + N' '
  END

  IF (@freq_type = 0x10) -- Monthly
  BEGIN
    SELECT @schedule_description = N'Every ' + CONVERT(NVARCHAR, @freq_recurrence_factor) + N' months(s) on day ' + CONVERT(NVARCHAR, @freq_interval) + N' of that month '
  END

  IF (@freq_type = 0x20) -- Monthly Relative
  BEGIN
    SELECT @schedule_description = N'Every ' + CONVERT(NVARCHAR, @freq_recurrence_factor) + N' months(s) on the '
    SELECT @schedule_description = @schedule_description +
      CASE @freq_relative_interval
        WHEN 0x01 THEN N'first '
        WHEN 0x02 THEN N'second '
        WHEN 0x04 THEN N'third '
        WHEN 0x08 THEN N'fourth '
        WHEN 0x10 THEN N'last '
      END +
      CASE
        WHEN (@freq_interval > 00)
         AND (@freq_interval < 08) THEN DATENAME(dw, N'1996120' + CONVERT(NVARCHAR, @freq_interval))
        WHEN (@freq_interval = 08) THEN N'day'
        WHEN (@freq_interval = 09) THEN N'week day'
        WHEN (@freq_interval = 10) THEN N'weekend day'
      END + N' of that month '
  END

  IF (@freq_type = 0x40) -- AutoStart
  BEGIN
    SELECT @schedule_description = FORMATMESSAGE(14579)
    RETURN
  END

  IF (@freq_type = 0x80) -- OnIdle
  BEGIN
    IF (SERVERPROPERTY('EngineEdition') = 8)
      SELECT @schedule_description = FORMATMESSAGE(41914, N'Schedule job ONIDLE')
    ELSE
    BEGIN
      EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                             N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                             N'IdleCPUPercent',
                                             @idle_cpu_percent OUTPUT,
                                             N'no_output'
      EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                             N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                             N'IdleCPUDuration',
                                             @idle_cpu_duration OUTPUT,
                                             N'no_output'
      SELECT @schedule_description = FORMATMESSAGE(14578, ISNULL(@idle_cpu_percent, 10), ISNULL(@idle_cpu_duration, 600))
    END
    RETURN
  END

  -- Subday stuff
  SELECT @schedule_description = @schedule_description +
    CASE @freq_subday_type
      WHEN 0x1 THEN N'at ' + CONVERT(NVARCHAR, @active_start_time)
      WHEN 0x2 THEN N'every ' + CONVERT(NVARCHAR, @freq_subday_interval) + N' second(s)'
      WHEN 0x4 THEN N'every ' + CONVERT(NVARCHAR, @freq_subday_interval) + N' minute(s)'
      WHEN 0x8 THEN N'every ' + CONVERT(NVARCHAR, @freq_subday_interval) + N' hour(s)'
    END
  IF (@freq_subday_type IN (0x2, 0x4, 0x8))
    SELECT @schedule_description = @schedule_description + N' between ' +
           CONVERT(NVARCHAR, @active_start_time) + N' and ' + CONVERT(NVARCHAR, @active_end_time)
END
go

CHECKPOINT
go


/**************************************************************/
/* SP_ADD_JOBSCHEDULE                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_jobschedule...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_jobschedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_jobschedule
go
CREATE PROCEDURE sp_add_jobschedule
  @job_id                 UNIQUEIDENTIFIER = NULL,
  @job_name               sysname          = NULL,
  @name                   sysname,
  @enabled                TINYINT          = 1,
  @freq_type              INT              = 1,
  @freq_interval          INT              = 0,
  @freq_subday_type       INT              = 0,
  @freq_subday_interval   INT              = 0,
  @freq_relative_interval INT              = 0,
  @freq_recurrence_factor INT              = 0,
  @active_start_date      INT              = NULL,     -- sp_verify_schedule assigns a default
  @active_end_date        INT              = 99991231, -- December 31st 9999
  @active_start_time      INT              = 000000,   -- 12:00:00 am
  @active_end_time        INT              = 235959,    -- 11:59:59 pm
  @schedule_id            INT              = NULL  OUTPUT,
  @automatic_post         BIT              = 1,         -- If 1 will post notifications to all tsx servers to that run this job
  @schedule_uid           UNIQUEIDENTIFIER = NULL OUTPUT
AS
BEGIN
  DECLARE @retval           INT
  DECLARE @owner_login_name sysname
  DECLARE @owner_sid varbinary(85)

  SET NOCOUNT ON

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (SERVERPROPERTY('EngineEdition') = 8 AND @freq_type = 0x80)
  BEGIN
    RAISERROR(41914, -1, 6, 'Schedule job ONIDLE')
    RETURN(1) -- Failure
  END

  -- Check authority (only SQLServerAgent can add a schedule to a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Check that we can uniquely identify the job
  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Get the owner of the job. Prior to resusable schedules the job owner also owned the schedule
  SELECT @owner_login_name = dbo.SQLAGENT_SUSER_SNAME(owner_sid),
         @owner_sid = owner_sid
  FROM   sysjobs
  WHERE  (job_id = @job_id)

  -- Switching to use SID instead of SNAME() to fix TFS#4427530
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) AND
	(SUSER_SID() <> @owner_sid))
  BEGIN
   RAISERROR(14525, -1, -1)
   RETURN(1) -- Failure
  END

  -- Check authority (only SQLServerAgent can add a schedule to a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Add the schedule first
  EXECUTE @retval = msdb.dbo.sp_add_schedule @schedule_name          = @name,
                                             @enabled                = @enabled,
                                             @freq_type              = @freq_type,
                                             @freq_interval          = @freq_interval,
                                             @freq_subday_type       = @freq_subday_type,
                                             @freq_subday_interval   = @freq_subday_interval,
                                             @freq_relative_interval = @freq_relative_interval,
                                             @freq_recurrence_factor = @freq_recurrence_factor,
                                             @active_start_date      = @active_start_date,
                                             @active_end_date        = @active_end_date,
                                             @active_start_time      = @active_start_time,
                                             @active_end_time        = @active_end_time,
                                             @owner_login_name       = @owner_login_name,
                                             @schedule_uid           = @schedule_uid OUTPUT,
                                             @schedule_id            = @schedule_id  OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure


  EXECUTE @retval = msdb.dbo.sp_attach_schedule @job_id           = @job_id,
                                                @job_name         = NULL,
                                                @schedule_id      = @schedule_id,
                                                @schedule_name    = NULL,
                                                @automatic_post   = @automatic_post
  IF (@retval <> 0)
    RETURN(1) -- Failure



  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_UPDATE_JOBSCHEDULE                                      */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_jobschedule...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_jobschedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_jobschedule
go
CREATE PROCEDURE sp_update_jobschedule              -- This SP is deprecated by sp_update_schedule.
  @job_id                 UNIQUEIDENTIFIER = NULL,
  @job_name               sysname          = NULL,
  @name                   sysname,
  @new_name               sysname          = NULL,
  @enabled                TINYINT          = NULL,
  @freq_type              INT              = NULL,
  @freq_interval          INT              = NULL,
  @freq_subday_type       INT              = NULL,
  @freq_subday_interval   INT              = NULL,
  @freq_relative_interval INT              = NULL,
  @freq_recurrence_factor INT              = NULL,
  @active_start_date      INT              = NULL,
  @active_end_date        INT              = NULL,
  @active_start_time      INT              = NULL,
  @active_end_time        INT              = NULL,
  @automatic_post         BIT              = 1         -- If 1 will post notifications to all tsx servers to that run this job
AS
BEGIN
  DECLARE @retval                   INT
  DECLARE @sched_count              INT
  DECLARE @schedule_id              INT
  DECLARE @job_owner_sid            VARBINARY(85)
  DECLARE @enable_only_used         INT

  DECLARE @x_name                   sysname
  DECLARE @x_enabled                TINYINT
  DECLARE @x_freq_type              INT
  DECLARE @x_freq_interval          INT
  DECLARE @x_freq_subday_type       INT
  DECLARE @x_freq_subday_interval   INT
  DECLARE @x_freq_relative_interval INT
  DECLARE @x_freq_recurrence_factor INT
  DECLARE @x_active_start_date      INT
  DECLARE @x_active_end_date        INT
  DECLARE @x_active_start_time      INT
  DECLARE @x_active_end_time        INT
  DECLARE @owner_sid                VARBINARY(85)

  SET NOCOUNT ON

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (SERVERPROPERTY('EngineEdition') = 8 AND @freq_type = 0x80)
  BEGIN
    RAISERROR(41914, -1, 7, 'Schedule job ONIDLE')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @name     = LTRIM(RTRIM(@name))
  SELECT @new_name = LTRIM(RTRIM(@new_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@new_name = N'') SELECT @new_name = NULL

  -- Check authority (only SQLServerAgent can modify a schedule of a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = 'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Check that we can uniquely identify the job
  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                               @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Is @enable the only parameter used beside jobname and jobid?
  IF ((@enabled                   IS NOT NULL) AND
     (@name                 IS NULL) AND
     (@new_name                IS NULL) AND
      (@freq_type              IS NULL) AND
      (@freq_interval             IS NULL) AND
      (@freq_subday_type          IS NULL) AND
      (@freq_subday_interval      IS NULL) AND
      (@freq_relative_interval       IS NULL) AND
      (@freq_recurrence_factor       IS NULL) AND
      (@active_start_date         IS NULL) AND
      (@active_end_date           IS NULL) AND
      (@active_start_time         IS NULL) AND
      (@active_end_time           IS NULL))
    SELECT @enable_only_used = 1
  ELSE
    SELECT @enable_only_used = 0


  IF ((SUSER_SID() <> @job_owner_sid)
     AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
      AND (@enable_only_used <> 1 OR ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) <> 1))
  BEGIN
   RAISERROR(14525, -1, -1)
   RETURN(1) -- Failure
  END

  -- Make sure the schedule_id can be uniquely identified and that it exists
  -- Note: It's safe use the values returned by the MIN() function because the SP errors out if more than 1 record exists
  SELECT @sched_count = COUNT(*),
         @schedule_id = MIN(sched.schedule_id),
         @owner_sid   = MIN(sched.owner_sid)
  FROM msdb.dbo.sysjobschedules as jsched
    JOIN msdb.dbo.sysschedules_localserver_view as sched
      ON jsched.schedule_id = sched.schedule_id
  WHERE (jsched.job_id = @job_id)
    AND (sched.name = @name)

  -- Need to use sp_update_schedule to update this ambiguous schedule name
  IF(@sched_count > 1)
  BEGIN
    RAISERROR(14375, -1, -1, @name, @job_name)
    RETURN(1) -- Failure
  END

  IF (@schedule_id IS NULL)
  BEGIN
   --raise an explicit message if the schedule does exist but isn't attached to this job
   IF EXISTS(SELECT *
           FROM sysschedules_localserver_view
              WHERE (name = @name))
   BEGIN
      RAISERROR(14374, -1, -1, @name, @job_name)
   END
   ELSE
    BEGIN
      --If the schedule is from an MSX and a sysadmin is calling report a specific 'MSX' message
      IF(PROGRAM_NAME() NOT LIKE N'SQLAgent%' AND
         ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1 AND
         EXISTS(SELECT *
                FROM msdb.dbo.sysschedules as sched
                  JOIN msdb.dbo.sysoriginatingservers_view as svr
                    ON sched.originating_server_id = svr.originating_server_id
                  JOIN msdb.dbo.sysjobschedules as js
                    ON sched.schedule_id = js.schedule_id
                WHERE (svr.master_server = 1) AND
                      (sched.name = @name) AND
                      (js.job_id = @job_id)))
     BEGIN
       RAISERROR(14274, -1, -1)
     END
      ELSE
      BEGIN
        --Generic message that the schedule doesn't exist
        RAISERROR(14262, -1, -1, 'Schedule Name', @name)
      END
   END

   RETURN(1) -- Failure
  END

  -- Set the x_ (existing) variables
  SELECT @x_name                   = name,
         @x_enabled                = enabled,
         @x_freq_type              = freq_type,
         @x_freq_interval          = freq_interval,
         @x_freq_subday_type       = freq_subday_type,
         @x_freq_subday_interval   = freq_subday_interval,
         @x_freq_relative_interval = freq_relative_interval,
         @x_freq_recurrence_factor = freq_recurrence_factor,
         @x_active_start_date      = active_start_date,
         @x_active_end_date        = active_end_date,
         @x_active_start_time      = active_start_time,
         @x_active_end_time        = active_end_time
  FROM msdb.dbo.sysschedules_localserver_view
  WHERE (schedule_id = @schedule_id )


  -- Fill out the values for all non-supplied parameters from the existing values
  IF (@new_name               IS NULL) SELECT @new_name               = @x_name
  IF (@enabled                IS NULL) SELECT @enabled                = @x_enabled
  IF (@freq_type              IS NULL) SELECT @freq_type              = @x_freq_type
  IF (@freq_interval          IS NULL) SELECT @freq_interval          = @x_freq_interval
  IF (@freq_subday_type       IS NULL) SELECT @freq_subday_type       = @x_freq_subday_type
  IF (@freq_subday_interval   IS NULL) SELECT @freq_subday_interval   = @x_freq_subday_interval
  IF (@freq_relative_interval IS NULL) SELECT @freq_relative_interval = @x_freq_relative_interval
  IF (@freq_recurrence_factor IS NULL) SELECT @freq_recurrence_factor = @x_freq_recurrence_factor
  IF (@active_start_date      IS NULL) SELECT @active_start_date      = @x_active_start_date
  IF (@active_end_date        IS NULL) SELECT @active_end_date        = @x_active_end_date
  IF (@active_start_time      IS NULL) SELECT @active_start_time      = @x_active_start_time
  IF (@active_end_time        IS NULL) SELECT @active_end_time        = @x_active_end_time

  -- Check schedule (frequency and owner) parameters
  EXECUTE @retval = sp_verify_schedule @schedule_id             = @schedule_id,
                                       @name                    = @new_name,
                                       @enabled                 = @enabled,
                                       @freq_type               = @freq_type,
                                       @freq_interval           = @freq_interval            OUTPUT,
                                       @freq_subday_type        = @freq_subday_type         OUTPUT,
                                       @freq_subday_interval    = @freq_subday_interval     OUTPUT,
                                       @freq_relative_interval  = @freq_relative_interval   OUTPUT,
                                       @freq_recurrence_factor  = @freq_recurrence_factor   OUTPUT,
                                       @active_start_date       = @active_start_date        OUTPUT,
                                       @active_start_time       = @active_start_time        OUTPUT,
                                       @active_end_date         = @active_end_date          OUTPUT,
                                       @active_end_time         = @active_end_time          OUTPUT,
                                       @owner_sid               = @owner_sid
  IF (@retval <> 0)
    RETURN(1) -- Failure


  -- Update the JobSchedule
  UPDATE msdb.dbo.sysschedules
  SET name                   = @new_name,
      enabled                = @enabled,
      freq_type              = @freq_type,
      freq_interval          = @freq_interval,
      freq_subday_type       = @freq_subday_type,
      freq_subday_interval   = @freq_subday_interval,
      freq_relative_interval = @freq_relative_interval,
      freq_recurrence_factor = @freq_recurrence_factor,
      active_start_date      = @active_start_date,
      active_end_date        = @active_end_date,
      active_start_time      = @active_start_time,
      active_end_time        = @active_end_time,
      date_modified          = GETDATE(),
      version_number         = version_number + 1
  WHERE (schedule_id = @schedule_id)

  SELECT @retval = @@error

  -- Update the job's version/last-modified information
  UPDATE msdb.dbo.sysjobs
  SET version_number = version_number + 1,
      date_modified = GETDATE()
  WHERE (job_id = @job_id)

  -- Notify SQLServerAgent of the change, but only if we know the job has been cached
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id = 0)))
  BEGIN
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                        @job_id      = @job_id,
                                        @schedule_id = @schedule_id,
                                        @action_type = N'U'
  END

  -- For a multi-server job, remind the user that they need to call sp_post_msx_operation
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
    -- Instruct the tsx servers to pick up the altered schedule
    IF (@automatic_post = 1)
    BEGIN
      DECLARE @schedule_uid UNIQUEIDENTIFIER
      SELECT @schedule_uid = schedule_uid
      FROM sysschedules
      WHERE schedule_id = @schedule_id

      IF(NOT @schedule_uid IS NULL)
      BEGIN
        -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines
        EXECUTE sp_post_msx_operation @operation = 'INSERT', @object_type = 'SCHEDULE', @schedule_uid = @schedule_uid
      END
    END
    ELSE
      RAISERROR(14547, 0, 1, N'INSERT', N'sp_post_msx_operation')

  -- Automatic addition and removal of -Continous parameter for replication agent
  EXECUTE sp_update_replication_job_parameter @job_id = @job_id,
                                              @old_freq_type = @x_freq_type,
                                              @new_freq_type = @freq_type

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_JOBSCHEDULE                                      */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_jobschedule...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_delete_jobschedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_jobschedule
go
CREATE PROCEDURE sp_delete_jobschedule           -- This SP is deprecated. Use sp_detach_schedule and sp_delete_schedule.
  @job_id           UNIQUEIDENTIFIER = NULL,
  @job_name         sysname          = NULL,
  @name             sysname,
  @keep_schedule    int              = 0,
  @automatic_post       BIT          = 1         -- If 1 will post notifications to all tsx servers to that run this schedule
AS
BEGIN
  DECLARE @retval           INT
  DECLARE @sched_count      INT
  DECLARE @schedule_id      INT
  DECLARE @job_owner_sid    VARBINARY(85)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name = LTRIM(RTRIM(@name))

  -- Check authority (only SQLServerAgent can delete a schedule of a non-local job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- Check that we can uniquely identify the job
  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                               @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) AND
      (SUSER_SID() <> @job_owner_sid))
  BEGIN
   RAISERROR(14525, -1, -1)
   RETURN(1) -- Failure
  END


  IF (UPPER(@name collate SQL_Latin1_General_CP1_CS_AS) = N'ALL')
  BEGIN
    SELECT @schedule_id = -1  -- We use this in the call to sp_sqlagent_notify

    --Delete the schedule(s) if it isn't being used by other jobs
    DECLARE @temp_schedules_to_delete TABLE (schedule_id INT NOT NULL)


    --If user requests that the schedules be removed (the legacy behavoir)
    --make sure it isnt being used by other jobs
    IF (@keep_schedule = 0)
    BEGIN
        --Get the list of schedules to delete
        INSERT INTO @temp_schedules_to_delete
        SELECT DISTINCT schedule_id
        FROM   msdb.dbo.sysschedules
        WHERE (schedule_id IN
                (SELECT schedule_id
                FROM msdb.dbo.sysjobschedules
                WHERE (job_id = @job_id)))

        --make sure no other jobs use these schedules
        IF( EXISTS(SELECT *
                    FROM msdb.dbo.sysjobschedules
                    WHERE (job_id <> @job_id)
                    AND (schedule_id in ( SELECT schedule_id
                                            FROM @temp_schedules_to_delete ))))
        BEGIN
        RAISERROR(14367, -1, -1)
        RETURN(1) -- Failure
        END
    END

    --OK to delete the jobschedule
    DELETE FROM msdb.dbo.sysjobschedules
    WHERE (job_id = @job_id)

    --OK to delete the schedule - temp_schedules_to_delete is empty if @keep_schedule <> 0
    DELETE FROM msdb.dbo.sysschedules
    WHERE schedule_id IN
    (SELECT schedule_id from @temp_schedules_to_delete)
  END
  ELSE
  BEGIN

    -- Make sure the schedule_id can be uniquely identified and that it exists
    -- Note: It's safe use the values returned by the MIN() function because the SP errors out if more than 1 record exists
    SELECT @sched_count = COUNT(*),
           @schedule_id = MIN(sched.schedule_id)
    FROM msdb.dbo.sysjobschedules as jsched
      JOIN msdb.dbo.sysschedules_localserver_view as sched
        ON jsched.schedule_id = sched.schedule_id
    WHERE (jsched.job_id = @job_id)
      AND (sched.name = @name)

    -- Need to use sp_detach_schedule to remove this ambiguous schedule name
    IF(@sched_count > 1)
    BEGIN
      RAISERROR(14376, -1, -1, @name, @job_name)
      RETURN(1) -- Failure
    END

    IF (@schedule_id IS NULL)
    BEGIN
     --raise an explicit message if the schedule does exist but isn't attached to this job
     IF EXISTS(SELECT *
             FROM sysschedules_localserver_view
                WHERE (name = @name))
     BEGIN
      RAISERROR(14374, -1, -1, @name, @job_name)
     END
     ELSE
      BEGIN
        --If the schedule is from an MSX and a sysadmin is calling report a specific 'MSX' message
        IF(PROGRAM_NAME() NOT LIKE N'SQLAgent%' AND
           ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1 AND
           EXISTS(SELECT *
                  FROM msdb.dbo.sysschedules as sched
                    JOIN msdb.dbo.sysoriginatingservers_view as svr
                      ON sched.originating_server_id = svr.originating_server_id
                    JOIN msdb.dbo.sysjobschedules as js
                      ON sched.schedule_id = js.schedule_id
                  WHERE (svr.master_server = 1) AND
                        (sched.name = @name) AND
                        (js.job_id = @job_id)))
       BEGIN
         RAISERROR(14274, -1, -1)
       END
        ELSE
        BEGIN
          --Generic message that the schedule doesn't exist
          RAISERROR(14262, -1, -1, '@name', @name)
        END
     END

      RETURN(1) -- Failure
    END

    --If user requests that the schedule be removed (the legacy behavoir)
    --make sure it isnt being used by another job
    IF (@keep_schedule = 0)
    BEGIN
      IF( EXISTS(SELECT *
                 FROM msdb.dbo.sysjobschedules
                 WHERE (schedule_id = @schedule_id)
                   AND (job_id <> @job_id) ))
      BEGIN
        RAISERROR(14368, -1, -1, @name)
        RETURN(1) -- Failure
      END
    END

    --Delete the job schedule link first
    DELETE FROM msdb.dbo.sysjobschedules
    WHERE (job_id = @job_id)
    AND (schedule_id = @schedule_id)
    --Delete schedule if required
    IF (@keep_schedule = 0)
    BEGIN
      --Now delete the schedule if required
      DELETE FROM msdb.dbo.sysschedules
      WHERE (schedule_id = @schedule_id)
    END

  END


  -- Update the job's version/last-modified information
  UPDATE msdb.dbo.sysjobs
  SET version_number = version_number + 1,
      date_modified = GETDATE()
  WHERE (job_id = @job_id)

  -- Notify SQLServerAgent of the change, but only if we know the job has been cached
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id = 0)))
  BEGIN
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'S',
                                        @job_id      = @job_id,
                                        @schedule_id = @schedule_id,
                                        @action_type = N'D'
  END

  -- For a multi-server job, remind the user that they need to call sp_post_msx_operation
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
    -- Instruct the tsx servers to pick up the altered schedule
    IF (@automatic_post = 1)
    BEGIN
      DECLARE @schedule_uid UNIQUEIDENTIFIER
      SELECT @schedule_uid = schedule_uid
      FROM sysschedules
      WHERE schedule_id = @schedule_id

      IF(NOT @schedule_uid IS NULL)
      BEGIN
        -- sp_post_msx_operation will do nothing if the schedule isn't assigned to any tsx machines
        EXECUTE sp_post_msx_operation @operation = 'INSERT', @object_type = 'SCHEDULE', @schedule_uid = @schedule_uid
      END
    END
    ELSE
      RAISERROR(14547, 0, 1, N'INSERT', N'sp_post_msx_operation')

  RETURN(@retval) -- 0 means success
END
go


/**************************************************************/
/* SP_HELP_SCHEDULE                                           */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_schedule...'
go

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_schedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_schedule
go

CREATE PROCEDURE sp_help_schedule
  @schedule_id              INT     = NULL, -- If both @schedule_id and @schedule_name are NULL retreive all schedules
  @schedule_name            sysname = NULL,
  @attached_schedules_only  BIT     = 0,    -- If 1 only retreive all schedules that are attached to jobs
  @include_description      BIT     = 0     -- 1 if a schedule description is required (NOTE: It's expensive to generate the description)
AS
BEGIN
  DECLARE @retval                 INT
  DECLARE @schedule_description   NVARCHAR(255)
  DECLARE @name                   sysname
  DECLARE @freq_type              INT
  DECLARE @freq_interval          INT
  DECLARE @freq_subday_type       INT
  DECLARE @freq_subday_interval   INT
  DECLARE @freq_relative_interval INT
  DECLARE @freq_recurrence_factor INT
  DECLARE @active_start_date      INT
  DECLARE @active_end_date        INT
  DECLARE @active_start_time      INT
  DECLARE @active_end_time        INT
  DECLARE @schedule_id_as_char    VARCHAR(10)

  SET NOCOUNT ON

  -- If both @schedule_id and @schedule_name are NULL retreive all schedules (depending on @attached_schedules_only)
  -- otherwise verify the schedule exists
  IF (@schedule_id IS NOT NULL) OR (@schedule_name IS NOT NULL)
  BEGIN
    -- Check that we can uniquely identify the schedule
    EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                              @name_of_id_parameter   = '@schedule_id',
                                                              @schedule_name          = @schedule_name OUTPUT,
                                                              @schedule_id            = @schedule_id   OUTPUT,
                                                              @owner_sid              = NULL,
                                                              @orig_server_id         = NULL
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END


  -- Get the schedule(s) that are attached to a job (or all schs if @attached_schedules_only = 0) into a temporary table
  SELECT schedule_id,
         schedule_uid,
        'schedule_name' = name,
         enabled,
         freq_type,
         freq_interval,
         freq_subday_type,
         freq_subday_interval,
         freq_relative_interval,
         freq_recurrence_factor,
         active_start_date,
         active_end_date,
         active_start_time,
         active_end_time,
         date_created,
        'schedule_description' = FORMATMESSAGE(14549)
  INTO #temp_jobschedule
  FROM msdb.dbo.sysschedules_localserver_view as sch
  WHERE ( (@attached_schedules_only = 0)
         OR (EXISTS(SELECT * FROM sysjobschedules as jobsch WHERE sch.schedule_id = jobsch.schedule_id)) )
    AND((@schedule_id IS NULL) OR (schedule_id = @schedule_id))

  IF (@include_description = 1)
  BEGIN
    -- For each schedule, generate the textual schedule description and update the temporary
    -- table with it
    IF (EXISTS (SELECT *
                FROM #temp_jobschedule))
    BEGIN
      WHILE (EXISTS (SELECT *
                     FROM #temp_jobschedule
                     WHERE schedule_description = FORMATMESSAGE(14549)))
      BEGIN
        SET ROWCOUNT 1
        SELECT @name                   = schedule_name,
               @freq_type              = freq_type,
               @freq_interval          = freq_interval,
               @freq_subday_type       = freq_subday_type,
               @freq_subday_interval   = freq_subday_interval,
               @freq_relative_interval = freq_relative_interval,
               @freq_recurrence_factor = freq_recurrence_factor,
               @active_start_date      = active_start_date,
               @active_end_date        = active_end_date,
               @active_start_time      = active_start_time,
               @active_end_time        = active_end_time
        FROM #temp_jobschedule
        WHERE (schedule_description = FORMATMESSAGE(14549))
        SET ROWCOUNT 0

        EXECUTE sp_get_schedule_description
          @freq_type,
          @freq_interval,
          @freq_subday_type,
          @freq_subday_interval,
          @freq_relative_interval,
          @freq_recurrence_factor,
          @active_start_date,
          @active_end_date,
          @active_start_time,
          @active_end_time,
          @schedule_description OUTPUT

        UPDATE #temp_jobschedule
        SET schedule_description = ISNULL(LTRIM(RTRIM(@schedule_description)), FORMATMESSAGE(14205))
        WHERE (schedule_name = @name)
      END -- While
    END
  END

  -- Return the result set, adding job count
  SELECT *, (SELECT COUNT(*) FROM sysjobschedules WHERE sysjobschedules.schedule_id = #temp_jobschedule.schedule_id) as 'job_count'
  FROM #temp_jobschedule
  ORDER BY schedule_id

  RETURN(@@error) -- 0 means success
END
go


/**************************************************************/
/* SP_HELP_JOBSCHEDULE                                        */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_jobschedule...'

go


IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobschedule')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobschedule

go

CREATE PROCEDURE sp_help_jobschedule
  @job_id              UNIQUEIDENTIFIER = NULL,
  @job_name            sysname          = NULL,
  @schedule_name       sysname          = NULL,
  @schedule_id         INT              = NULL,
  @include_description BIT              = 0 -- 1 if a schedule description is required (NOTE: It's expensive to generate the description)
AS
BEGIN
  DECLARE @retval                 INT
  DECLARE @schedule_description   NVARCHAR(255)
  DECLARE @name                   sysname
  DECLARE @freq_type              INT
  DECLARE @freq_interval          INT
  DECLARE @freq_subday_type       INT
  DECLARE @freq_subday_interval   INT
  DECLARE @freq_relative_interval INT
  DECLARE @freq_recurrence_factor INT
  DECLARE @active_start_date      INT
  DECLARE @active_end_date        INT
  DECLARE @active_start_time      INT
  DECLARE @active_end_time        INT
  DECLARE @schedule_id_as_char    VARCHAR(10)
  DECLARE @job_count               INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @schedule_name = LTRIM(RTRIM(@schedule_name))
  SELECT @job_count = 0

  -- Turn [nullable] empty string parameters into NULLs
  IF (@schedule_name = N'') SELECT @schedule_name = NULL

  -- The user must provide either:
  -- 1) job_id (or job_name) and (optionally) a schedule name
  -- or...
  -- 2) just schedule_id
  IF (@schedule_id IS NULL) AND
     (@job_id      IS NULL) AND
     (@job_name    IS NULL)
  BEGIN
    RAISERROR(14273, -1, -1)
    RETURN(1) -- Failure
  END

  IF (@schedule_id IS NOT NULL) AND ((@job_id        IS NOT NULL) OR
                                     (@job_name      IS NOT NULL) OR
                                     (@schedule_name IS NOT NULL))
  BEGIN
    RAISERROR(14273, -1, -1)
    RETURN(1) -- Failure
  END

  -- Check that the schedule (by ID) exists and it is only used by one job.
  -- Allowing this for backward compatibility with versions prior to V9
  IF (@schedule_id IS NOT NULL) AND
     (@job_id      IS NULL) AND
     (@job_name    IS NULL)
  BEGIN

    SELECT @job_count = COUNT(*)
    FROM msdb.dbo.sysjobschedules
    WHERE (schedule_id = @schedule_id)

    if(@job_count > 1)
    BEGIN
      SELECT @schedule_id_as_char = CONVERT(VARCHAR, @schedule_id)
      RAISERROR(14369, -1, -1, @schedule_id_as_char)
      RETURN(1) -- Failure
    END

    SELECT @job_id = job_id
    FROM msdb.dbo.sysjobschedules
    WHERE (schedule_id = @schedule_id)
    IF (@job_id IS NULL)
    BEGIN
      SELECT @schedule_id_as_char = CONVERT(VARCHAR, @schedule_id)
      RAISERROR(14262, -1, -1, '@schedule_id', @schedule_id_as_char)
      RETURN(1) -- Failure
    END
  END

  -- Check that we can uniquely identify the job
  IF (@job_id IS NOT NULL) OR (@job_name IS NOT NULL)
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT,
                                                'NO_TEST'
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  IF (@schedule_id IS NOT NULL OR @schedule_name IS NOT NULL)
  BEGIN
    -- Check that we can uniquely identify the schedule
    EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                              @name_of_id_parameter   = '@schedule_id',
                                                              @schedule_name          = @schedule_name OUTPUT,
                                                              @schedule_id            = @schedule_id   OUTPUT,
                                                              @owner_sid              = NULL,
                                                              @orig_server_id         = NULL,
                                                              @job_id_filter          = @job_id
    IF (@retval <> 0)
      RETURN(1) -- Failure

  END

  -- Check that the schedule (by name) exists
  IF (@schedule_name IS NOT NULL)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysjobschedules AS js
                      JOIN msdb.dbo.sysschedules AS s
                        ON js.schedule_id = s.schedule_id
                    WHERE (js.job_id = @job_id)
                      AND (s.name = @schedule_name)))
    BEGIN
      RAISERROR(14262, -1, -1, '@schedule_name', @schedule_name)
      RETURN(1) -- Failure
    END
  END

  -- Get the schedule(s) into a temporary table
  SELECT s.schedule_id,
        'schedule_name' = name,
         enabled,
         freq_type,
         freq_interval,
         freq_subday_type,
         freq_subday_interval,
         freq_relative_interval,
         freq_recurrence_factor,
         active_start_date,
         active_end_date,
         active_start_time,
         active_end_time,
         date_created,
        'schedule_description' = FORMATMESSAGE(14549),
         js.next_run_date,
         js.next_run_time,
         s.schedule_uid
  INTO #temp_jobschedule
  FROM msdb.dbo.sysjobschedules AS js
    JOIN msdb.dbo.sysschedules AS s
      ON js.schedule_id = s.schedule_id
  WHERE ((@job_id IS NULL) OR (js.job_id = @job_id))
    AND ((@schedule_name IS NULL) OR (s.name = @schedule_name))
    AND ((@schedule_id IS NULL) OR (s.schedule_id = @schedule_id))

  IF (@include_description = 1)
  BEGIN
    -- For each schedule, generate the textual schedule description and update the temporary
    -- table with it
    IF (EXISTS (SELECT *
                FROM #temp_jobschedule))
    BEGIN
      WHILE (EXISTS (SELECT *
                     FROM #temp_jobschedule
                     WHERE schedule_description = FORMATMESSAGE(14549)))
      BEGIN
        SET ROWCOUNT 1
        SELECT @name                   = schedule_name,
               @freq_type              = freq_type,
               @freq_interval          = freq_interval,
               @freq_subday_type       = freq_subday_type,
               @freq_subday_interval   = freq_subday_interval,
               @freq_relative_interval = freq_relative_interval,
               @freq_recurrence_factor = freq_recurrence_factor,
               @active_start_date      = active_start_date,
               @active_end_date        = active_end_date,
               @active_start_time      = active_start_time,
               @active_end_time        = active_end_time
        FROM #temp_jobschedule
        WHERE (schedule_description = FORMATMESSAGE(14549))
        SET ROWCOUNT 0

        EXECUTE sp_get_schedule_description
          @freq_type,
          @freq_interval,
          @freq_subday_type,
          @freq_subday_interval,
          @freq_relative_interval,
          @freq_recurrence_factor,
          @active_start_date,
          @active_end_date,
          @active_start_time,
          @active_end_time,
          @schedule_description OUTPUT

        UPDATE #temp_jobschedule
        SET schedule_description = ISNULL(LTRIM(RTRIM(@schedule_description)), FORMATMESSAGE(14205))
        WHERE (schedule_name = @name)
      END -- While
    END
  END

  -- Return the result set, adding job count to it
  SELECT *, (SELECT COUNT(*) FROM sysjobschedules WHERE sysjobschedules.schedule_id = #temp_jobschedule.schedule_id) as 'job_count'
  FROM #temp_jobschedule
  ORDER BY schedule_id

  RETURN(@@error) -- 0 means success
END

go

CHECKPOINT
go

/**************************************************************/
/* SP_VERIFY_JOB                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_job
go

CREATE PROCEDURE sp_verify_job
  @job_id                       UNIQUEIDENTIFIER,
  @name                         sysname,
  @enabled                      TINYINT,
  @start_step_id                INT,
  @category_name                sysname,
  @owner_sid                    VARBINARY(85) OUTPUT, -- Output since we may modify it
  @notify_level_eventlog        INT,
  @notify_level_email           INT           OUTPUT, -- Output since we may reset it to 0
  @notify_level_netsend         INT           OUTPUT, -- Output since we may reset it to 0
  @notify_level_page            INT           OUTPUT, -- Output since we may reset it to 0
  @notify_email_operator_name   sysname,
  @notify_netsend_operator_name sysname,
  @notify_page_operator_name    sysname,
  @delete_level                 INT,
  @category_id                  INT           OUTPUT, -- The ID corresponding to the name
  @notify_email_operator_id     INT           OUTPUT, -- The ID corresponding to the name
  @notify_netsend_operator_id   INT           OUTPUT, -- The ID corresponding to the name
  @notify_page_operator_id      INT           OUTPUT, -- The ID corresponding to the name
  @originating_server           sysname       OUTPUT  -- Output since we may modify it
AS
BEGIN
  DECLARE @job_type           INT
  DECLARE @retval             INT
  DECLARE @current_date       INT
  DECLARE @res_valid_range    NVARCHAR(200)

  SET NOCOUNT ON

  -- If this is Azure SQL Database - Managed Instance disable eventlog
  IF (SERVERPROPERTY('EngineEdition') = 8)
    SET @notify_level_eventlog = 0

  -- Remove any leading/trailing spaces from parameters
  SELECT @name                       = LTRIM(RTRIM(@name))
  SELECT @category_name              = LTRIM(RTRIM(@category_name))
  SELECT @originating_server         = UPPER(LTRIM(RTRIM(@originating_server)))

  SELECT @originating_server = ISNULL(@originating_server, UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))))

  -- Check originating server (only the SQLServerAgent can add jobs that originate from a remote server)
  IF (@originating_server <> UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))) AND
     (PROGRAM_NAME() NOT LIKE N'SQLAgent%')
  BEGIN
    RAISERROR(14275, -1, -1)
    RETURN(1) -- Failure
  END

  -- NOTE: We allow jobs with the same name (since job_id is always unique) but only if
  --       they originate from different servers.  Thus jobs can flow from an MSX to a TSX
  --       without having to worry about naming conflicts.
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobs as job
                JOIN msdb.dbo.sysoriginatingservers_view as svr
                  ON (svr.originating_server_id = job.originating_server_id)
              WHERE (name = @name)
                AND (svr.originating_server = @originating_server)
                AND (job_id <> ISNULL(@job_id, 0x911)))) -- When adding a new job @job_id is NULL
  BEGIN
    RAISERROR(14261, -1, -1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Check enabled state
  IF (@enabled <> 0) AND (@enabled <> 1)
  BEGIN
    RAISERROR(14266, -1, -1, '@enabled', '0, 1')
    RETURN(1) -- Failure
  END

  -- Check start step
  IF (@job_id IS NULL)
  BEGIN
    -- New job
    -- NOTE: For [new] MSX jobs we allow the start step to be other than 1 since
    --       the start step was validated when the job was created at the MSX
    IF (@start_step_id <> 1) AND (@originating_server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))))
    BEGIN
      RAISERROR(14266, -1, -1, '@start_step_id', '1')
      RETURN(1) -- Failure
    END
  END
  ELSE
  BEGIN
    -- Existing job
    DECLARE @max_step_id INT
    DECLARE @valid_range VARCHAR(50)

    -- Get current maximum step id
    SELECT @max_step_id = ISNULL(MAX(step_id), 0)
    FROM msdb.dbo.sysjobsteps
    WHERE (job_id = @job_id)

    IF (@start_step_id < 1) OR (@start_step_id > @max_step_id + 1)
    BEGIN
      SELECT @valid_range = '1..' + CONVERT(VARCHAR, @max_step_id + 1)
      RAISERROR(14266, -1, -1, '@start_step_id', @valid_range)
      RETURN(1) -- Failure
    END
  END

  -- Check category
  SELECT @job_type = NULL

  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id = 0)))
    SELECT @job_type = 1 -- LOCAL

  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id <> 0)))
    SELECT @job_type = 2 -- MULTI-SERVER

  -- A local job cannot be added to a multi-server job_category
  IF (@job_type = 1) AND (EXISTS (SELECT *
                                  FROM msdb.dbo.syscategories
                                  WHERE (category_class = 1) -- Job
                                    AND (category_type = 2) -- Multi-Server
                                    AND (name = @category_name)))
  BEGIN
    RAISERROR(14285, -1, -1)
    RETURN(1) -- Failure
  END

  -- A multi-server job cannot be added to a local job_category
  IF (@job_type = 2) AND (EXISTS (SELECT *
                                  FROM msdb.dbo.syscategories
                                  WHERE (category_class = 1) -- Job
                                    AND (category_type = 1) -- Local
                                    AND (name = @category_name)))
  BEGIN
    RAISERROR(14286, -1, -1)
    RETURN(1) -- Failure
  END

  -- Get the category_id, handling any special-cases as appropriate
  SELECT @category_id = NULL
  IF (@category_name = N'[DEFAULT]') -- User wants to revert to the default job category
  BEGIN
    SELECT @category_id = CASE ISNULL(@job_type, 1)
                            WHEN 1 THEN 0 -- [Uncategorized (Local)]
                            WHEN 2 THEN 2 -- [Uncategorized (Multi-Server)]
                          END
  END
  ELSE
  IF (@category_name IS NULL) -- The sp_add_job default
  BEGIN
    SELECT @category_id = 0
  END
  ELSE
  BEGIN
    SELECT @category_id = category_id
    FROM msdb.dbo.syscategories
    WHERE (category_class = 1) -- Job
      AND (name = @category_name)
  END

  IF (@category_id IS NULL)
  BEGIN
    RAISERROR(14234, -1, -1, '@category_name', 'sp_help_category')
    RETURN(1) -- Failure
  END

  -- Only SQLServerAgent may add jobs to the 'Jobs From MSX' category
  IF (@category_id = 1) AND
     (PROGRAM_NAME() NOT LIKE N'SQLAgent%')
  BEGIN
    RAISERROR(14267, -1, -1, @category_name)
    RETURN(1) -- Failure
  END

  -- Check owner
  -- Default the owner to be the calling user if:
  -- caller is not a sysadmin
  -- caller is not SQLAgentOperator and job_id is NULL, meaning new job
  IF (((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0) AND
      ((ISNULL(IS_MEMBER('SQLAgentOperatorRole'), 0) = 0) AND @job_id IS NULL)) AND
      (@owner_sid <> SUSER_SID()))
  BEGIN
    SELECT @owner_sid = SUSER_SID()
  END

  -- Now just check that the login id is valid (ie. it exists and isn't an NT group)
  IF ((@owner_sid <> 0x010100000000000512000000) AND -- NT AUTHORITY\SYSTEM sid
     (@owner_sid <> 0x010100000000000514000000)) OR  -- NT AUTHORITY\NETWORK SERVICE sid
     (@owner_sid IS NULL)
  BEGIN
     IF (@owner_sid IS NULL OR (EXISTS (SELECT sid
                                      FROM sys.syslogins
                                      WHERE sid = @owner_sid
                                        AND isntgroup <> 0)))
     BEGIN
       -- NOTE: In the following message we quote @owner_login_name instead of @owner_sid
       --       since this is the parameter the user passed to the calling SP (ie. either
       --       sp_add_job or sp_update_job)
       SELECT @res_valid_range = FORMATMESSAGE(14203)
       RAISERROR(14234, -1, -1, '@owner_login_name', @res_valid_range)
       RETURN(1) -- Failure
     END
  END

  -- Check notification levels (must be 0, 1, 2 or 3)
  IF (@notify_level_eventlog & 0x3 <> @notify_level_eventlog)
  BEGIN
    RAISERROR(14266, -1, -1, '@notify_level_eventlog', '0, 1, 2, 3')
    RETURN(1) -- Failure
  END
  IF (@notify_level_email & 0x3 <> @notify_level_email)
  BEGIN
    RAISERROR(14266, -1, -1, '@notify_level_email', '0, 1, 2, 3')
    RETURN(1) -- Failure
  END
  IF (@notify_level_netsend & 0x3 <> @notify_level_netsend)
  BEGIN
    RAISERROR(14266, -1, -1, '@notify_level_netsend', '0, 1, 2, 3')
    RETURN(1) -- Failure
  END
  IF (@notify_level_page & 0x3 <> @notify_level_page)
  BEGIN
    RAISERROR(14266, -1, -1, '@notify_level_page', '0, 1, 2, 3')
    RETURN(1) -- Failure
  END

  -- If we're at a TSX, only SQLServerAgent may add jobs that notify 'MSXOperator'
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.systargetservers)) AND
     ((@notify_email_operator_name = N'MSXOperator') OR
      (@notify_page_operator_name = N'MSXOperator') OR
      (@notify_netsend_operator_name = N'MSXOperator')) AND
     (PROGRAM_NAME() NOT LIKE N'SQLAgent%')
  BEGIN
    RAISERROR(14251, -1, -1, 'MSXOperator')
    RETURN(1) -- Failure
  END

  -- Check operator to notify (via email)
  IF (@notify_email_operator_name IS NOT NULL)
  BEGIN
    SELECT @notify_email_operator_id = id
    FROM msdb.dbo.sysoperators
    WHERE (name = @notify_email_operator_name)

    IF (@notify_email_operator_id IS NULL)
    BEGIN
      RAISERROR(14234, -1, -1, '@notify_email_operator_name', 'sp_help_operator')
      RETURN(1) -- Failure
    END
    -- If a valid operator is specified the level must be non-zero
    IF (@notify_level_email = 0)
    BEGIN
      RAISERROR(14266, -1, -1, '@notify_level_email', '1, 2, 3')
      RETURN(1) -- Failure
    END
  END
  ELSE
  BEGIN
    SELECT @notify_email_operator_id = 0
    SELECT @notify_level_email = 0
  END

  -- Check operator to notify (via netsend)
  IF (@notify_netsend_operator_name IS NOT NULL)
  BEGIN
    SELECT @notify_netsend_operator_id = id
    FROM msdb.dbo.sysoperators
    WHERE (name = @notify_netsend_operator_name)

    IF (@notify_netsend_operator_id IS NULL)
    BEGIN
      RAISERROR(14234, -1, -1, '@notify_netsend_operator_name', 'sp_help_operator')
      RETURN(1) -- Failure
    END
    -- If a valid operator is specified the level must be non-zero
    IF (@notify_level_netsend = 0)
    BEGIN
      RAISERROR(14266, -1, -1, '@notify_level_netsend', '1, 2, 3')
      RETURN(1) -- Failure
    END
  END
  ELSE
  BEGIN
    SELECT @notify_netsend_operator_id = 0
    SELECT @notify_level_netsend = 0
  END

  -- Check operator to notify (via page)
  IF (@notify_page_operator_name IS NOT NULL)
  BEGIN
    SELECT @notify_page_operator_id = id
    FROM msdb.dbo.sysoperators
    WHERE (name = @notify_page_operator_name)

    IF (@notify_page_operator_id IS NULL)
    BEGIN
      RAISERROR(14234, -1, -1, '@notify_page_operator_name', 'sp_help_operator')
      RETURN(1) -- Failure
    END
    -- If a valid operator is specified the level must be non-zero
    IF (@notify_level_page = 0)
    BEGIN
      RAISERROR(14266, -1, -1, '@notify_level_page', '1, 2, 3')
      RETURN(1) -- Failure
    END
  END
  ELSE
  BEGIN
    SELECT @notify_page_operator_id = 0
    SELECT @notify_level_page = 0
  END

  -- Check delete level (must be 0, 1, 2 or 3)
  IF (@delete_level & 0x3 <> @delete_level)
  BEGIN
    RAISERROR(14266, -1, -1, '@delete_level', '0, 1, 2, 3')
    RETURN(1) -- Failure
  END

  RETURN(0) -- Success
END

go

/**************************************************************/
/* SP_ADD_JOB                                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_job
go
CREATE PROCEDURE sp_add_job
  @job_name                     sysname,
  @enabled                      TINYINT          = 1,        -- 0 = Disabled, 1 = Enabled
  @description                  NVARCHAR(512)    = NULL,
  @start_step_id                INT              = 1,
  @category_name                sysname          = NULL,
  @category_id                  INT              = NULL,     -- A language-independent way to specify which category to use
  @owner_login_name             sysname          = NULL,     -- The procedure assigns a default
  @notify_level_eventlog        INT              = 2,        -- 0 = Never, 1 = On Success, 2 = On Failure, 3 = Always
  @notify_level_email           INT              = 0,        -- 0 = Never, 1 = On Success, 2 = On Failure, 3 = Always
  @notify_level_netsend         INT              = 0,        -- 0 = Never, 1 = On Success, 2 = On Failure, 3 = Always
  @notify_level_page            INT              = 0,        -- 0 = Never, 1 = On Success, 2 = On Failure, 3 = Always
  @notify_email_operator_name   sysname          = NULL,
  @notify_netsend_operator_name sysname          = NULL,
  @notify_page_operator_name    sysname          = NULL,
  @delete_level                 INT              = 0,        -- 0 = Never, 1 = On Success, 2 = On Failure, 3 = Always
  @job_id                       UNIQUEIDENTIFIER = NULL OUTPUT,
  @originating_server           sysname           = NULL      -- For SQLAgent use only
AS
BEGIN
  DECLARE @retval                     INT
  DECLARE @notify_email_operator_id   INT
  DECLARE @notify_netsend_operator_id INT
  DECLARE @notify_page_operator_id    INT
  DECLARE @owner_sid                  VARBINARY(85)
  DECLARE @originating_server_id      INT

  SET NOCOUNT ON

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (SERVERPROPERTY('EngineEdition') = 8)
  BEGIN
    IF (@notify_level_netsend <> 0)
    BEGIN
      RAISERROR(41914, -1, 9, 'NetSend');
      RETURN(1) -- Failure
    END
    SET @notify_level_eventlog = 0
  END

  -- Remove any leading/trailing spaces from parameters (except @owner_login_name)
  SELECT @originating_server           = UPPER(LTRIM(RTRIM(@originating_server)))
  SELECT @job_name                     = LTRIM(RTRIM(@job_name))
  SELECT @description                  = LTRIM(RTRIM(@description))
  SELECT @category_name                = LTRIM(RTRIM(@category_name))
  SELECT @notify_email_operator_name   = LTRIM(RTRIM(@notify_email_operator_name))
  SELECT @notify_netsend_operator_name = LTRIM(RTRIM(@notify_netsend_operator_name))
  SELECT @notify_page_operator_name    = LTRIM(RTRIM(@notify_page_operator_name))
  SELECT @originating_server_id        = NULL

  -- Turn [nullable] empty string parameters into NULLs
  IF (@originating_server           = N'') SELECT @originating_server           = NULL
  IF (@description                  = N'') SELECT @description                  = NULL
  IF (@category_name                = N'') SELECT @category_name                = NULL
  IF (@notify_email_operator_name   = N'') SELECT @notify_email_operator_name   = NULL
  IF (@notify_netsend_operator_name = N'') SELECT @notify_netsend_operator_name = NULL
  IF (@notify_page_operator_name    = N'') SELECT @notify_page_operator_name    = NULL

  IF (@originating_server IS NULL) OR (@originating_server = '(LOCAL)')
    SELECT @originating_server= UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

  --only members of sysadmins role can set the owner
  IF (@owner_login_name IS NOT NULL AND ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0) AND (@owner_login_name <> SUSER_SNAME())
  BEGIN
    RAISERROR(14515, -1, -1)
    RETURN(1) -- Failure
  END

  -- Default the owner (if not supplied or if a non-sa is [illegally] trying to create a job for another user)
  -- allow special account only when caller is sysadmin
  IF (@owner_login_name = N'$(SQLAgentAccount)')  AND
     (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
  BEGIN
    SELECT @owner_sid = 0xFFFFFFFF
  END
  ELSE
  IF (@owner_login_name IS NULL) OR ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0) AND (@owner_login_name <> SUSER_SNAME()))
  BEGIN
    SELECT @owner_sid = SUSER_SID()
  END
  ELSE
  BEGIN
    --force case insensitive comparation for NT users
    SELECT @owner_sid = SUSER_SID(@owner_login_name, 0) -- If @owner_login_name is invalid then SUSER_SID() will return NULL
  END

  -- Default the description (if not supplied)
  IF (@description IS NULL)
    SELECT @description = FORMATMESSAGE(14571)

  -- If a category ID is provided this overrides any supplied category name
  EXECUTE @retval = sp_verify_category_identifiers '@category_name',
                                                   '@category_id',
                                                    @category_name OUTPUT,
                                                    @category_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check parameters
  EXECUTE @retval = sp_verify_job NULL,  --  The job id is null since this is a new job
                                  @job_name,
                                  @enabled,
                                  @start_step_id,
                                  @category_name,
                                  @owner_sid                  OUTPUT,
                                  @notify_level_eventlog,
                                  @notify_level_email         OUTPUT,
                                  @notify_level_netsend       OUTPUT,
                                  @notify_level_page          OUTPUT,
                                  @notify_email_operator_name,
                                  @notify_netsend_operator_name,
                                  @notify_page_operator_name,
                                  @delete_level,
                                  @category_id                OUTPUT,
                                  @notify_email_operator_id   OUTPUT,
                                  @notify_netsend_operator_id OUTPUT,
                                  @notify_page_operator_id    OUTPUT,
                                  @originating_server         OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure


  SELECT @originating_server_id = originating_server_id
  FROM msdb.dbo.sysoriginatingservers_view
  WHERE (originating_server = @originating_server)
  IF (@originating_server_id IS NULL)
  BEGIN
    RAISERROR(14370, -1, -1)
    RETURN(1) -- Failure
  END


  IF (@job_id IS NULL)
  BEGIN
    -- Assign the GUID
    SELECT @job_id = NEWID()
  END
  ELSE
  BEGIN
    -- A job ID has been provided, so check that the caller is SQLServerAgent (inserting an MSX job)
    IF (PROGRAM_NAME() NOT LIKE N'SQLAgent%')
    BEGIN
      RAISERROR(14274, -1, -1)
      RETURN(1) -- Failure
    END
  END

  INSERT INTO msdb.dbo.sysjobs
         (job_id,
          originating_server_id,
          name,
          enabled,
          description,
          start_step_id,
          category_id,
          owner_sid,
          notify_level_eventlog,
          notify_level_email,
          notify_level_netsend,
          notify_level_page,
          notify_email_operator_id,
          notify_netsend_operator_id,
          notify_page_operator_id,
          delete_level,
          date_created,
          date_modified,
          version_number)
  VALUES  (@job_id,
          @originating_server_id,
          @job_name,
          @enabled,
          @description,
          @start_step_id,
          @category_id,
          @owner_sid,
          @notify_level_eventlog,
          @notify_level_email,
          @notify_level_netsend,
          @notify_level_page,
          @notify_email_operator_id,
          @notify_netsend_operator_id,
          @notify_page_operator_id,
          @delete_level,
          GETDATE(),
          GETDATE(),
          1) -- Version number 1
  SELECT @retval = @@error

  -- NOTE: We don't notify SQLServerAgent to update it's cache (we'll do this in sp_add_jobserver)

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_UPDATE_JOB                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_job
go
CREATE PROCEDURE sp_update_job
  @job_id                       UNIQUEIDENTIFIER = NULL, -- Must provide this or current_name
  @job_name                     sysname          = NULL, -- Must provide this or job_id
  @new_name                     sysname          = NULL,
  @enabled                      TINYINT          = NULL,
  @description                  NVARCHAR(512)    = NULL,
  @start_step_id                INT              = NULL,
  @category_name                sysname          = NULL,
  @owner_login_name             sysname          = NULL,
  @notify_level_eventlog        INT              = NULL,
  @notify_level_email           INT              = NULL,
  @notify_level_netsend         INT              = NULL,
  @notify_level_page            INT              = NULL,
  @notify_email_operator_name   sysname          = NULL,
  @notify_netsend_operator_name sysname          = NULL,
  @notify_page_operator_name    sysname          = NULL,
  @delete_level                 INT              = NULL,
  @automatic_post               BIT              = 1     -- Flag for SEM use only
AS
BEGIN
  DECLARE @retval                        INT
  DECLARE @category_id                   INT
  DECLARE @notify_email_operator_id      INT
  DECLARE @notify_netsend_operator_id    INT
  DECLARE @notify_page_operator_id       INT
  DECLARE @owner_sid                     VARBINARY(85)
  DECLARE @alert_id                      INT
  DECLARE @cached_attribute_modified     INT
  DECLARE @is_sysadmin                   INT
  DECLARE @current_owner                 sysname
  DECLARE @enable_only_used              INT

  DECLARE @x_new_name                    sysname
  DECLARE @x_enabled                     TINYINT
  DECLARE @x_description                 NVARCHAR(512)
  DECLARE @x_start_step_id               INT
  DECLARE @x_category_name               sysname
  DECLARE @x_category_id                 INT
  DECLARE @x_owner_sid                   VARBINARY(85)
  DECLARE @x_notify_level_eventlog       INT
  DECLARE @x_notify_level_email          INT
  DECLARE @x_notify_level_netsend        INT
  DECLARE @x_notify_level_page           INT
  DECLARE @x_notify_email_operator_name  sysname
  DECLARE @x_notify_netsnd_operator_name sysname
  DECLARE @x_notify_page_operator_name   sysname
  DECLARE @x_delete_level                INT
  DECLARE @x_originating_server_id       INT -- Not updatable
  DECLARE @x_master_server               BIT

  -- Remove any leading/trailing spaces from parameters (except @owner_login_name)
  SELECT @job_name                     = LTRIM(RTRIM(@job_name))
  SELECT @new_name                     = LTRIM(RTRIM(@new_name))
  SELECT @description                  = LTRIM(RTRIM(@description))
  SELECT @category_name                = LTRIM(RTRIM(@category_name))
  SELECT @notify_email_operator_name   = LTRIM(RTRIM(@notify_email_operator_name))
  SELECT @notify_netsend_operator_name = LTRIM(RTRIM(@notify_netsend_operator_name))
  SELECT @notify_page_operator_name    = LTRIM(RTRIM(@notify_page_operator_name))

  SET NOCOUNT ON

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (SERVERPROPERTY('EngineEdition') = 8)
  BEGIN
    IF (@notify_level_netsend <> 0)
    BEGIN
      RAISERROR(41914, -1, 10, 'NetSend');
      RETURN(1) -- Failure
    END
    SET @notify_level_eventlog = 0
  END

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Are we modifying an attribute which SQLServerAgent caches?
  IF ((@new_name                     IS NOT NULL) OR
      (@enabled                      IS NOT NULL) OR
      (@start_step_id                IS NOT NULL) OR
      (@owner_login_name             IS NOT NULL) OR
      (@notify_level_eventlog        IS NOT NULL) OR
      (@notify_level_email           IS NOT NULL) OR
      (@notify_level_netsend         IS NOT NULL) OR
      (@notify_level_page            IS NOT NULL) OR
      (@notify_email_operator_name   IS NOT NULL) OR
      (@notify_netsend_operator_name IS NOT NULL) OR
      (@notify_page_operator_name    IS NOT NULL) OR
      (@delete_level                 IS NOT NULL))
    SELECT @cached_attribute_modified = 1
  ELSE
    SELECT @cached_attribute_modified = 0

  -- Is @enable the only parameter used beside jobname and jobid?
  IF ((@enabled                   IS NOT NULL) AND
     (@new_name                IS NULL) AND
     (@description                  IS NULL) AND
     (@start_step_id                IS NULL) AND
     (@category_name                IS NULL) AND
     (@owner_login_name             IS NULL) AND
     (@notify_level_eventlog        IS NULL) AND
     (@notify_level_email           IS NULL) AND
     (@notify_level_netsend         IS NULL) AND
     (@notify_level_page            IS NULL) AND
     (@notify_email_operator_name   IS NULL) AND
     (@notify_netsend_operator_name IS NULL) AND
     (@notify_page_operator_name    IS NULL) AND
     (@delete_level                 IS NULL))
    SELECT @enable_only_used = 1
  ELSE
    SELECT @enable_only_used = 0

  -- Set the x_ (existing) variables
  SELECT @x_new_name                    = sjv.name,
         @x_enabled                     = sjv.enabled,
         @x_description                 = sjv.description,
         @x_start_step_id               = sjv.start_step_id,
         @x_category_name               = sc.name,                  -- From syscategories
         @x_category_id                 = sc.category_id,           -- From syscategories
         @x_owner_sid                   = sjv.owner_sid,
         @x_notify_level_eventlog       = sjv.notify_level_eventlog,
         @x_notify_level_email          = sjv.notify_level_email,
         @x_notify_level_netsend        = sjv.notify_level_netsend,
         @x_notify_level_page           = sjv.notify_level_page,
         @x_notify_email_operator_name  = so1.name,                   -- From sysoperators
         @x_notify_netsnd_operator_name = so2.name,                   -- From sysoperators
         @x_notify_page_operator_name   = so3.name,                   -- From sysoperators
         @x_delete_level                = sjv.delete_level,
         @x_originating_server_id       = sjv.originating_server_id,
         @x_master_server               = master_server
  FROM msdb.dbo.sysjobs_view                 sjv
       LEFT OUTER JOIN msdb.dbo.sysoperators so1 ON (sjv.notify_email_operator_id = so1.id)
       LEFT OUTER JOIN msdb.dbo.sysoperators so2 ON (sjv.notify_netsend_operator_id = so2.id)
       LEFT OUTER JOIN msdb.dbo.sysoperators so3 ON (sjv.notify_page_operator_id = so3.id),
       msdb.dbo.syscategories                sc
  WHERE (sjv.job_id = @job_id)
    AND (sjv.category_id = sc.category_id)

  -- Check authority (only SQLServerAgent can modify a non-local job)
  IF ((@x_master_server = 1) AND (PROGRAM_NAME() NOT LIKE N'SQLAgent%') )
  BEGIN
    RAISERROR(14274, -1, -1)
    RETURN(1) -- Failure
  END

  -- Check permissions beyond what's checked by the sysjobs_view
  -- SQLAgentReader and SQLAgentOperator roles that can see all jobs
  -- cannot modify jobs they do not own
  IF ( (@x_owner_sid <> SUSER_SID())                  -- does not own the job
      AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)   -- is not sysadmin
      AND (@enable_only_used <> 1 OR ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) <> 1))
  BEGIN
   RAISERROR(14525, -1, -1);
   RETURN(1) -- Failure
  END

  -- Check job category, only sysadmin can modify mutli-server jobs
  IF (EXISTS (SELECT * FROM msdb.dbo.syscategories WHERE (category_class = 1) -- Job
                                                     AND (category_type = 2) -- Multi-Server
                                                     AND (category_id = @x_category_id)
                                                     AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))) -- is not sysadmin
  BEGIN
     RAISERROR(14396, -1, -1);
     RETURN(1) -- Failure
  END

  IF (@new_name = N'') SELECT @new_name = NULL

  -- Fill out the values for all non-supplied parameters from the existing values
  IF (@new_name                     IS NULL) SELECT @new_name                     = @x_new_name
  IF (@enabled                      IS NULL) SELECT @enabled                      = @x_enabled
  IF (@description                  IS NULL) SELECT @description                  = @x_description
  IF (@start_step_id                IS NULL) SELECT @start_step_id                = @x_start_step_id
  IF (@category_name                IS NULL) SELECT @category_name                = @x_category_name
  IF (@owner_sid                    IS NULL) SELECT @owner_sid                    = @x_owner_sid
  IF (@notify_level_eventlog        IS NULL) SELECT @notify_level_eventlog        = @x_notify_level_eventlog
  IF (@notify_level_email           IS NULL) SELECT @notify_level_email           = @x_notify_level_email
  IF (@notify_level_netsend         IS NULL) SELECT @notify_level_netsend         = @x_notify_level_netsend
  IF (@notify_level_page            IS NULL) SELECT @notify_level_page            = @x_notify_level_page
  IF (@notify_email_operator_name   IS NULL) SELECT @notify_email_operator_name   = @x_notify_email_operator_name
  IF (@notify_netsend_operator_name IS NULL) SELECT @notify_netsend_operator_name = @x_notify_netsnd_operator_name
  IF (@notify_page_operator_name    IS NULL) SELECT @notify_page_operator_name    = @x_notify_page_operator_name
  IF (@delete_level                 IS NULL) SELECT @delete_level                 = @x_delete_level

  -- If the SA is attempting to assign ownership of the job to someone else, then convert
  -- the login name to an ID
  IF (@owner_login_name = N'$(SQLAgentAccount)')  AND
     (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
  BEGIN
    SELECT @owner_sid = 0xFFFFFFFF
  END
  ELSE IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) AND (@owner_login_name IS NOT NULL))
  BEGIN
    --force case insensitive comparation for NT users
    SELECT @owner_sid = SUSER_SID(@owner_login_name, 0) -- If @owner_login_name is invalid then SUSER_SID() will return NULL
  END

  -- Only the SA can re-assign jobs
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1) AND (@owner_login_name IS NOT NULL))
    RAISERROR(14242, -1, -1)

  -- Ownership of a multi-server job cannot be assigned to a non-sysadmin
  IF (@owner_login_name IS NOT NULL) AND
     (EXISTS (SELECT *
              FROM msdb.dbo.sysjobs       sj,
                   msdb.dbo.sysjobservers sjs
              WHERE (sj.job_id = sjs.job_id)
                AND (sj.job_id = @job_id)
                AND (sjs.server_id <> 0)))
  BEGIN
    IF (@owner_login_name = N'$(SQLAgentAccount)') -- allow distributed jobs to be assigned to special account
    BEGIN
      SELECT @is_sysadmin = 1
    END
    ELSE
    BEGIN
      SELECT @is_sysadmin = 0
      EXECUTE msdb.dbo.sp_sqlagent_has_server_access @login_name = @owner_login_name, @is_sysadmin_member = @is_sysadmin OUTPUT
    END

    IF (@is_sysadmin = 0)
    BEGIN
      SELECT @current_owner = dbo.SQLAGENT_SUSER_SNAME(@x_owner_sid)
      RAISERROR(14543, -1, -1, @current_owner, N'sysadmin')
      RETURN(1) -- Failure
    END
  END

  -- Turn [nullable] empty string parameters into NULLs
  IF (@description                  = N'') SELECT @description                  = NULL
  IF (@category_name                = N'') SELECT @category_name                = NULL
  IF (@notify_email_operator_name   = N'') SELECT @notify_email_operator_name   = NULL
  IF (@notify_netsend_operator_name = N'') SELECT @notify_netsend_operator_name = NULL
  IF (@notify_page_operator_name    = N'') SELECT @notify_page_operator_name    = NULL

  -- Check new values
  EXECUTE @retval = sp_verify_job @job_id,
                                  @new_name,
                                  @enabled,
                                  @start_step_id,
                                  @category_name,
                                  @owner_sid                  OUTPUT,
                                  @notify_level_eventlog,
                                  @notify_level_email         OUTPUT,
                                  @notify_level_netsend       OUTPUT,
                                  @notify_level_page          OUTPUT,
                                  @notify_email_operator_name,
                                  @notify_netsend_operator_name,
                                  @notify_page_operator_name,
                                  @delete_level,
                                  @category_id                OUTPUT,
                                  @notify_email_operator_id   OUTPUT,
                                  @notify_netsend_operator_id OUTPUT,
                                  @notify_page_operator_id    OUTPUT,
                                  NULL
  IF (@retval <> 0)
    RETURN(1) -- Failure

  BEGIN TRANSACTION

  -- If the job is being re-assigned, modify sysjobsteps.database_user_name as necessary
  IF (@owner_login_name IS NOT NULL)
  BEGIN
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobsteps
                WHERE (job_id = @job_id)
                  AND (subsystem = N'TSQL')))
    BEGIN
      IF (EXISTS (SELECT *
                  FROM master.dbo.syslogins
                  WHERE (sid = @owner_sid)
                    AND (sysadmin <> 1)))
      BEGIN
        -- The job is being re-assigned to an non-SA
        UPDATE msdb.dbo.sysjobsteps
        SET database_user_name = NULL
        WHERE (job_id = @job_id)
          AND (subsystem = N'TSQL')
      END
    END
  END

  UPDATE msdb.dbo.sysjobs
  SET name                       = @new_name,
      enabled                    = @enabled,
      description                = @description,
      start_step_id              = @start_step_id,
      category_id                = @category_id,              -- Returned from sp_verify_job
      owner_sid                  = @owner_sid,
      notify_level_eventlog      = @notify_level_eventlog,
      notify_level_email         = @notify_level_email,
      notify_level_netsend       = @notify_level_netsend,
      notify_level_page          = @notify_level_page,
      notify_email_operator_id   = @notify_email_operator_id,   -- Returned from sp_verify_job
      notify_netsend_operator_id = @notify_netsend_operator_id, -- Returned from sp_verify_job
      notify_page_operator_id    = @notify_page_operator_id,    -- Returned from sp_verify_job
      delete_level               = @delete_level,
      version_number             = version_number + 1,  -- Update the job's version
      date_modified              = GETDATE()            -- Update the job's last-modified information
  WHERE (job_id = @job_id)
  SELECT @retval = @@error

  COMMIT TRANSACTION

  -- Always re-post the job if it's an auto-delete job (or if we're updating an auto-delete job
  -- to be non-auto-delete)
  IF (((SELECT delete_level
        FROM msdb.dbo.sysjobs
        WHERE (job_id = @job_id)) <> 0) OR
      ((@x_delete_level = 1) AND (@delete_level = 0)))
    EXECUTE msdb.dbo.sp_post_msx_operation 'INSERT', 'JOB', @job_id
  ELSE
  BEGIN
    -- Post the update to target servers
    IF (@automatic_post = 1)
      EXECUTE msdb.dbo.sp_post_msx_operation 'UPDATE', 'JOB', @job_id
  END

  -- Keep SQLServerAgent's cache in-sync
  -- NOTE: We only notify SQLServerAgent if we know the job has been cached and if
  --       attributes other than description or category have been changed (since
  --       SQLServerAgent doesn't cache these two)
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id = 0)
                AND (@cached_attribute_modified = 1)))
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                        @job_id      = @job_id,
                                        @action_type = N'U'

  -- If the name was changed, make SQLServerAgent re-cache any alerts that reference the job
  -- since the alert cache contains the job name
  IF ((@job_name <> @new_name) AND (EXISTS (SELECT *
                                            FROM msdb.dbo.sysalerts
                                            WHERE (job_id = @job_id))))
  BEGIN
    DECLARE sysalerts_cache_update CURSOR LOCAL
    FOR
    SELECT id
    FROM msdb.dbo.sysalerts
    WHERE (job_id = @job_id)

    OPEN sysalerts_cache_update
    FETCH NEXT FROM sysalerts_cache_update INTO @alert_id

    WHILE (@@fetch_status = 0)
    BEGIN
      EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'A',
                                          @alert_id    = @alert_id,
                                          @action_type = N'U'
      FETCH NEXT FROM sysalerts_cache_update INTO @alert_id
    END
    DEALLOCATE sysalerts_cache_update
  END

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_JOB                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_job
go
CREATE PROCEDURE sp_delete_job
  @job_id               UNIQUEIDENTIFIER = NULL, -- If provided should NOT also provide job_name
  @job_name             sysname          = NULL, -- If provided should NOT also provide job_id
  @originating_server      sysname         = NULL, -- Reserved (used by SQLAgent)
  @delete_history       BIT              = 1,    -- Reserved (used by SQLAgent)
  @delete_unused_schedule   BIT              = 1     -- For backward compatibility schedules are deleted by default if they are not
                                        -- being used by another job. With the introduction of reusable schedules in V9
                                        -- callers should set this to 0 so the schedule will be preserved for reuse.
AS
BEGIN
  DECLARE @current_msx_server sysname
  DECLARE @bMSX_job           BIT
  DECLARE @retval             INT
  DECLARE @local_machine_name sysname
  DECLARE @category_id        INT
  DECLARE @job_owner_sid      VARBINARY(85)

  SET NOCOUNT ON
  -- Remove any leading/trailing spaces from parameters
  SELECT @originating_server = UPPER(LTRIM(RTRIM(@originating_server)))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@originating_server = N'') SELECT @originating_server = NULL

  -- Change server name to always reflect real servername or servername\instancename
  IF (@originating_server IS NOT NULL AND @originating_server = '(LOCAL)')
    SELECT @originating_server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT,
                                                 @owner_sid = @job_owner_sid OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

  END

  -- We need either a job name or a server name, not both
  IF ((@job_name IS NULL)     AND (@originating_server IS NULL)) OR
     ((@job_name IS NOT NULL) AND (@originating_server IS NOT NULL))
  BEGIN
    RAISERROR(14279, -1, -1)
    RETURN(1) -- Failure
  END

  -- Get category to see if it is a misc. replication agent. @category_id will be
  -- NULL if there is no @job_id.
  select @category_id = category_id from msdb.dbo.sysjobs where job_id = @job_id

  -- If job name was given, determine if the job is from an MSX
  IF (@job_id IS NOT NULL)
  BEGIN
    SELECT @bMSX_job = CASE UPPER(originating_server)
                         WHEN UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))) THEN 0
                         ELSE 1
                       END
    FROM msdb.dbo.sysjobs_view
    WHERE (job_id = @job_id)
  END

  -- If server name was given, warn user if different from current MSX
  IF (@originating_server IS NOT NULL)
  BEGIN
    EXECUTE @retval = master.dbo.xp_getnetname @local_machine_name OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

    IF ((@originating_server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))) OR (@originating_server = UPPER(@local_machine_name)))
      SELECT @originating_server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'MSXServerName',
                                           @current_msx_server OUTPUT,
                                           N'no_output'

    SELECT @current_msx_server = UPPER(@current_msx_server)
    -- If server name was given but it's not the current MSX, print a warning
    SELECT @current_msx_server = LTRIM(RTRIM(@current_msx_server))
    IF ((@current_msx_server IS NOT NULL) AND (@current_msx_server <> N'') AND (@originating_server <> @current_msx_server))
      RAISERROR(14224, 0, 1, @current_msx_server)
  END

  -- Check authority (only SQLServerAgent can delete a non-local job)
  IF (((@originating_server IS NOT NULL) AND (@originating_server <> UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName'))))) OR (@bMSX_job = 1)) AND
     (PROGRAM_NAME() NOT LIKE N'SQLAgent%')
  BEGIN
    RAISERROR(14274, -1, -1)
    RETURN(1) -- Failure
  END

  -- Check permissions beyond what's checked by the sysjobs_view
  -- SQLAgentReader and SQLAgentOperator roles that can see all jobs
  -- cannot delete jobs they do not own
  IF (@job_id IS NOT NULL)
  BEGIN
   IF (@job_owner_sid <> SUSER_SID()                     -- does not own the job
       AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)) -- is not sysadmin
   BEGIN
     RAISERROR(14525, -1, -1);
     RETURN(1) -- Failure
    END
  END

  -- Do the delete (for a specific job)
  IF (@job_id IS NOT NULL)
  BEGIN
    -- Note: This temp table is referenced by msdb.dbo.sp_delete_job_references,
    -- so it cannot be declared as a local table.
    CREATE TABLE #temp_jobs_to_delete (job_id UNIQUEIDENTIFIER NOT NULL PRIMARY KEY CLUSTERED,
                                       job_is_cached INT NOT NULL)

    DECLARE @temp_schedules_to_delete TABLE (schedule_id INT NOT NULL)

    INSERT INTO #temp_jobs_to_delete
    SELECT job_id, (SELECT COUNT(*)
                    FROM msdb.dbo.sysjobservers
                    WHERE (job_id = @job_id)
                      AND (server_id = 0))
    FROM msdb.dbo.sysjobs_view
    WHERE (job_id = @job_id)

    -- Check if we have any work to do
    IF (NOT EXISTS (SELECT *
                    FROM #temp_jobs_to_delete))
    BEGIN
      DROP TABLE #temp_jobs_to_delete
      RETURN(0) -- Success
    END

    -- Post the delete to any target servers (need to do this BEFORE
    -- deleting the job itself, but AFTER clearing all all pending
    -- download instructions).  Note that if the job is NOT a
    -- multi-server job then sp_post_msx_operation will catch this and
    -- will do nothing. Since it will do nothing that is why we need
    -- to NOT delete any pending delete requests, because that delete
    -- request might have been for the last target server and thus
    -- this job isn't a multi-server job anymore so posting the global
    -- delete would do nothing.
    DELETE FROM msdb.dbo.sysdownloadlist
    WHERE (object_id = @job_id)
      and (operation_code != 3) -- Delete
    EXECUTE msdb.dbo.sp_post_msx_operation 'DELETE', 'JOB', @job_id


    -- Must do this before deleting the job itself since sp_sqlagent_notify does a lookup on sysjobs_view
    -- Note: Don't notify agent in this call. It is done after the transaction is committed
    --       just in case this job is in the process of deleting itself
    EXECUTE msdb.dbo.sp_delete_job_references @notify_sqlagent = 0

    -- Delete all traces of the job
    BEGIN TRANSACTION

    DECLARE @err int

   --Get the schedules to delete before deleting records from sysjobschedules
    IF(@delete_unused_schedule = 1)
    BEGIN
        --Get the list of schedules to delete
        INSERT INTO @temp_schedules_to_delete
        SELECT DISTINCT schedule_id
        FROM   msdb.dbo.sysschedules
        WHERE (schedule_id IN
                (SELECT schedule_id
                FROM msdb.dbo.sysjobschedules
                WHERE (job_id = @job_id)))
    END


    DELETE FROM msdb.dbo.sysjobschedules
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

    DELETE FROM msdb.dbo.sysjobservers
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

    DELETE FROM msdb.dbo.sysjobsteps
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)

    DELETE FROM msdb.dbo.sysjobs
    WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)
    SELECT @err = @@ERROR

    IF @err <> 0
    BEGIN
    ROLLBACK TRANSACTION
    RETURN @err
    END


    --Delete the schedule(s) if requested to and it isn't being used by other jobs
    IF(@delete_unused_schedule = 1)
    BEGIN
      --Now OK to delete the schedule
      DELETE FROM msdb.dbo.sysschedules
      WHERE schedule_id IN
        (SELECT schedule_id
         FROM @temp_schedules_to_delete as sdel
         WHERE NOT EXISTS(SELECT *
                          FROM msdb.dbo.sysjobschedules AS js
                          WHERE (js.schedule_id = sdel.schedule_id)))
    END


    -- Delete the job history if requested
    IF (@delete_history = 1)
    BEGIN
      DELETE FROM msdb.dbo.sysjobhistory
      WHERE job_id IN (SELECT job_id FROM #temp_jobs_to_delete)
    END
    -- All done
    COMMIT TRANSACTION

    -- Now notify agent to delete the job.
    IF(EXISTS(SELECT * FROM #temp_jobs_to_delete WHERE job_is_cached > 0))
    BEGIN
      DECLARE @nt_user_name   NVARCHAR(100)
      SELECT @nt_user_name = ISNULL(NT_CLIENT(), ISNULL(SUSER_SNAME(), FORMATMESSAGE(14205)))
      --Call the xp directly. sp_sqlagent_notify checks sysjobs_view and the record has already been deleted
      EXEC master.dbo.xp_sqlagent_notify N'J', @job_id, 0, 0, N'D', @nt_user_name, 1, @@trancount, NULL, NULL
    END

  END
  ELSE
  -- Do the delete (for all jobs originating from the specific server)
  IF (@originating_server IS NOT NULL)
  BEGIN
    EXECUTE msdb.dbo.sp_delete_all_msx_jobs @msx_server = @originating_server

    -- NOTE: In this case there is no need to propagate the delete via sp_post_msx_operation
    --       since this type of delete is only ever performed on a TSX.
  END

  IF (OBJECT_ID(N'tempdb.dbo.#temp_jobs_to_delete', 'U') IS NOT NULL)
    DROP TABLE #temp_jobs_to_delete

  RETURN(0) -- 0 means success
END
go


/**************************************************************/
/* SP_GET_COMPOSITE_JOB_INFO                                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_get_composite_job_info...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_get_composite_job_info')
              AND (type = 'P')))
  DROP PROCEDURE sp_get_composite_job_info
go
CREATE PROCEDURE sp_get_composite_job_info
  @job_id             UNIQUEIDENTIFIER = NULL,
  @job_type           VARCHAR(12)      = NULL,  -- LOCAL or MULTI-SERVER
  @owner_login_name   sysname          = NULL,
  @subsystem          NVARCHAR(40)     = NULL,
  @category_id        INT              = NULL,
  @enabled            TINYINT          = NULL,
  @execution_status   INT              = NULL,  -- 0 = Not idle or suspended, 1 = Executing, 2 = Waiting For Thread, 3 = Between Retries, 4 = Idle, 5 = Suspended, [6 = WaitingForStepToFinish], 7 = PerformingCompletionActions
  @date_comparator    CHAR(1)          = NULL,  -- >, < or =
  @date_created       DATETIME         = NULL,
  @date_last_modified DATETIME         = NULL,
  @description        NVARCHAR(512)    = NULL,  -- We do a LIKE on this so it can include wildcards
  @schedule_id        INT              = NULL   -- if supplied only return the jobs that use this schedule
AS
BEGIN
  DECLARE @can_see_all_running_jobs INT
  DECLARE @job_owner   sysname

  SET NOCOUNT ON

  -- By 'composite' we mean a combination of sysjobs and xp_sqlagent_enum_jobs data.
  -- This proc should only ever be called by sp_help_job, so we don't verify the
  -- parameters (sp_help_job has already done this).

  -- Step 1: Create intermediate work tables
  DECLARE @job_execution_state TABLE (job_id                  UNIQUEIDENTIFIER NOT NULL,
                                     date_started            INT              NOT NULL,
                                     time_started            INT              NOT NULL,
                                     execution_job_status    INT              NOT NULL,
                                     execution_step_id       INT              NULL,
                                     execution_step_name     sysname          COLLATE database_default NULL,
                                     execution_retry_attempt INT              NOT NULL,
                                     next_run_date           INT              NOT NULL,
                                     next_run_time           INT              NOT NULL,
                                     next_run_schedule_id    INT              NOT NULL)
  DECLARE @filtered_jobs TABLE (job_id                   UNIQUEIDENTIFIER NOT NULL,
                               date_created             DATETIME         NOT NULL,
                               date_last_modified       DATETIME         NOT NULL,
                               current_execution_status INT              NULL,
                               current_execution_step   NVARCHAR(MAX)          COLLATE database_default NULL,
                               current_retry_attempt    INT              NULL,
                               last_run_date            INT              NOT NULL,
                               last_run_time            INT              NOT NULL,
                               last_run_outcome         INT              NOT NULL,
                               next_run_date            INT              NULL,
                               next_run_time            INT              NULL,
                               next_run_schedule_id     INT              NULL,
                               type                     INT              NOT NULL)
  DECLARE @xp_results TABLE (job_id                UNIQUEIDENTIFIER NOT NULL,
                            last_run_date         INT              NOT NULL,
                            last_run_time         INT              NOT NULL,
                            next_run_date         INT              NOT NULL,
                            next_run_time         INT              NOT NULL,
                            next_run_schedule_id  INT              NOT NULL,
                            requested_to_run      INT              NOT NULL, -- BOOL
                            request_source        INT              NOT NULL,
                            request_source_id     sysname          COLLATE database_default NULL,
                            running               INT              NOT NULL, -- BOOL
                            current_step          INT              NOT NULL,
                            current_retry_attempt INT              NOT NULL,
                            job_state             INT              NOT NULL)

  -- Step 2: Capture job execution information (for local jobs only since that's all SQLServerAgent caches)
  SELECT @can_see_all_running_jobs = ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0)
  IF (@can_see_all_running_jobs = 0)
  BEGIN
    SELECT @can_see_all_running_jobs = ISNULL(IS_MEMBER(N'SQLAgentReaderRole'), 0)
  END
  SELECT @job_owner = SUSER_SNAME()

  IF ((@@microsoftversion / 0x01000000) >= 8) -- SQL Server 8.0 or greater
    INSERT INTO @xp_results
    EXECUTE master.dbo.xp_sqlagent_enum_jobs @can_see_all_running_jobs, @job_owner, @job_id
  ELSE
    INSERT INTO @xp_results
    EXECUTE master.dbo.xp_sqlagent_enum_jobs @can_see_all_running_jobs, @job_owner

  INSERT INTO @job_execution_state
  SELECT xpr.job_id,
         xpr.last_run_date,
         xpr.last_run_time,
         xpr.job_state,
         sjs.step_id,
         sjs.step_name,
         xpr.current_retry_attempt,
         xpr.next_run_date,
         xpr.next_run_time,
         xpr.next_run_schedule_id
  FROM @xp_results                          xpr
       LEFT OUTER JOIN msdb.dbo.sysjobsteps sjs ON ((xpr.job_id = sjs.job_id) AND (xpr.current_step = sjs.step_id)),
       msdb.dbo.sysjobs_view                sjv
  WHERE (sjv.job_id = xpr.job_id)

  -- Step 3: Filter on everything but dates and job_type
  IF ((@subsystem        IS NULL) AND
      (@owner_login_name IS NULL) AND
      (@enabled          IS NULL) AND
      (@category_id      IS NULL) AND
      (@execution_status IS NULL) AND
      (@description      IS NULL) AND
      (@job_id           IS NULL))
  BEGIN
    -- Optimize for the frequently used case...
    INSERT INTO @filtered_jobs
    SELECT sjv.job_id,
           sjv.date_created,
           sjv.date_modified,
           ISNULL(jes.execution_job_status, 4), -- Will be NULL if the job is non-local or is not in @job_execution_state (NOTE: 4 = STATE_IDLE)
           CASE ISNULL(jes.execution_step_id, 0)
             WHEN 0 THEN NULL                   -- Will be NULL if the job is non-local or is not in @job_execution_state
             ELSE CONVERT(NVARCHAR, jes.execution_step_id) + N' (' + jes.execution_step_name + N')'
           END,
           jes.execution_retry_attempt,         -- Will be NULL if the job is non-local or is not in @job_execution_state
           0,  -- last_run_date placeholder    (we'll fix it up in step 3.3)
           0,  -- last_run_time placeholder    (we'll fix it up in step 3.3)
           5,  -- last_run_outcome placeholder (we'll fix it up in step 3.3 - NOTE: We use 5 just in case there are no jobservers for the job)
           jes.next_run_date,                   -- Will be NULL if the job is non-local or is not in @job_execution_state
           jes.next_run_time,                   -- Will be NULL if the job is non-local or is not in @job_execution_state
           jes.next_run_schedule_id,            -- Will be NULL if the job is non-local or is not in @job_execution_state
           0   -- type placeholder             (we'll fix it up in step 3.4)
    FROM msdb.dbo.sysjobs_view                sjv
         LEFT OUTER JOIN @job_execution_state jes ON (sjv.job_id = jes.job_id)
    WHERE ((@schedule_id IS NULL)
      OR   (EXISTS(SELECT *
                 FROM sysjobschedules as js
                 WHERE (sjv.job_id = js.job_id)
                   AND (js.schedule_id = @schedule_id))))
  END
  ELSE
  BEGIN
    INSERT INTO @filtered_jobs
    SELECT DISTINCT
           sjv.job_id,
           sjv.date_created,
           sjv.date_modified,
           ISNULL(jes.execution_job_status, 4), -- Will be NULL if the job is non-local or is not in @job_execution_state (NOTE: 4 = STATE_IDLE)
           CASE ISNULL(jes.execution_step_id, 0)
             WHEN 0 THEN NULL                   -- Will be NULL if the job is non-local or is not in @job_execution_state
             ELSE CONVERT(NVARCHAR, jes.execution_step_id) + N' (' + jes.execution_step_name + N')'
           END,
           jes.execution_retry_attempt,         -- Will be NULL if the job is non-local or is not in @job_execution_state
           0,  -- last_run_date placeholder    (we'll fix it up in step 3.3)
           0,  -- last_run_time placeholder    (we'll fix it up in step 3.3)
           5,  -- last_run_outcome placeholder (we'll fix it up in step 3.3 - NOTE: We use 5 just in case there are no jobservers for the job)
           jes.next_run_date,                   -- Will be NULL if the job is non-local or is not in @job_execution_state
           jes.next_run_time,                   -- Will be NULL if the job is non-local or is not in @job_execution_state
           jes.next_run_schedule_id,            -- Will be NULL if the job is non-local or is not in @job_execution_state
           0   -- type placeholder             (we'll fix it up in step 3.4)
    FROM msdb.dbo.sysjobs_view                sjv
         LEFT OUTER JOIN @job_execution_state jes ON (sjv.job_id = jes.job_id)
         LEFT OUTER JOIN msdb.dbo.sysjobsteps sjs ON (sjv.job_id = sjs.job_id)
    WHERE ((@subsystem        IS NULL) OR (sjs.subsystem            = @subsystem))
      AND ((@owner_login_name IS NULL)
          OR (sjv.owner_sid            = dbo.SQLAGENT_SUSER_SID(@owner_login_name)))--force case insensitive comparation for NT users
      AND ((@enabled          IS NULL) OR (sjv.enabled              = @enabled))
      AND ((@category_id      IS NULL) OR (sjv.category_id          = @category_id))
      AND ((@execution_status IS NULL) OR ((@execution_status > 0) AND (jes.execution_job_status = @execution_status))
                                       OR ((@execution_status = 0) AND (jes.execution_job_status <> 4) AND (jes.execution_job_status <> 5)))
      AND ((@description      IS NULL) OR (sjv.description       LIKE @description))
      AND ((@job_id           IS NULL) OR (sjv.job_id               = @job_id))
      AND ((@schedule_id IS NULL)
        OR (EXISTS(SELECT *
                 FROM sysjobschedules as js
                 WHERE (sjv.job_id = js.job_id)
                   AND (js.schedule_id = @schedule_id))))
  END

  -- Step 3.1: Change the execution status of non-local jobs from 'Idle' to 'Unknown'
  UPDATE @filtered_jobs
  SET current_execution_status = NULL
  WHERE (current_execution_status = 4)
    AND (job_id IN (SELECT job_id
                    FROM msdb.dbo.sysjobservers
                    WHERE (server_id <> 0)))

  -- Step 3.2: Check that if the user asked to see idle jobs that we still have some.
  --           If we don't have any then the query should return no rows.
  IF (@execution_status = 4) AND
     (NOT EXISTS (SELECT *
                  FROM @filtered_jobs
                  WHERE (current_execution_status = 4)))
  BEGIN
    DELETE FROM @filtered_jobs
  END

  -- Step 3.3: Populate the last run date/time/outcome [this is a little tricky since for
  --           multi-server jobs there are multiple last run details in sysjobservers, so
  --           we simply choose the most recent].
  IF (EXISTS (SELECT *
              FROM msdb.dbo.systargetservers))
  BEGIN
    UPDATE @filtered_jobs
    SET last_run_date = sjs.last_run_date,
        last_run_time = sjs.last_run_time,
        last_run_outcome = sjs.last_run_outcome
    FROM @filtered_jobs         fj,
         msdb.dbo.sysjobservers sjs
    WHERE (CONVERT(FLOAT, sjs.last_run_date) * 1000000) + sjs.last_run_time =
           (SELECT MAX((CONVERT(FLOAT, last_run_date) * 1000000) + last_run_time)
            FROM msdb.dbo.sysjobservers
            WHERE (job_id = sjs.job_id))
      AND (fj.job_id = sjs.job_id)
  END
  ELSE
  BEGIN
    UPDATE @filtered_jobs
    SET last_run_date = sjs.last_run_date,
        last_run_time = sjs.last_run_time,
        last_run_outcome = sjs.last_run_outcome
    FROM @filtered_jobs         fj,
         msdb.dbo.sysjobservers sjs
    WHERE (fj.job_id = sjs.job_id)
  END

  -- Step 3.4 : Set the type of the job to local (1) or multi-server (2)
  --            NOTE: If the job has no jobservers then it wil have a type of 0 meaning
  --                  unknown.  This is marginally inconsistent with the behaviour of
  --                  defaulting the category of a new job to [Uncategorized (Local)], but
  --                  prevents incompletely defined jobs from erroneously showing up as valid
  --                  local jobs.
  UPDATE @filtered_jobs
  SET type = 1 -- LOCAL
  FROM @filtered_jobs         fj,
       msdb.dbo.sysjobservers sjs
  WHERE (fj.job_id = sjs.job_id)
    AND (server_id = 0)
  UPDATE @filtered_jobs
  SET type = 2 -- MULTI-SERVER
  FROM @filtered_jobs         fj,
       msdb.dbo.sysjobservers sjs
  WHERE (fj.job_id = sjs.job_id)
    AND (server_id <> 0)

  -- Step 4: Filter on job_type
  IF (@job_type IS NOT NULL)
  BEGIN
    IF (UPPER(@job_type collate SQL_Latin1_General_CP1_CS_AS) = 'LOCAL')
      DELETE FROM @filtered_jobs
      WHERE (type <> 1) -- IE. Delete all the non-local jobs
    IF (UPPER(@job_type collate SQL_Latin1_General_CP1_CS_AS) = 'MULTI-SERVER')
      DELETE FROM @filtered_jobs
      WHERE (type <> 2) -- IE. Delete all the non-multi-server jobs
  END

  -- Step 5: Filter on dates
  IF (@date_comparator IS NOT NULL)
  BEGIN
    IF (@date_created IS NOT NULL)
    BEGIN
      IF (@date_comparator = '=')
        DELETE FROM @filtered_jobs WHERE (date_created <> @date_created)
      IF (@date_comparator = '>')
        DELETE FROM @filtered_jobs WHERE (date_created <= @date_created)
      IF (@date_comparator = '<')
        DELETE FROM @filtered_jobs WHERE (date_created >= @date_created)
    END
    IF (@date_last_modified IS NOT NULL)
    BEGIN
      IF (@date_comparator = '=')
        DELETE FROM @filtered_jobs WHERE (date_last_modified <> @date_last_modified)
      IF (@date_comparator = '>')
        DELETE FROM @filtered_jobs WHERE (date_last_modified <= @date_last_modified)
      IF (@date_comparator = '<')
        DELETE FROM @filtered_jobs WHERE (date_last_modified >= @date_last_modified)
    END
  END

  -- Return the result set (NOTE: No filtering occurs here)
  SELECT sjv.job_id,
         originating_server,
         sjv.name,
         sjv.enabled,
         sjv.description,
         sjv.start_step_id,
         category = ISNULL(sc.name, FORMATMESSAGE(14205)),
         owner = dbo.SQLAGENT_SUSER_SNAME(sjv.owner_sid),
         sjv.notify_level_eventlog,
         sjv.notify_level_email,
         sjv.notify_level_netsend,
         sjv.notify_level_page,
         notify_email_operator   = ISNULL(so1.name, FORMATMESSAGE(14205)),
         notify_netsend_operator = ISNULL(so2.name, FORMATMESSAGE(14205)),
         notify_page_operator    = ISNULL(so3.name, FORMATMESSAGE(14205)),
         sjv.delete_level,
         sjv.date_created,
         sjv.date_modified,
         sjv.version_number,
         fj.last_run_date,
         fj.last_run_time,
         fj.last_run_outcome,
         next_run_date = ISNULL(fj.next_run_date, 0),                                 -- This column will be NULL if the job is non-local
         next_run_time = ISNULL(fj.next_run_time, 0),                                 -- This column will be NULL if the job is non-local
         next_run_schedule_id = ISNULL(fj.next_run_schedule_id, 0),                   -- This column will be NULL if the job is non-local
         current_execution_status = ISNULL(fj.current_execution_status, 0),           -- This column will be NULL if the job is non-local
         current_execution_step = ISNULL(fj.current_execution_step, N'0 ' + FORMATMESSAGE(14205)), -- This column will be NULL if the job is non-local
         current_retry_attempt = ISNULL(fj.current_retry_attempt, 0),                 -- This column will be NULL if the job is non-local
         has_step = (SELECT COUNT(*)
                     FROM msdb.dbo.sysjobsteps sjst
                     WHERE (sjst.job_id = sjv.job_id)),
         has_schedule = (SELECT COUNT(*)
                         FROM msdb.dbo.sysjobschedules sjsch
                         WHERE (sjsch.job_id = sjv.job_id)),
         has_target = (SELECT COUNT(*)
                       FROM msdb.dbo.sysjobservers sjs
                       WHERE (sjs.job_id = sjv.job_id)),
         type = fj.type
  FROM @filtered_jobs                         fj
       LEFT OUTER JOIN msdb.dbo.sysjobs_view  sjv ON (fj.job_id = sjv.job_id)
       LEFT OUTER JOIN msdb.dbo.sysoperators  so1 ON (sjv.notify_email_operator_id = so1.id)
       LEFT OUTER JOIN msdb.dbo.sysoperators  so2 ON (sjv.notify_netsend_operator_id = so2.id)
       LEFT OUTER JOIN msdb.dbo.sysoperators  so3 ON (sjv.notify_page_operator_id = so3.id)
       LEFT OUTER JOIN msdb.dbo.syscategories sc  ON (sjv.category_id = sc.category_id)
  ORDER BY sjv.job_id

END
go


/**************************************************************/
/* SP_HELP_JOB                                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_job
go
CREATE PROCEDURE sp_help_job
  -- Individual job parameters
  @job_id                     UNIQUEIDENTIFIER = NULL,  -- If provided should NOT also provide job_name
  @job_name                   sysname          = NULL,  -- If provided should NOT also provide job_id
  @job_aspect                 VARCHAR(9)       = NULL,  -- JOB, STEPS, SCHEDULES, TARGETS or ALL
  -- Job set parameters
  @job_type                   VARCHAR(12)      = NULL,  -- LOCAL or MULTI-SERVER
  @owner_login_name           sysname          = NULL,
  @subsystem                  NVARCHAR(40)     = NULL,
  @category_name              sysname          = NULL,
  @enabled                    TINYINT          = NULL,
  @execution_status           INT              = NULL,  -- 1 = Executing, 2 = Waiting For Thread, 3 = Between Retries, 4 = Idle, 5 = Suspended, 6 = [obsolete], 7 = PerformingCompletionActions
  @date_comparator            CHAR(1)          = NULL,  -- >, < or =
  @date_created               DATETIME         = NULL,
  @date_last_modified         DATETIME         = NULL,
  @description                NVARCHAR(512)    = NULL   -- We do a LIKE on this so it can include wildcards
AS
BEGIN
  DECLARE @retval          INT
  DECLARE @category_id     INT
  DECLARE @job_id_as_char  VARCHAR(36)
  DECLARE @res_valid_range NVARCHAR(200)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters (except @owner_login_name)
  SELECT @job_name         = LTRIM(RTRIM(@job_name))
  SELECT @job_aspect       = LTRIM(RTRIM(@job_aspect))
  SELECT @job_type         = LTRIM(RTRIM(@job_type))
  SELECT @subsystem        = LTRIM(RTRIM(@subsystem))
  SELECT @category_name    = LTRIM(RTRIM(@category_name))
  SELECT @description      = LTRIM(RTRIM(@description))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@job_name         = N'') SELECT @job_name = NULL
  IF (@job_aspect       = '')  SELECT @job_aspect = NULL
  IF (@job_type         = '')  SELECT @job_type = NULL
  IF (@owner_login_name = N'') SELECT @owner_login_name = NULL
  IF (@subsystem        = N'') SELECT @subsystem = NULL
  IF (@category_name    = N'') SELECT @category_name = NULL
  IF (@description      = N'') SELECT @description = NULL

  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)

  -- If the user provided a job name or id but no aspect, default to ALL
  IF ((@job_name IS NOT NULL) OR (@job_id IS NOT NULL)) AND (@job_aspect IS NULL)
    SELECT @job_aspect = 'ALL'

  -- The caller must supply EITHER job name (or job id) and aspect OR one-or-more of the set
  -- parameters OR no parameters at all
  IF (((@job_name IS NOT NULL) OR (@job_id IS NOT NULL))
      AND ((@job_aspect          IS NULL)     OR
           (@job_type            IS NOT NULL) OR
           (@owner_login_name    IS NOT NULL) OR
           (@subsystem           IS NOT NULL) OR
           (@category_name       IS NOT NULL) OR
           (@enabled             IS NOT NULL) OR
           (@date_comparator     IS NOT NULL) OR
           (@date_created        IS NOT NULL) OR
           (@date_last_modified  IS NOT NULL)))
     OR
     ((@job_name IS NULL) AND (@job_id IS NULL) AND (@job_aspect IS NOT NULL))
  BEGIN
    RAISERROR(14280, -1, -1)
    RETURN(1) -- Failure
  END

  IF (@job_id IS NOT NULL)
  BEGIN
    -- Individual job...

    -- Check job aspect
    SELECT @job_aspect = UPPER(@job_aspect collate SQL_Latin1_General_CP1_CS_AS)
    IF (@job_aspect NOT IN ('JOB', 'STEPS', 'SCHEDULES', 'TARGETS', 'ALL'))
    BEGIN
      RAISERROR(14266, -1, -1, '@job_aspect', 'JOB, STEPS, SCHEDULES, TARGETS, ALL')
      RETURN(1) -- Failure
    END

    -- Generate results set...

    IF (@job_aspect IN ('JOB', 'ALL'))
    BEGIN
      IF (@job_aspect = 'ALL')
      BEGIN
        RAISERROR(14213, 0, 1)
        PRINT REPLICATE('=', DATALENGTH(FORMATMESSAGE(14213)) / 2)
      END
      EXECUTE sp_get_composite_job_info @job_id,
                                        @job_type,
                                        @owner_login_name,
                                        @subsystem,
                                        @category_id,
                                        @enabled,
                                        @execution_status,
                                        @date_comparator,
                                        @date_created,
                                        @date_last_modified,
                                        @description
    END

    IF (@job_aspect IN ('STEPS', 'ALL'))
    BEGIN
      IF (@job_aspect = 'ALL')
      BEGIN
        PRINT ''
        RAISERROR(14214, 0, 1)
        PRINT REPLICATE('=', DATALENGTH(FORMATMESSAGE(14214)) / 2)
      END
      EXECUTE ('EXECUTE sp_help_jobstep @job_id = ''' + @job_id_as_char + ''', @suffix = 1')
    END

    IF (@job_aspect IN ('SCHEDULES', 'ALL'))
    BEGIN
      IF (@job_aspect = 'ALL')
      BEGIN
        PRINT ''
        RAISERROR(14215, 0, 1)
        PRINT REPLICATE('=', DATALENGTH(FORMATMESSAGE(14215)) / 2)
      END
      EXECUTE ('EXECUTE sp_help_jobschedule @job_id = ''' + @job_id_as_char + '''')
    END

    IF (@job_aspect IN ('TARGETS', 'ALL'))
    BEGIN
      IF (@job_aspect = 'ALL')
      BEGIN
        PRINT ''
        RAISERROR(14216, 0, 1)
        PRINT REPLICATE('=', DATALENGTH(FORMATMESSAGE(14216)) / 2)
      END
      EXECUTE ('EXECUTE sp_help_jobserver @job_id = ''' + @job_id_as_char + ''', @show_last_run_details = 1')
    END
  END
  ELSE
  BEGIN
    -- Set of jobs...

    -- Check job type
    IF (@job_type IS NOT NULL)
    BEGIN
      SELECT @job_type = UPPER(@job_type collate SQL_Latin1_General_CP1_CS_AS)
      IF (@job_type NOT IN ('LOCAL', 'MULTI-SERVER'))
      BEGIN
        RAISERROR(14266, -1, -1, '@job_type', 'LOCAL, MULTI-SERVER')
        RETURN(1) -- Failure
      END
    END

    -- Check owner
    IF (@owner_login_name IS NOT NULL)
    BEGIN
      IF (SUSER_SID(@owner_login_name, 0) IS NULL)--force case insensitive comparation for NT users
      BEGIN
        RAISERROR(14262, -1, -1, '@owner_login_name', @owner_login_name)
        RETURN(1) -- Failure
      END
    END

    -- Check subsystem
    IF (@subsystem IS NOT NULL)
    BEGIN
      EXECUTE @retval = sp_verify_subsystem @subsystem
      IF (@retval <> 0)
        RETURN(1) -- Failure
    END

    -- Check job category
    IF (@category_name IS NOT NULL)
    BEGIN
      SELECT @category_id = category_id
      FROM msdb.dbo.syscategories
      WHERE (category_class = 1) -- Job
        AND (name = @category_name)
      IF (@category_id IS NULL)
      BEGIN
        RAISERROR(14262, -1, -1, '@category_name', @category_name)
        RETURN(1) -- Failure
      END
    END

    -- Check enabled state
    IF (@enabled IS NOT NULL) AND (@enabled NOT IN (0, 1))
    BEGIN
      RAISERROR(14266, -1, -1, '@enabled', '0, 1')
      RETURN(1) -- Failure
    END

    -- Check current execution status
    IF (@execution_status IS NOT NULL)
    BEGIN
      IF (@execution_status NOT IN (0, 1, 2, 3, 4, 5, 7))
      BEGIN
        SELECT @res_valid_range = FORMATMESSAGE(14204)
        RAISERROR(14266, -1, -1, '@execution_status', @res_valid_range)
        RETURN(1) -- Failure
      END
    END

    -- If a date comparator is supplied, we must have either a date-created or date-last-modified
    IF ((@date_comparator IS NOT NULL) AND (@date_created IS NOT NULL) AND (@date_last_modified IS NOT NULL)) OR
       ((@date_comparator IS NULL)     AND ((@date_created IS NOT NULL) OR (@date_last_modified IS NOT NULL)))
    BEGIN
      RAISERROR(14282, -1, -1)
      RETURN(1) -- Failure
    END

    -- Check dates / comparator
    IF (@date_comparator IS NOT NULL) AND (@date_comparator NOT IN ('=', '<', '>'))
    BEGIN
      RAISERROR(14266, -1, -1, '@date_comparator', '=, >, <')
      RETURN(1) -- Failure
    END
    IF (@date_created IS NOT NULL) AND
       ((@date_created < '19900101') OR (@date_created > '99991231 23:59'))
    BEGIN
      RAISERROR(14266, -1, -1, '@date_created', '1990-01-01 12:00am .. 9999-12-31 11:59pm')
      RETURN(1) -- Failure
    END
    IF (@date_last_modified IS NOT NULL) AND
       ((@date_last_modified < '19900101') OR (@date_last_modified > '99991231 23:59'))
    BEGIN
      RAISERROR(14266, -1, -1, '@date_last_modified', '1990-01-01 12:00am .. 9999-12-31 11:59pm')
      RETURN(1) -- Failure
    END

    -- Generate results set...
    EXECUTE sp_get_composite_job_info @job_id,
                                      @job_type,
                                      @owner_login_name,
                                      @subsystem,
                                      @category_id,
                                      @enabled,
                                      @execution_status,
                                      @date_comparator,
                                      @date_created,
                                      @date_last_modified,
                                      @description
  END

  RETURN(0) -- Success
END
go

CHECKPOINT
go


/**************************************************************/
/* sp_help_jobcount                                           */
/*      At least one parameter must be specified              */
/*      returns a one row/one column recordset with a integer */
/*      representing the number of jobs assigned to the       */
/*      specified schedule                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_jobcount...'
GO
IF (NOT OBJECT_ID(N'dbo.sp_help_jobcount', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_help_jobcount
GO

CREATE PROCEDURE sp_help_jobcount
  @schedule_name       sysname  = NULL, -- Specify if @schedule_id is null
  @schedule_id         INT      = NULL  -- Specify if @schedule_name is null
AS
BEGIN
  SET NOCOUNT ON

  DECLARE @retval   INT

  -- Check that we can uniquely identify the schedule. This only returns a schedule that is visible to this user
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @schedule_name    OUTPUT,
                                                            @schedule_id            = @schedule_id      OUTPUT,
                                                            @owner_sid              = NULL,
                                                            @orig_server_id         = NULL
  IF (@retval <> 0)
    RETURN(1) -- Failure


  SELECT COUNT(*) AS JobCount
  FROM msdb.dbo.sysjobschedules
  WHERE (schedule_id = @schedule_id)


  RETURN (0) -- 0 means success
END
go



/**************************************************************/
/* sp_help_jobs_in_schedule                                   */
/*      At least one parameter must be specified to identify  */
/*      the schedule. Returns the same information as         */
/*      sp_help_job. Only jobs in the specified schedule are  */
/*      in the recordset                                      */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_jobs_in_schedule...'
GO
IF (NOT OBJECT_ID(N'dbo.sp_help_jobs_in_schedule', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_help_jobs_in_schedule
GO

CREATE PROCEDURE sp_help_jobs_in_schedule
  @schedule_name       sysname  = NULL, -- Specify if @schedule_id is null
  @schedule_id         INT      = NULL  -- Specify if @schedule_name is null
AS
BEGIN
  SET NOCOUNT ON

  DECLARE @retval   INT

  -- Check that we can uniquely identify the schedule. This only returns a schedule that is visible to this user
  EXECUTE @retval = msdb.dbo.sp_verify_schedule_identifiers @name_of_name_parameter = '@schedule_name',
                                                            @name_of_id_parameter   = '@schedule_id',
                                                            @schedule_name          = @schedule_name    OUTPUT,
                                                            @schedule_id            = @schedule_id      OUTPUT,
                                                            @owner_sid              = NULL,
                                                            @orig_server_id         = NULL
  IF (@retval <> 0)
    RETURN(1) -- Failure

  EXECUTE @retval = msdb.dbo.sp_get_composite_job_info @schedule_id = @schedule_id
  IF (@retval <> 0)
    RETURN(1) -- Failure


  RETURN (0) -- 0 means success
END
go


/**************************************************************/
/* SP_MANAGE_JOBS_BY_LOGIN                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_manage_jobs_by_login...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_manage_jobs_by_login')
              AND (type = 'P')))
  DROP PROCEDURE sp_manage_jobs_by_login
go
CREATE PROCEDURE sp_manage_jobs_by_login
  @action                   VARCHAR(10), -- DELETE or REASSIGN
  @current_owner_login_name sysname,
  @new_owner_login_name     sysname = NULL
AS
BEGIN
  DECLARE @current_sid   VARBINARY(85)
  DECLARE @new_sid       VARBINARY(85)
  DECLARE @job_id        UNIQUEIDENTIFIER
  DECLARE @rows_affected INT
  DECLARE @is_sysadmin   INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @action                   = LTRIM(RTRIM(@action))
  SELECT @current_owner_login_name = LTRIM(RTRIM(@current_owner_login_name))
  SELECT @new_owner_login_name     = LTRIM(RTRIM(@new_owner_login_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@new_owner_login_name = N'') SELECT @new_owner_login_name = NULL

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check action
  IF (@action NOT IN ('DELETE', 'REASSIGN'))
  BEGIN
    RAISERROR(14266, -1, -1, '@action', 'DELETE, REASSIGN')
    RETURN(1) -- Failure
  END

  -- Check parameter combinations
  IF ((@action = 'DELETE') AND (@new_owner_login_name IS NOT NULL))
    RAISERROR(14281, 0, 1)

  IF ((@action = 'REASSIGN') AND (@new_owner_login_name IS NULL))
  BEGIN
    RAISERROR(14237, -1, -1)
    RETURN(1) -- Failure
  END

  -- Check current login
  SELECT @current_sid = dbo.SQLAGENT_SUSER_SID(@current_owner_login_name)
  IF (@current_sid IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@current_owner_login_name', @current_owner_login_name)
    RETURN(1) -- Failure
  END

  -- Check new login (if supplied)
  IF (@new_owner_login_name IS NOT NULL)
  BEGIN
    SELECT @new_sid = dbo.SQLAGENT_SUSER_SID(@new_owner_login_name)
    IF (@new_sid IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@new_owner_login_name', @new_owner_login_name)
      RETURN(1) -- Failure
    END
  END

  IF (@action = 'DELETE')
  BEGIN
    DECLARE jobs_to_delete CURSOR LOCAL
    FOR
    SELECT job_id
    FROM msdb.dbo.sysjobs
    WHERE (owner_sid = @current_sid)

    OPEN jobs_to_delete
    FETCH NEXT FROM jobs_to_delete INTO @job_id

    SELECT @rows_affected = 0
    WHILE (@@fetch_status = 0)
    BEGIN
      EXECUTE sp_delete_job @job_id = @job_id
      SELECT @rows_affected = @rows_affected + 1
      FETCH NEXT FROM jobs_to_delete INTO @job_id
    END
    DEALLOCATE jobs_to_delete
    RAISERROR(14238, 0, 1, @rows_affected)
  END
  ELSE
  IF (@action = 'REASSIGN')
  BEGIN
    -- Check if the current owner owns any multi-server jobs.
    -- If they do, then the new owner must be member of the sysadmin role.
    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobs       sj,
                     msdb.dbo.sysjobservers sjs
                WHERE (sj.job_id = sjs.job_id)
                  AND (sj.owner_sid = @current_sid)
                  AND (sjs.server_id <> 0)) AND @new_sid <> 0xFFFFFFFF) -- speical account allowed for MSX jobs
    BEGIN
      SELECT @is_sysadmin = 0
      EXECUTE msdb.dbo.sp_sqlagent_has_server_access @login_name = @new_owner_login_name, @is_sysadmin_member = @is_sysadmin OUTPUT
      IF (@is_sysadmin = 0)
      BEGIN
        RAISERROR(14543, -1, -1, @current_owner_login_name, N'sysadmin')
        RETURN(1) -- Failure
      END
    END

    UPDATE msdb.dbo.sysjobs
    SET owner_sid = @new_sid
    WHERE (owner_sid = @current_sid)
    RAISERROR(14239, 0, 1, @@rowcount, @new_owner_login_name)
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_APPLY_JOB_TO_TARGETS                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_apply_job_to_targets...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_apply_job_to_targets')
              AND (type = 'P')))
  DROP PROCEDURE sp_apply_job_to_targets
go
CREATE PROCEDURE sp_apply_job_to_targets
  @job_id               UNIQUEIDENTIFIER = NULL,   -- Must provide either this or job_name
  @job_name             sysname          = NULL,   -- Must provide either this or job_id
  @target_server_groups NVARCHAR(2048)   = NULL,   -- A comma-separated list of target server groups
  @target_servers       NVARCHAR(2048)   = NULL,   -- An comma-separated list of target servers
  @operation            VARCHAR(7)       = 'APPLY' -- Or 'REMOVE'
AS
BEGIN
  DECLARE @retval        INT
  DECLARE @rows_affected INT
  DECLARE @server_name   sysname
  DECLARE @groups        NVARCHAR(2048)
  DECLARE @group         sysname
  DECLARE @servers       NVARCHAR(2048)
  DECLARE @server        sysname
  DECLARE @pos_of_comma  INT

  SET NOCOUNT ON

  -- Only a sysadmin can do this
  IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @target_server_groups = LTRIM(RTRIM(@target_server_groups))
  SELECT @target_servers       = UPPER(LTRIM(RTRIM(@target_servers)))
  SELECT @operation            = LTRIM(RTRIM(@operation))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@target_server_groups = NULL) SELECT @target_server_groups = NULL
  IF (@target_servers       = NULL) SELECT @target_servers = NULL
  IF (@operation            = NULL) SELECT @operation = NULL

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check operation type
  IF ((@operation <> 'APPLY') AND (@operation <> 'REMOVE'))
  BEGIN
    RAISERROR(14266, -1, -1, '@operation', 'APPLY, REMOVE')
    RETURN(1) -- Failure
  END

  -- Check that we have a target server group list and/or a target server list
  IF ((@target_server_groups IS NULL) AND (@target_servers IS NULL))
  BEGIN
    RAISERROR(14283, -1, -1)
    RETURN(1) -- Failure
  END

  DECLARE @temp_groups TABLE (group_name sysname COLLATE database_default NOT NULL)
  DECLARE @temp_server_name TABLE (server_name sysname COLLATE database_default NOT NULL)

  -- Parse the Target Server comma-separated list (if supplied)
  IF (@target_servers IS NOT NULL)
  BEGIN
    SELECT @servers = @target_servers
    SELECT @pos_of_comma = CHARINDEX(N',', @servers)
    WHILE (@pos_of_comma <> 0)
    BEGIN
      SELECT @server = SUBSTRING(@servers, 1, @pos_of_comma - 1)
      INSERT INTO @temp_server_name (server_name) VALUES (LTRIM(RTRIM(@server)))
      SELECT @servers = RIGHT(@servers, (DATALENGTH(@servers) / 2) - @pos_of_comma)
      SELECT @pos_of_comma = CHARINDEX(N',', @servers)
    END
    INSERT INTO @temp_server_name (server_name) VALUES (LTRIM(RTRIM(@servers)))
  END

  -- Parse the Target Server Groups comma-separated list
  IF (@target_server_groups IS NOT NULL)
  BEGIN
    SELECT @groups = @target_server_groups
    SELECT @pos_of_comma = CHARINDEX(N',', @groups)
    WHILE (@pos_of_comma <> 0)
    BEGIN
      SELECT @group = SUBSTRING(@groups, 1, @pos_of_comma - 1)
      INSERT INTO @temp_groups (group_name) VALUES (LTRIM(RTRIM(@group)))
      SELECT @groups = RIGHT(@groups, (DATALENGTH(@groups) / 2) - @pos_of_comma)
      SELECT @pos_of_comma = CHARINDEX(N',', @groups)
    END
    INSERT INTO @temp_groups (group_name) VALUES (LTRIM(RTRIM(@groups)))
  END

  -- Check server groups
  SET ROWCOUNT 1 -- We do this so that we catch the FIRST invalid group
  SELECT @group = NULL
  SELECT @group = group_name
  FROM @temp_groups
  WHERE group_name NOT IN (SELECT name
                           FROM msdb.dbo.systargetservergroups)
  IF (@group IS NOT NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@target_server_groups', @group)
    RETURN(1) -- Failure
  END
  SET ROWCOUNT 0

  -- Find the distinct list of servers being targeted
  INSERT INTO @temp_server_name (server_name)
  SELECT DISTINCT sts.server_name
  FROM msdb.dbo.systargetservergroups       stsg,
       msdb.dbo.systargetservergroupmembers stsgm,
       msdb.dbo.systargetservers            sts
  WHERE (stsg.name IN (SELECT group_name FROM @temp_groups))
    AND (stsg.servergroup_id = stsgm.servergroup_id)
    AND (stsgm.server_id = sts.server_id)
    AND (UPPER(sts.server_name) NOT IN (SELECT server_name
                                        FROM @temp_server_name))

  IF (@operation = 'APPLY')
  BEGIN
    -- Remove those servers to which the job has already been applied
    DELETE FROM @temp_server_name
    WHERE server_name IN (SELECT sts.server_name
                          FROM msdb.dbo.sysjobservers    sjs,
                               msdb.dbo.systargetservers sts
                          WHERE (sjs.job_id = @job_id)
                            AND (sjs.server_id = sts.server_id))
  END

  IF (@operation = 'REMOVE')
  BEGIN
    -- Remove those servers to which the job is not currently applied
    DELETE FROM @temp_server_name
    WHERE server_name NOT IN (SELECT sts.server_name
                              FROM msdb.dbo.sysjobservers    sjs,
                                   msdb.dbo.systargetservers sts
                              WHERE (sjs.job_id = @job_id)
                                AND (sjs.server_id = sts.server_id))
  END

  SELECT @rows_affected = COUNT(*)
  FROM @temp_server_name

  SET ROWCOUNT 1
  WHILE (EXISTS (SELECT *
                 FROM @temp_server_name))
  BEGIN
    SELECT @server_name = server_name
    FROM @temp_server_name
    IF (@operation = 'APPLY')
      EXECUTE sp_add_jobserver @job_id = @job_id, @server_name = @server_name
    ELSE
    IF (@operation = 'REMOVE')
      EXECUTE sp_delete_jobserver @job_id = @job_id, @server_name = @server_name
    DELETE FROM @temp_server_name
    WHERE (server_name = @server_name)
  END
  SET ROWCOUNT 0

  IF (@operation = 'APPLY')
    RAISERROR(14240, 0, 1, @rows_affected)
  IF (@operation = 'REMOVE')
    RAISERROR(14241, 0, 1, @rows_affected)

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_REMOVE_JOB_FROM_TARGETS                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_remove_job_from_targets...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_remove_job_from_targets')
              AND (type = 'P')))
  DROP PROCEDURE sp_remove_job_from_targets
go
CREATE PROCEDURE sp_remove_job_from_targets
  @job_id               UNIQUEIDENTIFIER = NULL,   -- Must provide either this or job_name
  @job_name             sysname          = NULL,   -- Must provide either this or job_id
  @target_server_groups NVARCHAR(1024)   = NULL,   -- A comma-separated list of target server groups
  @target_servers       NVARCHAR(1024)   = NULL    -- A comma-separated list of target servers
AS
BEGIN
  DECLARE @retval INT

  SET NOCOUNT ON

  EXECUTE @retval = sp_apply_job_to_targets @job_id,
                                            @job_name,
                                            @target_server_groups,
                                            @target_servers,
                                           'REMOVE'
  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_GET_JOB_ALERTS                                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_get_job_alerts...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_get_job_alerts')
              AND (type = 'P')))
  DROP PROCEDURE sp_get_job_alerts
go
CREATE PROCEDURE sp_get_job_alerts
  @job_id   UNIQUEIDENTIFIER = NULL,
  @job_name sysname          = NULL
AS
BEGIN
  DECLARE @retval INT

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  SELECT id,
         name,
         enabled,
       type = CASE ISNULL(performance_condition, '!')
         WHEN '!' THEN 1              -- SQL Server event alert
         ELSE CASE event_id
            WHEN 8 THEN 3          -- WMI event alert
            ELSE 2                    -- SQL Server performance condition alert
         END
       END
  FROM msdb.dbo.sysalerts
  WHERE (job_id = @job_id)

  RETURN(0) -- Success
END
go

/**************************************************************/
/*                                                            */
/*   S  U  P  P  O  R  T     P  R  O  C  E  D  U  R  E  S     */
/*                                                            */
/**************************************************************/

/**************************************************************/
/* SP_START_JOB                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_start_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_start_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_start_job
go
CREATE PROCEDURE sp_start_job
  @job_name    sysname          = NULL,
  @job_id      UNIQUEIDENTIFIER = NULL,
  @error_flag  INT              = 1,    -- Set to 0 to suppress the error from sp_sqlagent_notify if SQLServerAgent is not running
  @server_name sysname          = NULL, -- The specific target server to start the [multi-server] job on
  @step_name   sysname          = NULL, -- The name of the job step to start execution with [for use with a local job only]
  @output_flag INT              = 1     -- Set to 0 to suppress the success message
AS
BEGIN
  DECLARE @job_id_as_char VARCHAR(36)
  DECLARE @retval         INT
  DECLARE @step_id        INT
  DECLARE @job_owner_sid  VARBINARY(85)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @job_name    = LTRIM(RTRIM(@job_name))
  SELECT @server_name = UPPER(LTRIM(RTRIM(@server_name)))
  SELECT @step_name   = LTRIM(RTRIM(@step_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@job_name = N'')    SELECT @job_name = NULL
  IF (@server_name = N'') SELECT @server_name = NULL
  IF (@step_name = N'')   SELECT @step_name = NULL

  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT,
                                               @owner_sid = @job_owner_sid OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check permissions beyond what's checked by the sysjobs_view
  -- SQLAgentReader role can see all jobs but
  -- cannot start/stop jobs they do not own
  IF (@job_owner_sid <> SUSER_SID()                      -- does not own the job
     AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0)     -- is not sysadmin
     AND (ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) = 0))  -- is not SQLAgentOperatorRole
  BEGIN
   RAISERROR(14393, -1, -1);
   RETURN(1) -- Failure
  END

  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysjobservers
                  WHERE (job_id = @job_id)))
  BEGIN
    SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)
    RAISERROR(14256, -1, -1, @job_name, @job_id_as_char)
    RETURN(1) -- Failure
  END

  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysjobservers
              WHERE (job_id = @job_id)
                AND (server_id = 0)))
  BEGIN
    -- The job is local, so start (run) the job locally

    -- Check the step name (if supplied)
    IF (@step_name IS NOT NULL)
    BEGIN
      SELECT @step_id = step_id
      FROM msdb.dbo.sysjobsteps
      WHERE (step_name = @step_name)
        AND (job_id = @job_id)

      IF (@step_id IS NULL)
      BEGIN
        RAISERROR(14262, -1, -1, '@step_name', @step_name)
        RETURN(1) -- Failure
      END
    END

    EXECUTE @retval = msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                                  @job_id      = @job_id,
                                                  @schedule_id = @step_id, -- This is the start step
                                                  @action_type = N'S',
                                                  @error_flag  = @error_flag
    IF ((@retval = 0) AND (@output_flag = 1))
      RAISERROR(14243, 0, 1, @job_name)
  END
  ELSE
  BEGIN
    -- The job is a multi-server job

      -- Only sysadmin can start multi-server job
      IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
      BEGIN
         RAISERROR(14397, -1, -1);
         RETURN(1) -- Failure
      END

    -- Check target server name (if any)
    IF (@server_name IS NOT NULL)
    BEGIN
      IF (NOT EXISTS (SELECT *
                      FROM msdb.dbo.systargetservers
                      WHERE (UPPER(server_name) = @server_name)))
      BEGIN
        RAISERROR(14262, -1, -1, '@server_name', @server_name)
        RETURN(1) -- Failure
      END
    END

    -- Re-post the job if it's an auto-delete job
    IF ((SELECT delete_level
         FROM msdb.dbo.sysjobs
         WHERE (job_id = @job_id)) <> 0)
      EXECUTE @retval = msdb.dbo.sp_post_msx_operation 'INSERT', 'JOB', @job_id, @server_name

    -- Post start instruction(s)
    EXECUTE @retval = msdb.dbo.sp_post_msx_operation 'START', 'JOB', @job_id, @server_name
  END

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_STOP_JOB                                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_stop_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_stop_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_stop_job
go
CREATE PROCEDURE sp_stop_job
  @job_name           sysname          = NULL,
  @job_id             UNIQUEIDENTIFIER = NULL,
  @originating_server sysname          = NULL, -- So that we can stop ALL jobs that came from the given server
  @server_name        sysname        = NULL  -- The specific target server to stop the [multi-server] job on
AS
BEGIN
  DECLARE @job_id_as_char           VARCHAR(36)
  DECLARE @retval                   INT
  DECLARE @num_parameters           INT
  DECLARE @job_owner_sid         VARBINARY(85)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @job_name           = LTRIM(RTRIM(@job_name))
  SELECT @originating_server = UPPER(LTRIM(RTRIM(@originating_server)))
  SELECT @server_name        = UPPER(LTRIM(RTRIM(@server_name)))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@job_name           = N'') SELECT @job_name = NULL
  IF (@originating_server = N'') SELECT @originating_server = NULL
  IF (@server_name        = N'') SELECT @server_name = NULL

  -- We must have EITHER a job id OR a job name OR an originating server
  SELECT @num_parameters = 0
  IF (@job_id IS NOT NULL)
    SELECT @num_parameters = @num_parameters + 1
  IF (@job_name IS NOT NULL)
    SELECT @num_parameters = @num_parameters + 1
  IF (@originating_server IS NOT NULL)
    SELECT @num_parameters = @num_parameters + 1
  IF (@num_parameters <> 1)
  BEGIN
    RAISERROR(14232, -1, -1)
    RETURN(1) -- Failure
  END

  IF (@originating_server IS NOT NULL)
  BEGIN
    -- Stop (cancel) ALL local jobs that originated from the specified server
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysjobs_view
                    WHERE (originating_server = @originating_server)))
    BEGIN
      RAISERROR(14268, -1, -1, @originating_server)
      RETURN(1) -- Failure
    END

    -- Check permissions beyond what's checked by the sysjobs_view
    -- SQLAgentReader role that can see all jobs but
    -- cannot start/stop jobs they do not own
    IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0)          -- is not sysadmin
       AND (ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) = 0)) -- is not SQLAgentOperatorRole
    BEGIN
       RAISERROR(14393, -1, -1);
       RETURN(1) -- Failure
    END

    DECLARE @total_counter   INT
    DECLARE @success_counter INT

    DECLARE stop_jobs CURSOR LOCAL
    FOR
    SELECT job_id
    FROM msdb.dbo.sysjobs_view
    WHERE (originating_server = @originating_server)

    SELECT @total_counter = 0, @success_counter = 0
    OPEN stop_jobs
    FETCH NEXT FROM stop_jobs INTO @job_id
    WHILE (@@fetch_status = 0)
    BEGIN
      SELECT @total_counter + @total_counter + 1
      EXECUTE @retval = msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                                    @job_id      = @job_id,
                                                    @action_type = N'C'
      IF (@retval = 0)
        SELECT @success_counter = @success_counter + 1
      FETCH NEXT FROM stop_jobs INTO @job_id
    END
    RAISERROR(14253, 0, 1, @success_counter, @total_counter)
    DEALLOCATE stop_jobs

    RETURN(0) -- 0 means success
  END
  ELSE
  BEGIN
    -- Stop ONLY the specified job
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT,
                                                 @owner_sid = @job_owner_sid OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure

    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysjobservers
                    WHERE (job_id = @job_id)))
    BEGIN
      SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)
      RAISERROR(14257, -1, -1, @job_name, @job_id_as_char)
      RETURN(1) -- Failure
    END

    -- Check permissions beyond what's checked by the sysjobs_view
    -- SQLAgentReader role that can see all jobs but
    -- cannot start/stop jobs they do not own
    IF (@job_owner_sid <> SUSER_SID()                  -- does not own the job
       AND (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 0)       -- is not sysadmin
       AND (ISNULL(IS_MEMBER(N'SQLAgentOperatorRole'), 0) = 0)) -- is not SQLAgentOperatorRole
    BEGIN
     RAISERROR(14393, -1, -1);
     RETURN(1) -- Failure
    END

    IF (EXISTS (SELECT *
                FROM msdb.dbo.sysjobservers
                WHERE (job_id = @job_id)
                  AND (server_id = 0)))
    BEGIN
      -- The job is local, so stop (cancel) the job locally
      EXECUTE @retval = msdb.dbo.sp_sqlagent_notify @op_type     = N'J',
                                                    @job_id      = @job_id,
                                                    @action_type = N'C'
      IF (@retval = 0)
        RAISERROR(14254, 0, 1, @job_name)
    END
    ELSE
    BEGIN
      -- The job is a multi-server job

      -- Only sysadmin can stop multi-server job
      IF (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1)
      BEGIN
         RAISERROR(14397, -1, -1);
         RETURN(1) -- Failure
      END

      -- Check target server name (if any)
      IF (@server_name IS NOT NULL)
      BEGIN
        IF (NOT EXISTS (SELECT *
                        FROM msdb.dbo.systargetservers
                        WHERE (UPPER(server_name) = @server_name)))
        BEGIN
          RAISERROR(14262, -1, -1, '@server_name', @server_name)
          RETURN(1) -- Failure
        END
      END

      -- Post the stop instruction(s)
      EXECUTE @retval = sp_post_msx_operation 'STOP', 'JOB', @job_id, @server_name
    END

    RETURN(@retval) -- 0 means success
  END

END
go

/**************************************************************/
/* SP_CYCLE_AGENT_ERRORLOG                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_cycle_agent_errorlog...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_cycle_agent_errorlog')
              AND (type = 'P')))
  DROP PROCEDURE sp_cycle_agent_errorlog
go

CREATE PROCEDURE sp_cycle_agent_errorlog
AS
BEGIN
   exec sp_sqlagent_notify N'L'
END
go

/**************************************************************/
/* SP_GET_CHUNKED_JOBSTEP_PARAMS                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_get_chunked_jobstep_params...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_get_chunked_jobstep_params')
              AND (type = 'P')))
  DROP PROCEDURE sp_get_chunked_jobstep_params
go
CREATE PROCEDURE sp_get_chunked_jobstep_params
  @job_name sysname,
  @step_id  INT = 1
AS
BEGIN
  DECLARE @job_id           UNIQUEIDENTIFIER
  DECLARE @step_id_as_char  VARCHAR(10)
  DECLARE @retval           INT

  SET NOCOUNT ON

  -- Check that the job exists
  EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                              '@job_id',
                                               @job_name OUTPUT,
                                               @job_id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check that the step exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysjobsteps
                  WHERE (job_id = @job_id)
                    AND (step_id = @step_id)))
  BEGIN
    SELECT @step_id_as_char = CONVERT(VARCHAR(10), @step_id)
    RAISERROR(14262, -1, -1, '@step_id', @step_id_as_char)
    RETURN(1) -- Failure
  END

  -- Return the sysjobsteps.additional_parameters
  SELECT additional_parameters
  FROM msdb.dbo.sysjobsteps
  WHERE (job_id = @job_id)
    AND (step_id = @step_id)


  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_CHECK_FOR_OWNED_JOBS                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_check_for_owned_jobs...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_check_for_owned_jobs')
              AND (type = 'P')))
  DROP PROCEDURE sp_check_for_owned_jobs
go
CREATE PROCEDURE sp_check_for_owned_jobs
  @login_name sysname,
  @table_name sysname
AS
BEGIN
  SET NOCOUNT ON

  -- This procedure is called by sp_droplogin to check if the login being dropped
  -- still owns jobs.  The return value (the number of jobs owned) is passed back
  -- via the supplied table name [this cumbersome approach is necessary because
  -- sp_check_for_owned_jobs is invoked via an EXEC() and because we always want
  -- sp_droplogin to work, even if msdb and/or sysjobs does not exist].

  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysobjects
              WHERE (name = N'sysjobs')
                AND (type = 'U')))
  BEGIN
    DECLARE @sql NVARCHAR(1024)
    SET @sql = N'INSERT INTO ' + QUOTENAME(@table_name, N'[') + N' SELECT COUNT(*) FROM msdb.dbo.sysjobs WHERE (owner_sid = SUSER_SID(N' + QUOTENAME(@login_name, '''') + ', 0))' --force case insensitive comparation for NT users
    EXEC sp_executesql @statement = @sql
  END
END
go

/**************************************************************/
/* SP_CHECK_FOR_OWNED_JOBSTEPS                                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_check_for_owned_jobsteps...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_check_for_owned_jobsteps')
              AND (type = 'P')))
  DROP PROCEDURE sp_check_for_owned_jobsteps
go
CREATE PROCEDURE sp_check_for_owned_jobsteps
  @login_name         sysname = NULL,  -- Supply this OR the database_X parameters, but not both
  @database_name      sysname = NULL,
  @database_user_name sysname = NULL
AS
BEGIN
  DECLARE @db_name            NVARCHAR(128)
  DECLARE @delimited_db_name  NVARCHAR(258)
  DECLARE @escaped_db_name    NVARCHAR(256) -- double sysname
  DECLARE @escaped_login_name NVARCHAR(256) -- double sysname

  SET NOCOUNT ON

  CREATE TABLE #work_table
  (
  database_name      sysname COLLATE database_default,
  database_user_name sysname COLLATE database_default
  )

  IF ((@login_name IS NOT NULL) AND (@database_name IS NULL) AND (@database_user_name IS NULL))
  BEGIN
    IF (SUSER_SID(@login_name, 0) IS NULL)--force case insensitive comparation for NT users
    BEGIN
      DROP TABLE #work_table

      RAISERROR(14262, -1, -1, '@login_name', @login_name)
      RETURN(1) -- Failure
    END

    DECLARE all_databases CURSOR LOCAL
    FOR
    SELECT name
    FROM master.dbo.sysdatabases

    OPEN all_databases
    FETCH NEXT FROM all_databases INTO @db_name

    -- Double up any single quotes in @login_name
    SELECT @escaped_login_name = REPLACE(@login_name, N'''', N'''''')

    WHILE (@@fetch_status = 0)
    BEGIN
      SELECT @delimited_db_name = QUOTENAME(@db_name, N'[')
      SELECT @escaped_db_name = REPLACE(@db_name, '''', '''''')
      EXECUTE(N'INSERT INTO #work_table
                SELECT N''' + @escaped_db_name + N''', name
                FROM ' + @delimited_db_name + N'.dbo.sysusers
                WHERE (sid = SUSER_SID(N''' + @escaped_login_name + N''', 0))')--force case insensitive comparation for NT users
      FETCH NEXT FROM all_databases INTO @db_name
    END

    DEALLOCATE all_databases

    -- If the login is an NT login, check for steps run as the login directly (as is the case with transient NT logins)
    IF (@login_name LIKE '%\%')
    BEGIN
      INSERT INTO #work_table
      SELECT database_name, database_user_name
      FROM msdb.dbo.sysjobsteps
      WHERE (database_user_name = @login_name)
    END
  END

  IF ((@login_name IS NULL) AND (@database_name IS NOT NULL) AND (@database_user_name IS NOT NULL))
  BEGIN
    INSERT INTO #work_table
    SELECT @database_name, @database_user_name
  END

  IF (EXISTS (SELECT *
              FROM #work_table wt,
                   msdb.dbo.sysjobsteps sjs
              WHERE (wt.database_name = sjs.database_name)
                AND (wt.database_user_name = sjs.database_user_name)))
  BEGIN
    SELECT sjv.job_id,
           sjv.name,
           sjs.step_id,
           sjs.step_name
    FROM #work_table           wt,
         msdb.dbo.sysjobsteps  sjs,
         msdb.dbo.sysjobs_view sjv
    WHERE (wt.database_name = sjs.database_name)
      AND (wt.database_user_name = sjs.database_user_name)
      AND (sjv.job_id = sjs.job_id)
    ORDER BY sjs.job_id
  END

  DROP TABLE #work_table
  RETURN(0) -- 0 means success
END
go

/**************************************************************/
/* SP_SQLAGENT_REFRESH_JOB                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_refresh_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_sqlagent_refresh_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_refresh_job
go
CREATE PROCEDURE sp_sqlagent_refresh_job
  @job_id      UNIQUEIDENTIFIER = NULL,
  @server_name sysname          = NULL -- This parameter allows a TSX to use this SP when updating a job
AS
BEGIN
  DECLARE @server_id INT

  SET NOCOUNT ON

  IF (@server_name IS NULL) OR (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = '(LOCAL)')
    SELECT @server_name = CONVERT(sysname, SERVERPROPERTY('ServerName'))

  SELECT @server_name = UPPER(@server_name)

  SELECT @server_id = server_id
  FROM msdb.dbo.systargetservers_view
  WHERE (UPPER(server_name) = ISNULL(@server_name, UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))))

  SELECT @server_id = ISNULL(@server_id, 0)

  SELECT sjv.job_id,
         sjv.name COLLATE SQL_Latin1_General_CP1_CI_AS,
         sjv.enabled,
         sjv.start_step_id,
         owner = dbo.SQLAGENT_SUSER_SNAME(sjv.owner_sid),
         sjv.notify_level_eventlog,
         sjv.notify_level_email,
         sjv.notify_level_netsend,
         sjv.notify_level_page,
         sjv.notify_email_operator_id,
         sjv.notify_netsend_operator_id,
         sjv.notify_page_operator_id,
         sjv.delete_level,
         has_step = (SELECT COUNT(*)
                     FROM msdb.dbo.sysjobsteps sjst
                     WHERE (sjst.job_id = sjv.job_id)),
         sjv.version_number,
         last_run_date = ISNULL(sjs.last_run_date, 0),
         last_run_time = ISNULL(sjs.last_run_time, 0),
         sjv.originating_server,
         sjv.description COLLATE SQL_Latin1_General_CP1_CI_AS,
         agent_account = CASE sjv.owner_sid
              WHEN 0xFFFFFFFF THEN 1
              ELSE                 0
         END,
		 0 AS is_system
  FROM msdb.dbo.sysjobservers sjs,
       msdb.dbo.sysjobs_view  sjv
  WHERE ((@job_id IS NULL) OR (@job_id = sjv.job_id))
    AND (sjv.job_id = sjs.job_id)
    AND (sjs.server_id = @server_id)
  UNION
  SELECT
	job_id,
	name COLLATE SQL_Latin1_General_CP1_CI_AS,
	enabled,
	start_step_id,
	dbo.SQLAGENT_SUSER_SNAME(0x01) AS [owner],
	notify_level_eventlog,
	0 AS notify_level_email,          -- notify_level_email
	0 AS notify_level_netsend,        -- notify_level_netsend
	0 AS notify_level_page,           -- notify_level_page
	0 AS notify_email_operator_id,    -- notify_email_operator_id
	0 AS notify_netsend_operator_id,  -- notify_netsend_operator_id
	0 AS notify_page_operator_id,     -- notify_page_operator_id
	delete_level,
	has_step = (SELECT COUNT(*)
                     FROM sys.fn_sqlagent_jobsteps(j.job_id, NULL) js
                     ),
	0 AS version_number,				-- version_number
	0 AS last_run_date,
	0 AS last_run_time,
	@server_name AS originating_server,
	description COLLATE SQL_Latin1_General_CP1_CI_AS,
	0 AS agent_account,
	1 AS is_system
  FROM sys.fn_sqlagent_jobs(NULL) j
  WHERE ((@job_id IS NULL) OR (@job_id = j.job_id))

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_JOBHISTORY_ROW_LIMITER                                  */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_jobhistory_row_limiter...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_jobhistory_row_limiter')
              AND (type = 'P')))
  DROP PROCEDURE dbo.sp_jobhistory_row_limiter
go
CREATE PROCEDURE sp_jobhistory_row_limiter
  @job_id UNIQUEIDENTIFIER
AS
BEGIN
  DECLARE @max_total_rows         INT -- This value comes from the registry (MaxJobHistoryTableRows)
  DECLARE @max_rows_per_job       INT -- This value comes from the registry (MaxJobHistoryRows)
  DECLARE @rows_to_delete         INT
  DECLARE @current_rows           INT
  DECLARE @current_rows_per_job   INT

  SET NOCOUNT ON

  -- Get max-job-history-rows from the registry
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'JobHistoryMaxRows',
                                         @max_total_rows OUTPUT,
                                         N'no_output'

  -- Check if we are limiting sysjobhistory rows
  IF (ISNULL(@max_total_rows, -1) = -1)
    RETURN(0)

  -- Check that max_total_rows is more than 1
  IF (ISNULL(@max_total_rows, 0) < 2)
  BEGIN
    -- It isn't, so set the default to 1000 rows
    SELECT @max_total_rows = 1000
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'JobHistoryMaxRows',
                                            N'REG_DWORD',
                                            @max_total_rows
  END

  -- Get the per-job maximum number of rows to keep
  SELECT @max_rows_per_job = 0
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'JobHistoryMaxRowsPerJob',
                                         @max_rows_per_job OUTPUT,
                                         N'no_output'

  -- Check that max_rows_per_job is <= max_total_rows
  IF ((@max_rows_per_job > @max_total_rows) OR (@max_rows_per_job < 1))
  BEGIN
    -- It isn't, so default the rows_per_job to max_total_rows
    SELECT @max_rows_per_job = @max_total_rows
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'JobHistoryMaxRowsPerJob',
                                            N'REG_DWORD',
                                            @max_rows_per_job
  END

  BEGIN TRANSACTION

  SELECT @current_rows_per_job = COUNT(*)
  FROM msdb.dbo.sysjobhistory with (TABLOCKX)
  WHERE (job_id = @job_id)

  -- Delete the oldest history row(s) for the job being inserted if the new row has
  -- pushed us over the per-job row limit (MaxJobHistoryRows)
  SELECT @rows_to_delete = @current_rows_per_job - @max_rows_per_job

  IF (@rows_to_delete > 0)
  BEGIN
    WITH RowsToDelete AS (
      SELECT TOP (@rows_to_delete) *
      FROM msdb.dbo.sysjobhistory
      WHERE (job_id = @job_id)
      ORDER BY instance_id
    )
    DELETE FROM RowsToDelete;
  END

  -- Delete the oldest history row(s) if inserting the new row has pushed us over the
  -- global MaxJobHistoryTableRows limit.
  SELECT @current_rows = COUNT(*)
  FROM msdb.dbo.sysjobhistory

  SELECT @rows_to_delete = @current_rows - @max_total_rows

  IF (@rows_to_delete > 0)
  BEGIN
    WITH RowsToDelete AS (
      SELECT TOP (@rows_to_delete) *
      FROM msdb.dbo.sysjobhistory
      ORDER BY instance_id
    )
    DELETE FROM RowsToDelete;
  END

  IF (@@trancount > 0)
    COMMIT TRANSACTION

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_SQLAGENT_SET_JOB_COMPLETION_STATE                       */
/**************************************************************/
IF (NOT OBJECT_ID('dbo.sp_sqlagent_set_job_completion_state', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_sqlagent_set_job_completion_state]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_sqlagent_set_job_completion_state]...'
GO
CREATE PROCEDURE [dbo].[sp_sqlagent_set_job_completion_state]
    @job_id               UNIQUEIDENTIFIER,
    @last_run_outcome     TINYINT,
    @last_outcome_message NVARCHAR(4000),
    @last_run_date        INT,
    @last_run_time        INT,
    @last_run_duration    INT
AS
BEGIN
    -- Update last run date, time for specific job_id in local server
    UPDATE msdb.dbo.sysjobservers
    SET last_run_outcome =  @last_run_outcome,
        last_outcome_message = @last_outcome_message,
        last_run_date = @last_run_date,
        last_run_time = @last_run_time,
        last_run_duration = @last_run_duration
    WHERE job_id  = @job_id
    AND server_id = 0
END
GO

/**************************************************************/
/* SP_SQLAGENT_SET_JOBSTEP_COMPLETION_STATE                   */
/**************************************************************/
IF (NOT OBJECT_ID('dbo.sp_sqlagent_set_jobstep_completion_state', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_sqlagent_set_jobstep_completion_state]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_sqlagent_set_jobstep_completion_state]...'
GO
CREATE PROCEDURE [dbo].[sp_sqlagent_set_jobstep_completion_state]
    @job_id                UNIQUEIDENTIFIER,
    @step_id               INT,
    @last_run_outcome      INT,
    @last_run_duration     INT,
    @last_run_retries      INT,
    @last_run_date         INT,
    @last_run_time         INT,
    @session_id            INT
AS
BEGIN
    -- Update job step completion state in sysjobsteps as well as sysjobactivity
    UPDATE [msdb].[dbo].[sysjobsteps]
    SET last_run_outcome      = @last_run_outcome,
        last_run_duration     = @last_run_duration,
        last_run_retries      = @last_run_retries,
        last_run_date         = @last_run_date,
        last_run_time         = @last_run_time
    WHERE job_id   = @job_id
    AND   step_id  = @step_id

    DECLARE @last_executed_step_date DATETIME
    SET @last_executed_step_date = [msdb].[dbo].[agent_datetime](@last_run_date, @last_run_time)

    UPDATE [msdb].[dbo].[sysjobactivity]
    SET last_executed_step_date = @last_executed_step_date,
        last_executed_step_id   = @step_id
    WHERE job_id     = @job_id
    AND   session_id = @session_id
END
GO

/**************************************************************/
/* SP_SQLAGENT_CREATE_JOBACTIVITY                             */
/**************************************************************/
IF (NOT OBJECT_ID('dbo.sp_sqlagent_create_jobactivity', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_sqlagent_create_jobactivity]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_sqlagent_create_jobactivity]...'
GO
CREATE PROCEDURE [dbo].[sp_sqlagent_create_jobactivity]
    @session_id            INT,
    @job_id                UNIQUEIDENTIFIER,
	@is_system             TINYINT = 0
AS
BEGIN
    IF(@is_system = 1)
    BEGIN
        -- TODO:: Call job activity update spec proc
    RETURN
    END

    IF(@job_id IS NULL)
    BEGIN
        -- On SQL Agent startup, session id along with all jobs are populated
        INSERT [msdb].[dbo].[sysjobactivity]
        (session_id, job_id)
        SELECT @session_id, job_id
        FROM [msdb].[dbo].[sysjobs]
    END
    ELSE
    BEGIN
        -- whenever a new job was created later & started, only that specific job_id is populated in
        -- sysjobactivity table
        INSERT [msdb].[dbo].[sysjobactivity]
        (session_id, job_id)
        VALUES(
            @session_id,
            @job_id
        )
    END
END
GO

/**************************************************************/
/* SP_SQLAGENT_UPDATE_JOBACTIVITY_NEXT_SCHEDULED_DATE         */
/**************************************************************/
IF (NOT OBJECT_ID('dbo.sp_sqlagent_update_jobactivity_next_scheduled_date', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_sqlagent_update_jobactivity_next_scheduled_date]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_sqlagent_update_jobactivity_next_scheduled_date]...'
GO
CREATE PROCEDURE [dbo].[sp_sqlagent_update_jobactivity_next_scheduled_date]
    @session_id            INT,
    @job_id                UNIQUEIDENTIFIER,
	@is_system             TINYINT = 0,
    @last_run_date         INT,
    @last_run_time         INT
AS
BEGIN
    IF(@is_system = 1)
    BEGIN
		-- TODO:: Call job activity update spec proc
		RETURN
    END

   DECLARE @next_scheduled_run_date DATETIME
   SET @next_scheduled_run_date = NULL

   -- If last rundate and last runtime is not null then convert date, time to datetime
   IF (@last_run_date IS NOT NULL AND @last_run_time IS NOT NULL)
   BEGIN
        SET @next_scheduled_run_date = [msdb].[dbo].[agent_datetime](@last_run_date, @last_run_time)
   END

   UPDATE [msdb].[dbo].[sysjobactivity]
   SET next_scheduled_run_date = @next_scheduled_run_date
   WHERE session_id = @session_id
   AND job_id = @job_id
END
GO

/**************************************************************/
/* SP_SQLAGENT_UPDATE_JOBACTIVITY_REQUESTED_DATE              */
/**************************************************************/
IF (NOT OBJECT_ID('dbo.sp_sqlagent_update_jobactivity_requested_date', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_sqlagent_update_jobactivity_requested_date]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_sqlagent_update_jobactivity_requested_date]...'
GO
CREATE PROCEDURE [dbo].[sp_sqlagent_update_jobactivity_requested_date]
    @session_id               INT,
    @job_id                   UNIQUEIDENTIFIER,
    @is_system             TINYINT = 0,
    @run_requested_source_id  TINYINT
AS
BEGIN
    IF(@is_system = 1)
    BEGIN
		-- TODO:: Call job activity update spec proc
		RETURN
    END

    -- update sysjobactivity for user jobs
    UPDATE [msdb].[dbo].[sysjobactivity]
    SET run_requested_date = DATEADD(ms, -DATEPART(ms, GETDATE()),  GETDATE()),
        run_requested_source = CONVERT(SYSNAME, @run_requested_source_id),
        queued_date = NULL,
        start_execution_date = NULL,
        last_executed_step_id = NULL,
        last_executed_step_date = NULL,
        stop_execution_date = NULL,
        job_history_id = NULL,
        next_scheduled_run_date = NULL
    WHERE job_id = @job_id
    AND session_id = @session_id
END
GO

/**************************************************************/
/* SP_SQLAGENT_UPDATE_JOBACTIVITY_QUEUED_DATE                 */
/**************************************************************/
IF (NOT OBJECT_ID('dbo.sp_sqlagent_update_jobactivity_queued_date', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_sqlagent_update_jobactivity_queued_date]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_sqlagent_update_jobactivity_queued_date]...'
GO
CREATE PROCEDURE [dbo].[sp_sqlagent_update_jobactivity_queued_date]
    @session_id               INT,
    @job_id                   UNIQUEIDENTIFIER,
    @is_system             TINYINT = 0
AS
BEGIN
    IF(@is_system = 1)
    BEGIN
		-- TODO:: Call job activity update spec proc
		RETURN
    END

    UPDATE [msdb].[dbo].[sysjobactivity]
    SET queued_date = DATEADD(ms, -DATEPART(ms, GETDATE()),  GETDATE())
    WHERE job_id = @job_id
    AND session_id = @session_id
END
GO

/**************************************************************/
/* SP_SQLAGENT_UPDATE_JOBACTIVITY_START_EXECUTION_DATE        */
/**************************************************************/
IF (NOT OBJECT_ID('dbo.sp_sqlagent_update_jobactivity_start_execution_date', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_sqlagent_update_jobactivity_start_execution_date]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_sqlagent_update_jobactivity_start_execution_date]...'
GO
CREATE PROCEDURE [dbo].[sp_sqlagent_update_jobactivity_start_execution_date]
    @session_id               INT,
    @job_id                   UNIQUEIDENTIFIER,
    @is_system                TINYINT = 0,
    @begin_execution_date     INT,
    @begin_execution_time     INT
AS
BEGIN
    IF(@is_system = 1)
    BEGIN
		-- TODO:: Call job activity update spec proc
		RETURN
    END

   DECLARE @start_execution_date DATETIME
   SET @start_execution_date = [msdb].[dbo].[agent_datetime](@begin_execution_date, @begin_execution_time)

   UPDATE [msdb].[dbo].[sysjobactivity]
   SET start_execution_date = @start_execution_date
   WHERE session_id = @session_id
   AND job_id = @job_id
END
GO

/**************************************************************/
/* SP_SQLAGENT_LOG_JOBHISTORY                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_log_jobhistory...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_sqlagent_log_jobhistory')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_log_jobhistory
go
CREATE PROCEDURE sp_sqlagent_log_jobhistory
  @job_id               UNIQUEIDENTIFIER,
  @step_id              INT,
  @sql_message_id       INT = 0,
  @sql_severity         INT = 0,
  @message              NVARCHAR(4000) = NULL,
  @run_status           INT, -- SQLAGENT_EXEC_X code
  @run_date             INT,
  @run_time             INT,
  @run_duration         INT,
  @operator_id_emailed  INT = 0,
  @operator_id_netsent  INT = 0,
  @operator_id_paged    INT = 0,
  @retries_attempted    INT,
  @server               sysname = NULL,
  @session_id           INT = 0
AS
BEGIN
  DECLARE @retval              INT
  DECLARE @operator_id_as_char VARCHAR(10)
  DECLARE @step_name           sysname
  DECLARE @error_severity      INT

  SET NOCOUNT ON

  IF (@server IS NULL) OR (UPPER(@server collate SQL_Latin1_General_CP1_CS_AS) = '(LOCAL)')
    SELECT @server = UPPER(CONVERT(sysname, SERVERPROPERTY('ServerName')))

  -- Check authority (only SQLServerAgent can add a history entry for a job)
  EXECUTE @retval = sp_verify_jobproc_caller @job_id = @job_id, @program_name = N'SQLAgent%'
  IF (@retval <> 0)
    RETURN(@retval)

  -- NOTE: We raise all errors as informational (sev 0) to prevent SQLServerAgent from caching
  --       the operation (if it fails) since if the operation will never run successfully we
  --       don't want it to stay around in the operation cache.
  SELECT @error_severity = 0

  -- Check job_id
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysjobs_view
                  WHERE (job_id = @job_id)))
  BEGIN
    DECLARE @job_id_as_char      VARCHAR(36)
    SELECT @job_id_as_char = CONVERT(VARCHAR(36), @job_id)
    RAISERROR(14262, @error_severity, -1, 'Job', @job_id_as_char)
    RETURN(1) -- Failure
  END

  -- Check step id
  IF (@step_id <> 0) -- 0 means 'for the whole job'
  BEGIN
    SELECT @step_name = step_name
    FROM msdb.dbo.sysjobsteps
    WHERE (job_id = @job_id)
      AND (step_id = @step_id)
    IF (@step_name IS NULL)
    BEGIN
      DECLARE @step_id_as_char     VARCHAR(10)
      SELECT @step_id_as_char = CONVERT(VARCHAR, @step_id)
      RAISERROR(14262, @error_severity, -1, '@step_id', @step_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
    SELECT @step_name = FORMATMESSAGE(14570)

  -- Check run_status
  IF (@run_status NOT IN (0, 1, 2, 3, 4, 5)) -- SQLAGENT_EXEC_X code
  BEGIN
    RAISERROR(14266, @error_severity, -1, '@run_status', '0, 1, 2, 3, 4, 5')
    RETURN(1) -- Failure
  END

  -- Check run_date
  EXECUTE @retval = sp_verify_job_date @run_date, '@run_date', 10
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check run_time
  EXECUTE @retval = sp_verify_job_time @run_time, '@run_time', 10
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check operator_id_emailed
  IF (@operator_id_emailed <> 0)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysoperators
                    WHERE (id = @operator_id_emailed)))
    BEGIN
      SELECT @operator_id_as_char = CONVERT(VARCHAR, @operator_id_emailed)
      RAISERROR(14262, @error_severity, -1, '@operator_id_emailed', @operator_id_as_char)
      RETURN(1) -- Failure
    END
  END

  -- Check operator_id_netsent
  IF (@operator_id_netsent <> 0)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysoperators
                    WHERE (id = @operator_id_netsent)))
    BEGIN
      SELECT @operator_id_as_char = CONVERT(VARCHAR, @operator_id_netsent)
      RAISERROR(14262, @error_severity, -1, '@operator_id_netsent', @operator_id_as_char)
      RETURN(1) -- Failure
    END
  END

  -- Check operator_id_paged
  IF (@operator_id_paged <> 0)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysoperators
                    WHERE (id = @operator_id_paged)))
    BEGIN
      SELECT @operator_id_as_char = CONVERT(VARCHAR, @operator_id_paged)
      RAISERROR(14262, @error_severity, -1, '@operator_id_paged', @operator_id_as_char)
      RETURN(1) -- Failure
    END
  END

  -- Insert the history row
  INSERT INTO msdb.dbo.sysjobhistory
         (job_id,
          step_id,
          step_name,
          sql_message_id,
          sql_severity,
          message,
          run_status,
          run_date,
          run_time,
          run_duration,
          operator_id_emailed,
          operator_id_netsent,
          operator_id_paged,
          retries_attempted,
          server)
  VALUES (@job_id,
          @step_id,
          @step_name,
          @sql_message_id,
          @sql_severity,
          @message,
          @run_status,
          @run_date,
          @run_time,
          @run_duration,
          @operator_id_emailed,
          @operator_id_netsent,
          @operator_id_paged,
          @retries_attempted,
          @server)

  -- Update sysjobactivity table
  IF (@step_id = 0) --only update for job, not for each step
  BEGIN
    UPDATE msdb.dbo.sysjobactivity
    SET stop_execution_date = DATEADD(ms, -DATEPART(ms, GetDate()),  GetDate()),
        job_history_id = SCOPE_IDENTITY()
    WHERE
        session_id = @session_id AND job_id = @job_id
  END
  -- Special handling of replication jobs
  DECLARE @job_name sysname
  DECLARE @category_id int
  SELECT  @job_name = name, @category_id = category_id from msdb.dbo.sysjobs
   WHERE job_id = @job_id

  -- If replicatio agents (snapshot, logreader, distribution, merge, and queuereader
  -- and the step has been canceled and if we are at the distributor.
  IF @category_id in (10,13,14,15,19) and @run_status = 3 and
   object_id('MSdistributiondbs') is not null
  BEGIN
    -- Get the database
    DECLARE @database sysname
    SELECT @database = database_name from sysjobsteps where job_id = @job_id and
   lower(subsystem) in (N'distribution', N'logreader','snapshot',N'merge',
      N'queuereader')
    -- If the database is a distribution database
    IF EXISTS (select * from MSdistributiondbs where name = @database)
    BEGIN
   DECLARE @proc nvarchar(500)
   SELECT @proc = quotename(@database) + N'.dbo.sp_MSlog_agent_cancel'
   EXEC @proc @job_id = @job_id, @category_id = @category_id,
      @message = @message
    END
  END

  -- Delete any history rows that are over the registry-defined limits
  IF (@step_id = 0) --only check once per job execution.
  BEGIN
    EXECUTE msdb.dbo.sp_jobhistory_row_limiter @job_id
  END

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_SQLAGENT_CHECK_MSX_VERSION                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_check_msx_version...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_sqlagent_check_msx_version')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_check_msx_version
go
CREATE PROCEDURE sp_sqlagent_check_msx_version
  @required_microsoft_version INT = NULL
AS
BEGIN
  SET NOCOUNT ON

  DECLARE @msx_version          NVARCHAR(16)
  DECLARE @required_msx_version NVARCHAR(16)

  IF (@required_microsoft_version IS NULL)
    SELECT @required_microsoft_version = 0x07000252 -- 7.0.594

  IF (@@microsoftversion < @required_microsoft_version)
  BEGIN
    SELECT @msx_version = CONVERT( NVARCHAR(2), CONVERT( INT, CONVERT( BINARY(1), @@microsoftversion / 0x1000000 ) ) )
   + N'.'
   + CONVERT( NVARCHAR(2), CONVERT( INT, CONVERT( BINARY(1), CONVERT( BINARY(2), ((@@microsoftversion / 0x10000) % 0x100) ) ) ) )
   + N'.'
   + CONVERT( NVARCHAR(4), @@microsoftversion % 0x10000 )

    SELECT @required_msx_version = CONVERT( NVARCHAR(2), CONVERT( INT, CONVERT( BINARY(1), @required_microsoft_version / 0x1000000 ) ) )
   + N'.'
   + CONVERT( NVARCHAR(2), CONVERT( INT, CONVERT( BINARY(1), CONVERT( BINARY(2), ((@required_microsoft_version / 0x10000) % 0x100) ) ) ) )
   + N'.'
   + CONVERT( NVARCHAR(4), @required_microsoft_version % 0x10000 )

   RAISERROR(14541, -1, -1, @msx_version, @required_msx_version)
    RETURN(1) -- Failure
  END
  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_SQLAGENT_PROBE_MSX                                      */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sqlagent_probe_msx...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_sqlagent_probe_msx')
              AND (type = 'P')))
  DROP PROCEDURE sp_sqlagent_probe_msx
go
CREATE PROCEDURE sp_sqlagent_probe_msx
  @server_name          sysname,  -- The name of the target server probing the MSX
  @local_time           NVARCHAR(100), -- The local time at the target server in the format YYYY/MM/DD HH:MM:SS
  @poll_interval        INT,           -- The frequency (in seconds) with which the target polls the MSX
  @time_zone_adjustment INT = NULL     -- The offset from GMT in minutes (may be NULL if unknown)
AS
BEGIN
  DECLARE @bad_enlistment        BIT
  DECLARE @blocking_instructions INT
  DECLARE @pending_instructions  INT

  SET NOCOUNT ON

  SELECT @server_name = UPPER(@server_name)
  SELECT @bad_enlistment = 0, @blocking_instructions = 0, @pending_instructions = 0

  UPDATE msdb.dbo.systargetservers
  SET last_poll_date = GETDATE(),
      local_time_at_last_poll = CONVERT(DATETIME, @local_time, 111),
      poll_interval = @poll_interval,
      time_zone_adjustment = ISNULL(@time_zone_adjustment, time_zone_adjustment)
  WHERE (UPPER(server_name) = @server_name)

  -- If the systargetservers entry is missing (and no DEFECT instruction has been posted)
  -- then the enlistment is bad
  IF (NOT EXISTS (SELECT 1
                  FROM msdb.dbo.systargetservers
                  WHERE (UPPER(server_name) = @server_name))) AND
     (NOT EXISTS (SELECT 1
                  FROM msdb.dbo.sysdownloadlist
                  WHERE (target_server = @server_name)
                    AND (operation_code = 7)
                    AND (object_type = 2)))
    SELECT @bad_enlistment = 1

  SELECT @blocking_instructions = COUNT(*)
  FROM msdb.dbo.sysdownloadlist
  WHERE (target_server = @server_name)
    AND (error_message IS NOT NULL)

  SELECT @pending_instructions = COUNT(*)
  FROM msdb.dbo.sysdownloadlist
  WHERE (target_server = @server_name)
    AND (error_message IS NULL)
    AND (status = 0)

  SELECT @bad_enlistment, @blocking_instructions, @pending_instructions
END
go

/**************************************************************/
/* SP_SET_LOCAL_TIME                                          */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_set_local_time...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_set_local_time')
              AND (type = 'P')))
  DROP PROCEDURE sp_set_local_time
go
CREATE PROCEDURE sp_set_local_time
  @server_name           sysname = NULL,
  @adjustment_in_minutes INT          = 0 -- Only needed for Win9x
AS
BEGIN
  DECLARE @ret              INT
  DECLARE @local_time       INT
  DECLARE @local_date       INT
  DECLARE @current_datetime DATETIME
  DECLARE @local_time_sz    VARCHAR(30)
  DECLARE @cmd              NVARCHAR(200)
  DECLARE @date_format      NVARCHAR(64)
  DECLARE @year_sz          NVARCHAR(16)
  DECLARE @month_sz         NVARCHAR(16)
  DECLARE @day_sz           NVARCHAR(16)

  -- Synchronize the clock with the remote server (if supplied)
  -- NOTE: NT takes timezones into account, whereas Win9x does not
  IF (@server_name IS NOT NULL)
  BEGIN
    -- Sanitize the input string so it is ready to be passed to the shell
    -- Same logic as in sys.fn_escapecmdshellsymbolsremovequotes in REPL.
    DECLARE @escaped_command_string NVARCHAR(4000), @curr_char NVARCHAR(1), @curr_char_index INT
    SELECT @escaped_command_string = N'', @curr_char = N'', @curr_char_index = 1
    WHILE @curr_char_index <= len(@server_name)
    BEGIN
      SELECT @curr_char = substring(@server_name, @curr_char_index, 1)
      IF @curr_char in (N'%', N'<', N'>', N'|', N'&', N'^')
      BEGIN
        SELECT @escaped_command_string = @escaped_command_string + N'^'
      END

      IF @curr_char <> '"'
      BEGIN
        SELECT @escaped_command_string = @escaped_command_string + @curr_char
      END
      SELECT @curr_char_index = @curr_char_index + 1
    END

    SELECT @cmd = N'net time "\\' + @escaped_command_string collate database_default + N'" /set /y'
    EXECUTE @ret = master.dbo.xp_cmdshell @cmd, no_output
    IF (@ret <> 0)
      RETURN(1) -- Failure
  END

  -- Since NET TIME on Win9x does not take time zones into account we need to manually adjust
  -- for this using @adjustment_in_minutes which will be the difference between the MSX GMT
  -- offset and the target server GMT offset
  IF ((PLATFORM() & 0x2) = 0x2) -- Win9x
  BEGIN
    -- Get the date format from the registry (so that we can construct our DATE command-line command)
    EXECUTE master.dbo.xp_regread N'HKEY_CURRENT_USER',
                                  N'Control Panel\International',
                                  N'sShortDate',
                                  @date_format OUTPUT,
                                  N'no_output'
    SELECT @date_format = LOWER(@date_format)

    IF (@adjustment_in_minutes <> 0)
    BEGIN
      -- Wait for SQLServer to re-cache the OS time
      WAITFOR DELAY '00:01:00'

      SELECT @current_datetime = DATEADD(mi, @adjustment_in_minutes, GETDATE())
      SELECT @local_time_sz = SUBSTRING(CONVERT(VARCHAR, @current_datetime, 8), 1, 5)
      SELECT @local_time = CONVERT(INT, LTRIM(SUBSTRING(@local_time_sz, 1, PATINDEX('%:%', @local_time_sz) - 1)  + SUBSTRING(@local_time_sz, PATINDEX('%:%', @local_time_sz) + 1, 2)))
      SELECT @local_date = CONVERT(INT, CONVERT(VARCHAR, @current_datetime, 112))

      -- Set the date
      SELECT @year_sz = CONVERT(NVARCHAR, @local_date / 10000)
      SELECT @month_sz = CONVERT(NVARCHAR, (@local_date % 10000) / 100)
      SELECT @day_sz = CONVERT(NVARCHAR, @local_date % 100)

      IF (@date_format LIKE N'y%m%d')
        SELECT @cmd = N'DATE ' + @year_sz + N'-' + @month_sz + N'-' + @day_sz
      IF (@date_format LIKE N'y%d%m')
        SELECT @cmd = N'DATE ' + @year_sz + N'-' + @day_sz + N'-' + @month_sz
      IF (@date_format LIKE N'm%d%y')
        SELECT @cmd = N'DATE ' + @month_sz + N'-' + @day_sz + N'-' + @year_sz
      IF (@date_format LIKE N'd%m%y')
        SELECT @cmd = N'DATE ' + @day_sz + N'-' + @month_sz + N'-' + @year_sz

      EXECUTE @ret = master.dbo.xp_cmdshell @cmd, no_output
      IF (@ret <> 0)
        RETURN 1 -- Failure

      -- Set the time (NOTE: We can't set the millisecond part of the time, so we may be up to .999 sec off)
      SELECT @cmd = N'TIME ' + CONVERT(NVARCHAR, @local_time / 100) + N':' + CONVERT(NVARCHAR, @local_time % 100) + ':' + CONVERT(NVARCHAR(2), DATEPART(SS, GETDATE()))
      EXECUTE @ret = master.dbo.xp_cmdshell @cmd, no_output
      IF (@ret <> 0)
        RETURN 1 -- Failure
    END

  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_MULTI_SERVER_JOB_SUMMARY [used by SEM only]             */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_multi_server_job_summary...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_multi_server_job_summary')
              AND (type = 'P')))
  DROP PROCEDURE sp_multi_server_job_summary
go
CREATE PROCEDURE sp_multi_server_job_summary
  @job_id   UNIQUEIDENTIFIER = NULL,
  @job_name sysname          = NULL
AS
BEGIN
  DECLARE @retval INT

  SET NOCOUNT ON

  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END

  -- NOTE: We join with syscategories - not sysjobservers - since we want to include jobs
  --       which are of type multi-server but which don't currently have any servers
  SELECT 'job_id'   = sj.job_id,
         'job_name' = sj.name,
         'enabled'  = sj.enabled,
         'category_name'  = sc.name,
         'target_servers' = (SELECT COUNT(*)
                             FROM msdb.dbo.sysjobservers sjs
                             WHERE (sjs.job_id = sj.job_id)),
         'pending_download_instructions' = (SELECT COUNT(*)
                                            FROM msdb.dbo.sysdownloadlist sdl
                                            WHERE (sdl.object_id = sj.job_id)
                                              AND (status = 0)),
         'download_errors' = (SELECT COUNT(*)
                              FROM msdb.dbo.sysdownloadlist sdl
                              WHERE (sdl.object_id = sj.job_id)
                                AND (sdl.error_message IS NOT NULL)),
         'execution_failures' = (SELECT COUNT(*)
                                 FROM msdb.dbo.sysjobservers sjs
                                 WHERE (sjs.job_id = sj.job_id)
                                   AND (sjs.last_run_date <> 0)
                                   AND (sjs.last_run_outcome <> 1)) -- 1 is success
  FROM msdb.dbo.sysjobs sj,
       msdb.dbo.syscategories sc
  WHERE (sj.category_id = sc.category_id)
    AND (sc.category_class = 1) -- JOB
    AND (sc.category_type  = 2) -- Multi-Server
    AND ((@job_id IS NULL)   OR (sj.job_id = @job_id))
    AND ((@job_name IS NULL) OR (sj.name = @job_name))

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_TARGET_SERVER_SUMMARY [used by SEM only]                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_target_server_summary...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_target_server_summary')
              AND (type = 'P')))
  DROP PROCEDURE sp_target_server_summary
go
CREATE PROCEDURE sp_target_server_summary
  @target_server sysname = NULL
AS
BEGIN
  SET NOCOUNT ON

  SELECT server_id,
         server_name,
        'local_time' = DATEADD(SS, DATEDIFF(SS, last_poll_date, GETDATE()), local_time_at_last_poll),
         last_poll_date,
        'unread_instructions' = (SELECT COUNT(*)
                                 FROM msdb.dbo.sysdownloadlist sdl
                                 WHERE (UPPER(sdl.target_server) = UPPER(sts.server_name))
                                   AND (sdl.status = 0)),
        'blocked' = (SELECT COUNT(*)
                     FROM msdb.dbo.sysdownloadlist sdl
                     WHERE (UPPER(sdl.target_server) = UPPER(sts.server_name))
                       AND (sdl.error_message IS NOT NULL)),
         poll_interval
  FROM msdb.dbo.systargetservers sts
  WHERE ((@target_server IS NULL) OR (UPPER(@target_server) = UPPER(sts.server_name)))
END
go

CHECKPOINT
go


/**************************************************************/
/*                                                            */
/*         6  .  X     P  R  O  C  E  D  U  R  E  S           */
/*                                                            */
/* These procedures are provided for backwards compatability  */
/* with 6.x scripts and 6.x replication.  The re-implemented  */
/* procedures are as follows:                                 */
/*                                                            */
/* - sp_uniquetaskname  (SQLDMO)                              */
/* - systasks_view      (INSTDIST.SQL)                        */
/* - sp_addtask         (INSTREPL.SQL, INSTDIST.SQL, SQLDMO)  */
/* - sp_droptask        (INSTREPL.SQL, INSTDIST.SQL, SQLDMO)  */
/* - systasks           (For completeness only)               */
/**************************************************************/


/**************************************************************/
/* SP_UNIQUETASKNAME                                          */
/**************************************************************/

PRINT ''
PRINT 'Creating [legacy] procedure sp_uniquetaskname...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_uniquetaskname')
              AND (type = 'P')))
  DROP PROCEDURE sp_uniquetaskname
go
CREATE PROCEDURE sp_uniquetaskname
  @seed NVARCHAR(92)
AS
BEGIN
  DECLARE @newest_suffix INT

  SET NOCOUNT ON

  -- We're going to add a suffix of 8 characters so make sure the seed is at most 84 characters
  SELECT @seed = LTRIM(RTRIM(@seed))
  IF (DATALENGTH(@seed) > 0)
    SELECT @seed = SUBSTRING(@seed, 1, 84)

  -- Find the newest (highest) suffix so far
  SELECT @newest_suffix = MAX(CONVERT(INT, RIGHT(name, 8)))
  FROM msdb.dbo.sysjobs -- DON'T use sysjobs_view here!
  WHERE (name LIKE N'%[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]')

  -- Generate the task name by appending the 'newest suffix' value (plus one) to the seed
  IF (@newest_suffix IS NOT NULL)
  BEGIN
    SELECT @newest_suffix = @newest_suffix + 1
    SELECT 'TaskName' = CONVERT(NVARCHAR(92), @seed + REPLICATE(N'0', 8 - (DATALENGTH(CONVERT(NVARCHAR, @newest_suffix)) / 2)) + CONVERT(NVARCHAR, @newest_suffix))
  END
  ELSE
    SELECT 'TaskName' = CONVERT(NVARCHAR(92), @seed + N'00000001')
END
go

/**************************************************************/
/* SP_ADDTASK                                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating [legacy] procedure sp_addtask...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_addtask')
              AND (type = 'P')))
  DROP PROCEDURE sp_addtask
go
CREATE PROCEDURE sp_addtask
  @name                   sysname,               -- Was VARCHAR(100) in 6.x
  @subsystem              NVARCHAR(40)   = N'TSQL', -- Was VARCHAR(30) in 6.x
  @server                 sysname        = NULL,
  @username               sysname        = NULL, -- Was VARCHAR(30) in 6.x
  @databasename           sysname        = NULL, -- Was VARCHAR(30) in 6.x
  @enabled                TINYINT        = 0,
  @freqtype               INT            = 2,    -- 2 means OnDemand
  @freqinterval           INT            = 1,
  @freqsubtype            INT            = 1,
  @freqsubinterval        INT            = 1,
  @freqrelativeinterval   INT            = 1,
  @freqrecurrencefactor   INT            = 1,
  @activestartdate        INT            = 0,
  @activeenddate          INT            = 0,
  @activestarttimeofday   INT            = 0,
  @activeendtimeofday     INT            = 0,
  @nextrundate            INT            = 0,
  @nextruntime            INT            = 0,
  @runpriority            INT            = 0,
  @emailoperatorname      sysname        = NULL, -- Was VARCHAR(50) in 6.x
  @retryattempts          INT            = 0,
  @retrydelay             INT            = 10,
  @command                NVARCHAR(max) = NULL,
  @loghistcompletionlevel INT            = 2,
  @emailcompletionlevel   INT            = 0,
  @description            NVARCHAR(512)  = NULL, -- Was VARCHAR(255) in 6.x
  @tagadditionalinfo      VARCHAR(96)    = NULL, -- Obsolete in 7.0
  @tagobjectid            INT            = NULL, -- Obsolete in 7.0
  @tagobjecttype          INT            = NULL, -- Obsolete in 7.0
  @newid                  INT            = NULL OUTPUT,
  @parameters             NVARCHAR(max)  = NULL, -- Was TEXT in 6.x
  @cmdexecsuccesscode     INT            = 0,
  @category_name          sysname        = NULL, -- New for 7.0
  @category_id            INT            = NULL  -- New for 7.0
AS
BEGIN
  DECLARE @retval INT
  DECLARE @job_id UNIQUEIDENTIFIER
  DECLARE @id     INT
  DECLARE @distdb sysname
  DECLARE @proc nvarchar(255)

  SET NOCOUNT ON

  IF (SERVERPROPERTY('EngineEdition') = 8)
    SET @loghistcompletionlevel = 0

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (SERVERPROPERTY('EngineEdition') = 8 AND @freqtype = 0x80)
  BEGIN
    RAISERROR(41914, -1, 8, 'Schedule job ONIDLE')
    RETURN(1) -- Failure
  END

  SELECT @retval = 1 -- 0 means success, 1 means failure

  -- Set 7.0 category names for 6.5 replication tasks
  IF (LOWER(@subsystem) = N'sync')
    SELECT @category_id = 15
  ELSE IF (LOWER(@subsystem) = N'logreader')
    SELECT @category_id = 13
  ELSE IF (LOWER(@subsystem) = N'distribution')
    SELECT @category_id = 10

  -- Convert old replication synchronization subsystem name to the 7.0 name
  IF (LOWER(@subsystem) = N'sync')
    SELECT @subsystem = N'Snapshot'

  -- If a category ID is provided this overrides any supplied category name
  IF (@category_id IS NOT NULL)
  BEGIN
    SELECT @category_name = name
    FROM msdb.dbo.syscategories
    WHERE (category_id = @category_id)
    SELECT @category_name = ISNULL(@category_name, FORMATMESSAGE(14205))
  END

  -- In 6.x active start date was not restricted, but it is in 7.0; so to avoid a "noisey"
  -- failure in sp_add_jobschedule we modify the value accordingly
  IF ((@activestartdate <> 0) AND (@activestartdate < 19900101))
    SELECT @activestartdate = 19900101

  BEGIN TRANSACTION

    -- Add the job
    EXECUTE @retval = sp_add_job
      @job_name                   = @name,
      @enabled                    = @enabled,
      @start_step_id              = 1,
      @description                = @description,
      @category_name              = @category_name,
      @notify_level_eventlog      = @loghistcompletionlevel,
      @notify_level_email         = @emailcompletionlevel,
      @notify_email_operator_name = @emailoperatorname,
      @job_id                     = @job_id OUTPUT

    IF (@retval <> 0)
    BEGIN
      ROLLBACK TRANSACTION
      GOTO Quit
    END

    -- Add an entry to systaskids for the new job (created by a 6.x client)
    INSERT INTO msdb.dbo.systaskids (job_id) VALUES (@job_id)

    -- Get the assigned task id
    SELECT @id = task_id, @newid = task_id
    FROM msdb.dbo.systaskids
    WHERE (job_id = @job_id)

    -- Add the job step
    EXECUTE @retval = sp_add_jobstep
      @job_id                = @job_id,
      @step_id               = 1,
      @step_name             = N'Step 1',
      @subsystem             = @subsystem,
      @command               = @command,
      @additional_parameters = @parameters,
      @cmdexec_success_code  = @cmdexecsuccesscode,
      @server                = @server,
      @database_name         = @databasename,
      @database_user_name    = @username,
      @retry_attempts        = @retryattempts,
      @retry_interval        = @retrydelay,
      @os_run_priority       = @runpriority

    IF (@retval <> 0)
    BEGIN
      ROLLBACK TRANSACTION
      GOTO Quit
    END

    -- Add the job schedule
    IF (@activestartdate = 0)
      SELECT @activestartdate = NULL
    IF (@activeenddate = 0)
      SELECT @activeenddate = NULL
    IF (@activestarttimeofday = 0)
      SELECT @activestarttimeofday = NULL
    IF (@activeendtimeofday = 0)
      SELECT @activeendtimeofday = NULL
    IF (@freqtype <> 0x2) -- OnDemand tasks simply have no schedule in 7.0
    BEGIN

      EXECUTE @retval = sp_add_jobschedule
        @job_id                 = @job_id,
        @name                   = N'6.x schedule',
        @enabled                = 1,
        @freq_type              = @freqtype,
        @freq_interval          = @freqinterval,
        @freq_subday_type       = @freqsubtype,
        @freq_subday_interval   = @freqsubinterval,
        @freq_relative_interval = @freqrelativeinterval,
        @freq_recurrence_factor = @freqrecurrencefactor,
        @active_start_date      = @activestartdate,
        @active_end_date        = @activeenddate,
        @active_start_time      = @activestarttimeofday,
        @active_end_time        = @activeendtimeofday

      IF (@retval <> 0)
      BEGIN
        ROLLBACK TRANSACTION
        GOTO Quit
      END
    END

    -- And finally, add the job server
    EXECUTE @retval = sp_add_jobserver @job_id = @job_id, @server_name = NULL

    IF (@retval <> 0)
    BEGIN
      ROLLBACK TRANSACTION
      GOTO Quit
    END

    -- Add the replication agent for monitoring
    IF (@category_id = 13) -- Logreader
    BEGIN
      SELECT @distdb = distribution_db from MSdistpublishers where name = @server
      SELECT @proc = @distdb + '.dbo.sp_MSadd_logreader_agent'

      EXECUTE @retval = @proc
        @name = @name,
        @publisher = @server,
        @publisher_db = @databasename,
        @publication = '',
        @local_job = 1,
        @job_existing = 1,
        @job_id = @job_id

      IF (@retval <> 0)
      BEGIN
        ROLLBACK TRANSACTION
        GOTO Quit
      END
    END
    ELSE
    IF (@category_id = 15) -- Snapshot
    BEGIN
      DECLARE @publication sysname

      EXECUTE @retval = master.dbo.sp_MSget_publication_from_taskname
                            @taskname = @name,
                            @publisher = @server,
                            @publisherdb = @databasename,
                            @publication = @publication OUTPUT

      IF (@publication IS NOT NULL)
      BEGIN

        SELECT @distdb = distribution_db from MSdistpublishers where name = @server
        SELECT @proc = @distdb + '.dbo.sp_MSadd_snapshot_agent'

        EXECUTE @retval = @proc
                @name = @name,
                @publisher = @server,
                @publisher_db = @databasename,
                @publication = @publication,
                @local_job = 1,
                @job_existing = 1,
                @snapshot_jobid = @job_id

        IF (@retval <> 0)
        BEGIN
          ROLLBACK TRANSACTION
          GOTO Quit
        END

        SELECT @proc = @distdb + '.dbo.sp_MSadd_publication'
        EXECUTE @retval = @proc
                @publisher = @server,
                @publisher_db = @databasename,
                @publication = @publication,
                @publication_type = 0 -- Transactional
        IF (@retval <> 0)
        BEGIN
          ROLLBACK TRANSACTION
          GOTO Quit
        END
      END
    END

  COMMIT TRANSACTION

  -- If this is an autostart LogReader or Distribution job, add the [new] '-Continuous' paramter to the command
  IF (@freqtype = 0x40) AND ((UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = N'LOGREADER') OR (UPPER(@subsystem collate SQL_Latin1_General_CP1_CS_AS) = N'DISTRIBUTION'))
  BEGIN
    UPDATE msdb.dbo.sysjobsteps
    SET command = command + N' -Continuous'
    WHERE (job_id = @job_id)
      AND (step_id = 1)
  END

  -- If this is an autostart job, start it now (for backwards compatibility with 6.x SQLExecutive behaviour)
  IF (@freqtype = 0x40)
    EXECUTE msdb.dbo.sp_start_job @job_id = @job_id, @error_flag = 0, @output_flag = 0

Quit:
  RETURN(@retval) -- 0 means success

END
go


/**************************************************************/
/* SP_DROPTASK                                                */
/**************************************************************/

PRINT ''
PRINT 'Creating [legacy] procedure sp_droptask...'

go

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_droptask')
              AND (type = 'P')))
  DROP PROCEDURE sp_droptask

go

CREATE PROCEDURE sp_droptask
  @name      sysname = NULL, -- Was VARCHAR(100) in 6.x
  @loginname sysname = NULL, -- Was VARCHAR(30) in 6.x
  @id        INT     = NULL
AS
BEGIN
  DECLARE @retval INT
  DECLARE @job_id UNIQUEIDENTIFIER
  DECLARE @category_id int

  SET NOCOUNT ON

  IF ((@name      IS NULL)     AND (@id    IS NULL)     AND (@loginname IS NULL)) OR
     ((@name      IS NOT NULL) AND ((@id   IS NOT NULL) OR  (@loginname IS NOT NULL))) OR
     ((@id        IS NOT NULL) AND ((@name IS NOT NULL) OR  (@loginname IS NOT NULL))) OR
     ((@loginname IS NOT NULL) AND ((@name IS NOT NULL) OR  (@id        IS NOT NULL)))
  BEGIN
    RAISERROR(14245, -1, -1)
    RETURN(1) -- Failure
  END

  -- If the name is supplied, get the job_id directly from sysjobs
  IF (@name IS NOT NULL)
  BEGIN
    -- Check if the name is ambiguous
    IF ((SELECT COUNT(*)
         FROM msdb.dbo.sysjobs_view
         WHERE (name = @name)) > 1)
    BEGIN
      RAISERROR(14292, -1, -1, @name, '@id', '@name')
      RETURN(1) -- Failure
    END

    SELECT @job_id = job_id, @category_id = category_id
    FROM msdb.dbo.sysjobs_view
    WHERE (name = @name)

    SELECT @id = task_id
    FROM msdb.dbo.systaskids
    WHERE (job_id = @job_id)

    IF (@job_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@name', @name)
      RETURN(1) -- Failure
    END
  END

  -- If the id is supplied lookup the corresponding job_id from systaskids
  IF (@id IS NOT NULL)
  BEGIN
    SELECT @job_id = job_id
    FROM msdb.dbo.systaskids
    WHERE (task_id = @id)

    -- Check that the job still exists
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysjobs_view
                    WHERE (job_id = @job_id)))
    BEGIN
      SELECT @name = CONVERT(NVARCHAR, @id)
      RAISERROR(14262, -1, -1, '@id', @name)
      RETURN(1) -- Failure
    END

    -- Get the name of this job
    SELECT @name = name, @category_id = category_id
    FROM msdb.dbo.sysjobs_view
    WHERE (job_id = @job_id)
  END

  -- Delete the specific job
  IF (@name IS NOT NULL)
  BEGIN
    BEGIN TRANSACTION

    DELETE FROM msdb.dbo.systaskids
    WHERE (job_id = @job_id)
    EXECUTE @retval = sp_delete_job @job_id = @job_id
    IF (@retval <> 0)
   BEGIN
      ROLLBACK TRANSACTION
     GOTO Quit
   END

   -- If a Logreader or Snapshot task, delete corresponding replication agent information
   IF @category_id = 13 or @category_id = 15
   BEGIN
        EXECUTE @retval = sp_MSdrop_6x_replication_agent @job_id, @category_id
     IF (@retval <> 0)
     BEGIN
      ROLLBACK TRANSACTION
      GOTO Quit
     END
   END

    COMMIT TRANSACTION
  END

  -- Delete all jobs belonging to the specified login
  IF (@loginname IS NOT NULL)
  BEGIN
    BEGIN TRANSACTION

    DELETE FROM msdb.dbo.systaskids
    WHERE job_id IN (SELECT job_id
                     FROM msdb.dbo.sysjobs_view
                     WHERE (owner_sid = SUSER_SID(@loginname)))
    EXECUTE @retval = sp_manage_jobs_by_login @action = 'DELETE',
                                              @current_owner_login_name = @loginname
    IF (@retval <> 0)
    BEGIN
      ROLLBACK TRANSACTION
      GOTO Quit
    END

    COMMIT TRANSACTION
  END

Quit:
  RETURN(@retval) -- 0 means success

END
go



/**************************************************************/
/*                                                            */
/*         E  R  R  O  R    M  E  S  S  A  G  E  S            */
/*                                                            */
/*  These are now created by MESSAGES.SQL.                    */
/*                                                            */
/*  NOTE: 14255 and 14265 are called by dynamic SQL generated */
/*        by SQLServerAgent.                                  */
/**************************************************************/


/**************************************************************/
/*                                                            */
/*                   T  R  I  G  G  E  R  S                   */
/*                                                            */
/**************************************************************/

/**************************************************************/
/* TRIG_TARGETSERVER_INSERT                                   */
/**************************************************************/

PRINT ''
PRINT 'Creating trigger trig_targetserver_insert...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_targetserver_insert')
              AND (type = 'TR')))
  DROP TRIGGER dbo.trig_targetserver_insert
go
CREATE TRIGGER trig_targetserver_insert
ON msdb.dbo.systargetservers
FOR INSERT, DELETE
AS
BEGIN
  SET NOCOUNT ON

  -- Disallow the insert if the server is called 'ALL'
  -- NOTE: We have to do this check here in the trigger since there is no sp_add_targetserver
  --       (target servers insert a row for themselves when they 'enlist' in an MSX)
  IF (EXISTS (SELECT *
              FROM inserted
              WHERE (server_name = N'ALL')))
  BEGIN
    DELETE FROM msdb.dbo.systargetservers
    WHERE (server_name = N'ALL')
    RAISERROR(14271, -1, -1, 'ALL')
    RETURN
  END

  -- Set (or delete) the registy flag (so that SETUP can detect if we're an MSX)
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.systargetservers) = 0)
  BEGIN
    DECLARE @val INT

    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'MSXServer',
                                           @val OUTPUT,
                                           N'no_output'
    IF (@val IS NOT NULL)
      EXECUTE master.dbo.xp_instance_regdeletevalue N'HKEY_LOCAL_MACHINE',
                                                    N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                                    N'MSXServer'
  END
  ELSE
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'MSXServer',
                                            N'REG_DWORD',
                                            1
END
go

CHECKPOINT
go



/**************************************************************/
/**                                                          **/
/**          A L E R T S  A N D  O P E R A T O R S           **/
/**                                                          **/
/**************************************************************/

/**************************************************************/
/*                                                            */
/*        C  O  R  E     P  R  O  C  E  D  U  R  E  S         */
/*                                                            */
/**************************************************************/


/**************************************************************/
/* SP_ADD_ALERT_INTERNAL                                      */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_alert_internal...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_alert_internal')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_alert_internal
go
CREATE PROCEDURE sp_add_alert_internal
  @name                         sysname,
  @message_id                   INT              = 0,
  @severity                     INT              = 0,
  @enabled                      TINYINT          = 1,
  @delay_between_responses      INT              = 0,
  @notification_message         NVARCHAR(512)    = NULL,
  @include_event_description_in TINYINT          = 5,    -- 0 = None, 1 = Email, 2 = Pager, 4 = NetSend, 7 = All
  @database_name                sysname          = NULL,
  @event_description_keyword    NVARCHAR(100)    = NULL,
  @job_id                       UNIQUEIDENTIFIER = NULL, -- If provided must NOT also provide job_name
  @job_name                     sysname          = NULL, -- If provided must NOT also provide job_id
  @raise_snmp_trap              TINYINT          = 0,
  @performance_condition        NVARCHAR(512)    = NULL, -- New for 7.0
  @category_name                sysname          = NULL, -- New for 7.0
 @wmi_namespace                NVARCHAR(512)     = NULL, -- New for 9.0
  @wmi_query                    NVARCHAR(512)     = NULL, -- New for 9.0
  @verify_alert                    TINYINT             = 1     -- 0 = do not verify alert, 1(or anything else) = verify alert before adding
AS
BEGIN
  DECLARE @event_source           NVARCHAR(100)
  DECLARE @event_category_id      INT
  DECLARE @event_id               INT
  DECLARE @last_occurrence_date   INT
  DECLARE @last_occurrence_time   INT
  DECLARE @last_notification_date INT
  DECLARE @last_notification_time INT
  DECLARE @occurrence_count       INT
  DECLARE @count_reset_date       INT
  DECLARE @count_reset_time       INT
  DECLARE @has_notification       INT
  DECLARE @return_code            INT
  DECLARE @duplicate_name         sysname
  DECLARE @category_id            INT
  DECLARE @alert_id               INT

  SET NOCOUNT ON

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (SERVERPROPERTY('EngineEdition') = 8)
   BEGIN
    RAISERROR(41914, -1, 19, 'alerting');
    RETURN(1) -- Failure
  END

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (SERVERPROPERTY('EngineEdition') = 8 AND (@include_event_description_in & 4 = 4))
  BEGIN
    RAISERROR(41914, -1, 11, 'NetSend')
    RETURN(1) -- Failure
  END

  -- Remove any leading/trailing spaces from parameters
  SELECT @name                      = LTRIM(RTRIM(@name))
  SELECT @notification_message      = LTRIM(RTRIM(@notification_message))
  SELECT @database_name             = LTRIM(RTRIM(@database_name))
  SELECT @event_description_keyword = LTRIM(RTRIM(@event_description_keyword))
  SELECT @job_name                  = LTRIM(RTRIM(@job_name))
  SELECT @performance_condition     = LTRIM(RTRIM(@performance_condition))
  SELECT @category_name             = LTRIM(RTRIM(@category_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@notification_message      = N'') SELECT @notification_message = NULL
  IF (@database_name             = N'') SELECT @database_name = NULL
  IF (@event_description_keyword = N'') SELECT @event_description_keyword = NULL
  IF (@job_name                  = N'') SELECT @job_name = NULL
  IF (@performance_condition     = N'') SELECT @performance_condition = NULL
  IF (@category_name             = N'') SELECT @category_name = NULL

  SELECT @message_id = ISNULL(@message_id, 0)
  SELECT @severity = ISNULL(@severity, 0)

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check if SQLServerAgent is in the process of starting
  EXECUTE @return_code = msdb.dbo.sp_is_sqlagent_starting
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  -- Hard-code the new Alert defaults
  -- event source needs to be instance aware
  DECLARE @instance_name sysname
  SELECT @instance_name = CONVERT (sysname, SERVERPROPERTY ('InstanceName'))
  IF (@instance_name IS NULL OR @instance_name = N'MSSQLSERVER')
    SELECT @event_source  = N'MSSQLSERVER'
  ELSE
    SELECT @event_source  = N'MSSQL$' + @instance_name

  SELECT @event_category_id = NULL
  SELECT @event_id = NULL
  SELECT @last_occurrence_date = 0
  SELECT @last_occurrence_time = 0
  SELECT @last_notification_date = 0
  SELECT @last_notification_time = 0
  SELECT @occurrence_count = 0
  SELECT @count_reset_date = 0
  SELECT @count_reset_time = 0
  SELECT @has_notification = 0

  IF (@category_name IS NULL)
  BEGIN
    --Default category_id for alerts
    SELECT @category_id = 98

    SELECT @category_name = name
    FROM msdb.dbo.syscategories
    WHERE (category_id = 98)
  END

  -- Map a job_id of 0 to the real value we use to mean 'no job'
  IF (@job_id = CONVERT(UNIQUEIDENTIFIER, 0x00)) AND (@job_name IS NULL)
    SELECT @job_name = N''

  -- Verify the Alert if @verify_alert <> 0
  IF (@verify_alert <> 0)
  BEGIN
    IF (@job_id = CONVERT(UNIQUEIDENTIFIER, 0x00))
        SELECT @job_id = NULL
    EXECUTE @return_code = sp_verify_alert @name,
                                            @message_id,
                                            @severity,
                                            @enabled,
                                            @delay_between_responses,
                                            @notification_message,
                                            @include_event_description_in,
                                            @database_name,
                                            @event_description_keyword,
                                            @job_id OUTPUT,
                                            @job_name OUTPUT,
                                            @occurrence_count,
                                            @raise_snmp_trap,
                                            @performance_condition,
                                            @category_name,
                                            @category_id OUTPUT,
                                            @count_reset_date,
                                            @count_reset_time,
                                            @wmi_namespace,
                                            @wmi_query,
                                            @event_id OUTPUT
    IF (@return_code <> 0)
    BEGIN
        RETURN(1) -- Failure
    END
  END

  -- For WMI alerts replace
  -- database_name with wmi_namespace and
  -- performance_conditon with wmi_query
  -- so we can store them in those columns in sysalerts table
  IF (@event_id = 8)
  BEGIN
    SELECT @database_name = @wmi_namespace
    SELECT @performance_condition = @wmi_query
  END

  -- Check if this Alert already exists
  SELECT @duplicate_name = FORMATMESSAGE(14205)
  SELECT @duplicate_name = name
  FROM msdb.dbo.sysalerts
  WHERE ((event_id = 8) AND
       (ISNULL(performance_condition, N'') = ISNULL(@performance_condition, N'')) AND
       (ISNULL(database_name, N'') = ISNULL(@database_name, N''))) OR
      ((ISNULL(event_id,1) <> 8) AND
       (ISNULL(performance_condition, N'apples') = ISNULL(@performance_condition, N'oranges'))) OR
      ((performance_condition IS NULL) AND
         (message_id = @message_id) AND
         (severity = @severity) AND
         (ISNULL(database_name, N'') = ISNULL(@database_name, N'')) AND
         (ISNULL(event_description_keyword, N'') = ISNULL(@event_description_keyword, N'')))
  IF (@duplicate_name <> FORMATMESSAGE(14205))
  BEGIN
    RAISERROR(14501, 16, 1, @duplicate_name)
    RETURN(1) -- Failure
  END

  -- Finally, do the actual INSERT
  INSERT INTO msdb.dbo.sysalerts
         (name,
          event_source,
          event_category_id,
          event_id,
          message_id,
          severity,
          enabled,
          delay_between_responses,
          last_occurrence_date,
          last_occurrence_time,
          last_response_date,
          last_response_time,
          notification_message,
          include_event_description,
          database_name,
          event_description_keyword,
          occurrence_count,
          count_reset_date,
          count_reset_time,
          job_id,
          has_notification,
          flags,
          performance_condition,
          category_id)
  VALUES (@name,
          @event_source,
          @event_category_id,
          @event_id,
          @message_id,
          @severity,
          @enabled,
          @delay_between_responses,
          @last_occurrence_date,
          @last_occurrence_time,
          @last_notification_date,
          @last_notification_time,
          @notification_message,
          @include_event_description_in,
          @database_name,
          @event_description_keyword,
          @occurrence_count,
          @count_reset_date,
          @count_reset_time,
          ISNULL(@job_id, CONVERT(UNIQUEIDENTIFIER, 0x00)),
          @has_notification,
          @raise_snmp_trap,
          @performance_condition,
          @category_id)

  -- Notify SQLServerAgent of the change
  SELECT @alert_id = id
  FROM msdb.dbo.sysalerts
  WHERE (name = @name)
  EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'A',
                                      @alert_id    = @alert_id,
                                      @action_type = N'I'
  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_ADD_ALERT                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_alert...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_alert')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_alert
go
CREATE PROCEDURE sp_add_alert
  @name                         sysname,
  @message_id                   INT              = 0,
  @severity                     INT              = 0,
  @enabled                      TINYINT          = 1,
  @delay_between_responses      INT              = 0,
  @notification_message         NVARCHAR(512)    = NULL,
  @include_event_description_in TINYINT          = NULL,    -- 0 = None, 1 = Email, 2 = Pager, 4 = NetSend, 7 = All
  @database_name                sysname          = NULL,
  @event_description_keyword    NVARCHAR(100)    = NULL,
  @job_id                       UNIQUEIDENTIFIER = NULL, -- If provided must NOT also provide job_name
  @job_name                     sysname          = NULL, -- If provided must NOT also provide job_id
  @raise_snmp_trap              TINYINT          = 0,
  @performance_condition        NVARCHAR(512)    = NULL, -- New for 7.0
  @category_name                sysname          = NULL, -- New for 7.0
  @wmi_namespace                sysname             = NULL, -- New for 9.0
  @wmi_query                    NVARCHAR(512)     = NULL  -- New for 9.0
AS
BEGIN
  DECLARE @verify_alert         INT

  --Always verify alerts before adding
  SELECT @verify_alert = 1

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (SERVERPROPERTY('EngineEdition') = 8)
   BEGIN
    RAISERROR(41914, -1, 20, 'alerting');
    RETURN(1) -- Failure
  END


  IF (@include_event_description_in IS NULL)
  BEGIN
  IF (SERVERPROPERTY('EngineEdition') = 8)
    SELECT @include_event_description_in = 1
  ELSE
    SELECT @include_event_description_in = 5
  END

  EXECUTE msdb.dbo.sp_add_alert_internal @name,
                                         @message_id,
                                         @severity,
                                         @enabled,
                                         @delay_between_responses,
                                         @notification_message,
                                         @include_event_description_in,
                                         @database_name,
                                         @event_description_keyword,
                                         @job_id,
                                         @job_name,
                                         @raise_snmp_trap,
                                         @performance_condition,
                                         @category_name,
                                         @wmi_namespace,
                                         @wmi_query,
                                         @verify_alert
END
GO


/**************************************************************/
/* SP_DELETE_ALERT                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_alert...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_alert')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_alert
go
CREATE PROCEDURE sp_delete_alert
  @name sysname
AS
BEGIN
  DECLARE @alert_id    INT
  DECLARE @return_code INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name = LTRIM(RTRIM(@name))

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check if SQLServerAgent is in the process of starting
  EXECUTE @return_code = msdb.dbo.sp_is_sqlagent_starting
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  -- Check if this Alert exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysalerts
                  WHERE (name = @name)))
  BEGIN
    RAISERROR(14262, 16, 1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Convert the Name to it's ID
  SELECT @alert_id = id
  FROM msdb.dbo.sysalerts
  WHERE (name = @name)

  BEGIN TRANSACTION

    -- Delete sysnotifications entries
    DELETE FROM msdb.dbo.sysnotifications
    WHERE (alert_id = @alert_id)

    -- Finally, do the actual DELETE
    DELETE FROM msdb.dbo.sysalerts
    WHERE (id = @alert_id)

  COMMIT TRANSACTION

  -- Notify SQLServerAgent of the change
  EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'A',
                                      @alert_id    = @alert_id,
                                      @action_type = N'D'
  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_HELP_ALERT                                              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_alert...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_alert')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_alert
go
CREATE PROCEDURE sp_help_alert
  @alert_name    sysname = NULL,
  @order_by      sysname = N'name',
  @alert_id      INT     = NULL,
  @category_name sysname = NULL,
  @legacy_format BIT  = 0
AS
BEGIN
  DECLARE @alert_id_as_char NVARCHAR(10)
  DECLARE @escaped_alert_name NVARCHAR(256) -- double sysname
  DECLARE @escaped_category_name NVARCHAR(256) -- double sysname
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @alert_name    = LTRIM(RTRIM(@alert_name))
  SELECT @order_by      = LTRIM(RTRIM(@order_by))
  SELECT @category_name = LTRIM(RTRIM(@category_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@category_name = N'') SELECT @category_name = NULL
  IF (@alert_name = N'')    SELECT @alert_name = NULL

  -- Check alert name
  IF (@alert_name IS NOT NULL)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysalerts
                    WHERE (name = @alert_name)))
    BEGIN
      RAISERROR(14262, -1, -1, '@alert_name', @alert_name)
      RETURN(1) -- Failure
    END
  END

  -- Check alert id
  IF (@alert_id IS NOT NULL)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysalerts
                    WHERE (id = @alert_id)))
    BEGIN
      SELECT @alert_id_as_char = CONVERT(VARCHAR, @alert_id)
      RAISERROR(14262, -1, -1, '@alert_id', @alert_id_as_char)
      RETURN(1) -- Failure
    END
  END

  IF (@alert_id IS NOT NULL)
    SELECT @alert_id_as_char = CONVERT(VARCHAR, @alert_id)
  ELSE
    SELECT @alert_id_as_char = N'NULL'

  -- Double up any single quotes in @alert_name
  IF (@alert_name IS NOT NULL)
    SELECT @escaped_alert_name = REPLACE(@alert_name, N'''', N'''''')

  -- Double up any single quotes in @category_name
  IF (@category_name IS NOT NULL)
    SELECT @escaped_category_name = REPLACE(@category_name, N'''', N'''''')

  IF (@legacy_format <> 0)
  BEGIN

     -- @order_by parameter validation.
     IF  ( (@order_by IS NOT NULL) AND
           (EXISTS(SELECT so.object_id FROM msdb.sys.objects so
                      JOIN msdb.sys.columns sc ON (so.object_id = sc.object_id)
                   WHERE so.type='U' AND so.name='sysalerts'
                                     AND LOWER(sc.name collate SQL_Latin1_General_CP1_CS_AS)=LOWER(@order_by collate SQL_Latin1_General_CP1_CS_AS)
                  )
          ) )
     BEGIN
       SELECT @order_by = N'sa.' + @order_by
     END
     ELSE
     BEGIN
        IF (LOWER(@order_by collate SQL_Latin1_General_CP1_CS_AS) NOT IN ( N'job_name', N'category_name', N'type' ) )
           AND --special "order by" clause used only by sqlagent. if you change it you need to change agent too
           (@order_by <> N'event_id DESC, severity ASC, message_id ASC, database_name DESC')
           AND
           (@order_by <> N'severity ASC, message_id ASC, database_name DESC')
        BEGIN
          RAISERROR(18750, -1, -1, 'sp_help_alert', '@order_by')
          RETURN(1) -- Failure
        END
     END

    -- Old query version (for SQL Server 2000 and older servers)
    -- database_name and performance_conditions are reported
    -- directly from sysalerts columns
    EXECUTE (N'SELECT sa.id,
               sa.name,
                    sa.event_source,
                    sa.event_category_id,
                    sa.event_id,
                    sa.message_id,
                    sa.severity,
                    sa.enabled,
                    sa.delay_between_responses,
                    sa.last_occurrence_date,
                    sa.last_occurrence_time,
                    sa.last_response_date,
                    sa.last_response_time,
                    sa.notification_message,
                    sa.include_event_description,
                    sa.database_name,
                    sa.event_description_keyword,
                    sa.occurrence_count,
                    sa.count_reset_date,
                    sa.count_reset_time,
                    sjv.job_id,
                    job_name = sjv.name,
                    sa.has_notification,
                    sa.flags,
                    sa.performance_condition,
                    category_name = sc.name,
                    type = CASE ISNULL(sa.performance_condition, ''!'')
                  WHEN ''!'' THEN 1            -- SQL Server event alert
                  ELSE CASE sa.event_id
                     WHEN 8 THEN 4          -- WMI event alert
                     ELSE 2                    -- SQL Server performance condition alert
                  END
               END
             FROM msdb.dbo.sysalerts                     sa
                  LEFT OUTER JOIN msdb.dbo.sysjobs_view  sjv ON (sa.job_id = sjv.job_id)
                  LEFT OUTER JOIN msdb.dbo.syscategories sc  ON (sa.category_id = sc.category_id)
             WHERE ((N''' + @escaped_alert_name + N''' = N'''') OR (sa.name = N''' + @escaped_alert_name + N'''))
               AND ((' + @alert_id_as_char + N' IS NULL) OR (sa.id = ' + @alert_id_as_char + N'))
               AND ((N''' + @escaped_category_name + N''' = N'''') OR (sc.name = N''' + @escaped_category_name + N'''))
             ORDER BY ' + @order_by)
  END
  ELSE
  BEGIN

     -- @order_by parameter validation.
     IF  ( (@order_by IS NOT NULL) AND
           (EXISTS(SELECT so.object_id FROM msdb.sys.objects so
                      JOIN msdb.sys.columns sc ON (so.object_id = sc.object_id)
                   WHERE so.type='U' AND so.name='sysalerts'
                                     AND LOWER(sc.name collate SQL_Latin1_General_CP1_CS_AS)=LOWER(@order_by collate SQL_Latin1_General_CP1_CS_AS)
                  )
          ) )
     BEGIN
       SELECT @order_by = N'sa.' + @order_by
     END
     ELSE
     BEGIN
        IF (LOWER(@order_by collate SQL_Latin1_General_CP1_CS_AS) NOT IN (N'database_name', N'job_name', N'performance_condition', N'category_name', N'wmi_namespace', N'wmi_query', N'type' ) )
           AND --special "order by" clause used only by sqlagent. if you change it you need to change agent too
           (@order_by <> N'event_id DESC, severity ASC, message_id ASC, database_name DESC')
           AND
           (@order_by <> N'severity ASC, message_id ASC, database_name DESC')
        BEGIN
           RAISERROR(18750, -1, -1, 'sp_help_alert', '@order_by')
           RETURN(1) -- Failure
        END
     END

    -- New query version. If alert is a WMI alert
    -- then database_name is reported as wmi_namespace and
    -- performance_condition is reported as wmi_query.
    -- For other alerts those two new columns are NULL
    EXECUTE (N'SELECT sa.id,
                    sa.name,
                    sa.event_source,
                    sa.event_category_id,
                    sa.event_id,
                    sa.message_id,
                    sa.severity,
                    sa.enabled,
                    sa.delay_between_responses,
                    sa.last_occurrence_date,
                    sa.last_occurrence_time,
                    sa.last_response_date,
                    sa.last_response_time,
                    sa.notification_message,
                    sa.include_event_description,
               database_name = CASE ISNULL(sa.event_id, 1)
                  WHEN 8 THEN NULL
                  ELSE sa.database_name
               END,
                    sa.event_description_keyword,
                    sa.occurrence_count,
                    sa.count_reset_date,
                    sa.count_reset_time,
                    sjv.job_id,
                    job_name = sjv.name,
                    sa.has_notification,
                    sa.flags,
               performance_condition = CASE ISNULL(sa.event_id, 1)
                  WHEN 8 THEN NULL
                  ELSE sa.performance_condition
               END,
                    category_name = sc.name,
                    wmi_namespace = CASE ISNULL(sa.event_id, 1)
                  WHEN 8 THEN sa.database_name
                  ELSE NULL
               END,
               wmi_query = CASE ISNULL(sa.event_id, 1)
                  WHEN 8 THEN sa.performance_condition
                  ELSE NULL
               END,
                    type = CASE ISNULL(sa.performance_condition, ''!'')
                  WHEN ''!'' THEN 1            -- SQL Server event alert
                  ELSE CASE sa.event_id
                     WHEN 8 THEN 4          -- WMI event alert
                     ELSE 2                    -- SQL Server performance condition alert
                  END
               END
             FROM msdb.dbo.sysalerts                     sa
                  LEFT OUTER JOIN msdb.dbo.sysjobs_view  sjv ON (sa.job_id = sjv.job_id)
                  LEFT OUTER JOIN msdb.dbo.syscategories sc  ON (sa.category_id = sc.category_id)
             WHERE ((N''' + @escaped_alert_name + N''' = N'''') OR (sa.name = N''' + @escaped_alert_name + N'''))
               AND ((' + @alert_id_as_char + N' IS NULL) OR (sa.id = ' + @alert_id_as_char + N'))
               AND ((N''' + @escaped_category_name + N''' = N'''') OR (sc.name = N''' + @escaped_category_name + N'''))
             ORDER BY ' + @order_by)
  END

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_VERIFY_OPERATOR                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_operator...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_operator')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_operator
go
CREATE PROCEDURE sp_verify_operator
  @name                      sysname,
  @enabled                   TINYINT,
  @pager_days                TINYINT,
  @weekday_pager_start_time  INT,
  @weekday_pager_end_time    INT,
  @saturday_pager_start_time INT,
  @saturday_pager_end_time   INT,
  @sunday_pager_start_time   INT,
  @sunday_pager_end_time     INT,
  @category_name             sysname,
  @category_id               INT OUTPUT
AS
BEGIN
  DECLARE @return_code     TINYINT
  DECLARE @res_valid_range NVARCHAR(100)

  SET NOCOUNT ON

  SELECT @res_valid_range = FORMATMESSAGE(14209)

  -- Remove any leading/trailing spaces from parameters
  SELECT @name          = LTRIM(RTRIM(@name))
  SELECT @category_name = LTRIM(RTRIM(@category_name))

  -- The name must be unique
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysoperators
              WHERE (name = @name)))
  BEGIN
    RAISERROR(14261, 16, 1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Enabled must be 0 or 1
  IF (@enabled NOT IN (0, 1))
  BEGIN
    RAISERROR(14266, 16, 1, '@enabled', '0, 1')
    RETURN(1) -- Failure
  END

  -- Check PagerDays
  IF (@pager_days < 0) OR (@pager_days > 127)
  BEGIN
    RAISERROR(14266, 16, 1, '@pager_days', @res_valid_range)
    RETURN(1) -- Failure
  END

  -- Check Start/End Times
  EXECUTE @return_code = sp_verify_job_time @weekday_pager_start_time, '@weekday_pager_start_time'
  IF (@return_code <> 0)
    RETURN(1)

  EXECUTE @return_code = sp_verify_job_time @weekday_pager_end_time, '@weekday_pager_end_time'
  IF (@return_code <> 0)
    RETURN(1)

  EXECUTE @return_code = sp_verify_job_time @saturday_pager_start_time, '@saturday_pager_start_time'
  IF (@return_code <> 0)
    RETURN(1)

  EXECUTE @return_code = sp_verify_job_time @saturday_pager_end_time, '@saturday_pager_end_time'
  IF (@return_code <> 0)
    RETURN(1)

  EXECUTE @return_code = sp_verify_job_time @sunday_pager_start_time, '@sunday_pager_start_time'
  IF (@return_code <> 0)
    RETURN(1)

  EXECUTE @return_code = sp_verify_job_time @sunday_pager_end_time, '@sunday_pager_end_time'
  IF (@return_code <> 0)
    RETURN(1)

  -- Check category name
  IF (@category_name = N'[DEFAULT]')
    SELECT @category_id = 99
  ELSE
  BEGIN
    SELECT @category_id = category_id
    FROM msdb.dbo.syscategories
    WHERE (category_class = 3) -- Operators
      AND (category_type = 3) -- None
      AND (name = @category_name)
  END
  IF (@category_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@category_name', @category_name)
    RETURN(1) -- Failure
  END

  RETURN(0)
END
go

/**************************************************************/
/* SP_ADD_OPERATOR                                            */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_operator...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_operator')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_operator
go
CREATE PROCEDURE sp_add_operator
  @name                      sysname,
  @enabled                   TINYINT       = 1,
  @email_address             NVARCHAR(100) = NULL,
  @pager_address             NVARCHAR(100) = NULL,
  @weekday_pager_start_time  INT           = 090000, -- HHMMSS using 24 hour clock
  @weekday_pager_end_time    INT           = 180000, -- As above
  @saturday_pager_start_time INT           = 090000, -- As above
  @saturday_pager_end_time   INT           = 180000, -- As above
  @sunday_pager_start_time   INT           = 090000, -- As above
  @sunday_pager_end_time     INT           = 180000, -- As above
  @pager_days                TINYINT       = 0,      -- 1 = Sunday .. 64 = Saturday
  @netsend_address           NVARCHAR(100) = NULL,   -- New for 7.0
  @category_name             sysname       = NULL    -- New for 7.0
AS
BEGIN
  DECLARE @return_code TINYINT
  DECLARE @category_id INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name            = LTRIM(RTRIM(@name))
  SELECT @email_address   = LTRIM(RTRIM(@email_address))
  SELECT @pager_address   = LTRIM(RTRIM(@pager_address))
  SELECT @netsend_address = LTRIM(RTRIM(@netsend_address))
  SELECT @category_name   = LTRIM(RTRIM(@category_name))

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (@netsend_address <> N'' AND SERVERPROPERTY('EngineEdition') = 8)
  BEGIN
    RAISERROR(41914, -1, 12, 'NetSend')
    RETURN(1) -- Failure
  END

  -- Turn [nullable] empty string parameters into NULLs
  IF (@email_address   = N'') SELECT @email_address   = NULL
  IF (@pager_address   = N'') SELECT @pager_address   = NULL
  IF (@netsend_address = N'') SELECT @netsend_address = NULL
  IF (@category_name   = N'') SELECT @category_name   = NULL

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  IF (@category_name IS NULL)
  BEGIN
    SELECT @category_name = name
    FROM msdb.dbo.syscategories
    WHERE (category_id = 99)
  END

  -- Verify the operator
  EXECUTE @return_code = sp_verify_operator @name,
                                            @enabled,
                                            @pager_days,
                                            @weekday_pager_start_time,
                                            @weekday_pager_end_time,
                                            @saturday_pager_start_time,
                                            @saturday_pager_end_time,
                                            @sunday_pager_start_time,
                                            @sunday_pager_end_time,
                                            @category_name,
                                            @category_id OUTPUT
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  -- Finally, do the INSERT
  INSERT INTO msdb.dbo.sysoperators
         (name,
          enabled,
          email_address,
          last_email_date,
          last_email_time,
          pager_address,
          last_pager_date,
          last_pager_time,
          weekday_pager_start_time,
          weekday_pager_end_time,
          saturday_pager_start_time,
          saturday_pager_end_time,
          sunday_pager_start_time,
          sunday_pager_end_time,
          pager_days,
          netsend_address,
          last_netsend_date,
          last_netsend_time,
          category_id)
  VALUES (@name,
          @enabled,
          @email_address,
          0,
          0,
          @pager_address,
          0,
          0,
          @weekday_pager_start_time,
          @weekday_pager_end_time,
          @saturday_pager_start_time,
          @saturday_pager_end_time,
          @sunday_pager_start_time,
          @sunday_pager_end_time,
          @pager_days,
          @netsend_address,
          0,
          0,
          @category_id)

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_UPDATE_OPERATOR                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_operator...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_operator')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_operator
go
CREATE PROCEDURE sp_update_operator
  @name                      sysname,
  @new_name                  sysname       = NULL,
  @enabled                   TINYINT       = NULL,
  @email_address             NVARCHAR(100) = NULL,
  @pager_address             NVARCHAR(100) = NULL,
  @weekday_pager_start_time  INT           = NULL, -- HHMMSS using 24 hour clock
  @weekday_pager_end_time    INT           = NULL, -- As above
  @saturday_pager_start_time INT           = NULL, -- As above
  @saturday_pager_end_time   INT           = NULL, -- As above
  @sunday_pager_start_time   INT           = NULL, -- As above
  @sunday_pager_end_time     INT           = NULL, -- As above
  @pager_days                TINYINT       = NULL,
  @netsend_address           NVARCHAR(100) = NULL, -- New for 7.0
  @category_name             sysname       = NULL  -- New for 7.0
AS
BEGIN
  DECLARE @x_enabled                   TINYINT
  DECLARE @x_email_address             NVARCHAR(100)
  DECLARE @x_pager_address             NVARCHAR(100)
  DECLARE @x_weekday_pager_start_time  INT
  DECLARE @x_weekday_pager_end_time    INT
  DECLARE @x_saturday_pager_start_time INT
  DECLARE @x_saturday_pager_end_time   INT
  DECLARE @x_sunday_pager_start_time   INT
  DECLARE @x_sunday_pager_end_time     INT
  DECLARE @x_pager_days                TINYINT
  DECLARE @x_netsend_address           NVARCHAR(100)
  DECLARE @x_category_id               INT

  DECLARE @return_code                 INT
  DECLARE @notification_method         INT
  DECLARE @alert_fail_safe_operator    sysname
  DECLARE @current_msx_server          sysname
  DECLARE @category_id                 INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name            = LTRIM(RTRIM(@name))
  SELECT @new_name        = LTRIM(RTRIM(@new_name))
  SELECT @email_address   = LTRIM(RTRIM(@email_address))
  SELECT @pager_address   = LTRIM(RTRIM(@pager_address))
  SELECT @netsend_address = LTRIM(RTRIM(@netsend_address))
  SELECT @category_name   = LTRIM(RTRIM(@category_name))

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF (@netsend_address <> N'' AND SERVERPROPERTY('EngineEdition') = 8)
  BEGIN
    RAISERROR(41914, -1, 13, 'NetSend')
    RETURN(1) -- Failure
  END

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check if this Operator exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysoperators
                  WHERE (name = @name)))
  BEGIN
    RAISERROR(14262, 16, 1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Check if this operator is 'MSXOperator'
  IF (@name = N'MSXOperator')
  BEGIN
    -- Disallow the update operation if we're at a TSX for all callers other than xp_msx_enlist
    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'MSXServerName',
                                           @current_msx_server OUTPUT,
                                           N'no_output'
    IF ((@current_msx_server IS NOT NULL) AND (PROGRAM_NAME() <> N'xp_msx_enlist'))
    BEGIN
      RAISERROR(14223, 16, 1, 'MSXOperator', 'TSX')
      RETURN(1) -- Failure
    END
  END

  -- Get existing (@x_) operator property values
  SELECT @x_enabled                   = enabled,
         @x_email_address             = email_address,
         @x_pager_address             = pager_address,
         @x_weekday_pager_start_time  = weekday_pager_start_time,
         @x_weekday_pager_end_time    = weekday_pager_end_time,
         @x_saturday_pager_start_time = saturday_pager_start_time,
         @x_saturday_pager_end_time   = saturday_pager_end_time,
         @x_sunday_pager_start_time   = sunday_pager_start_time,
         @x_sunday_pager_end_time     = sunday_pager_end_time,
         @x_pager_days                = pager_days,
         @x_netsend_address           = netsend_address,
         @x_category_id               = category_id
  FROM msdb.dbo.sysoperators
  WHERE (name = @name)

  -- Fill out the values for all non-supplied parameters from the existsing values
  IF (@enabled                   IS NULL) SELECT @enabled                   = @x_enabled
  IF (@email_address             IS NULL) SELECT @email_address             = @x_email_address
  IF (@pager_address             IS NULL) SELECT @pager_address             = @x_pager_address
  IF (@weekday_pager_start_time  IS NULL) SELECT @weekday_pager_start_time  = @x_weekday_pager_start_time
  IF (@weekday_pager_end_time    IS NULL) SELECT @weekday_pager_end_time    = @x_weekday_pager_end_time
  IF (@saturday_pager_start_time IS NULL) SELECT @saturday_pager_start_time = @x_saturday_pager_start_time
  IF (@saturday_pager_end_time   IS NULL) SELECT @saturday_pager_end_time   = @x_saturday_pager_end_time
  IF (@sunday_pager_start_time   IS NULL) SELECT @sunday_pager_start_time   = @x_sunday_pager_start_time
  IF (@sunday_pager_end_time     IS NULL) SELECT @sunday_pager_end_time     = @x_sunday_pager_end_time
  IF (@pager_days                IS NULL) SELECT @pager_days                = @x_pager_days
  IF (@netsend_address           IS NULL) SELECT @netsend_address           = @x_netsend_address
  IF (@category_name             IS NULL) SELECT @category_name = name FROM msdb.dbo.syscategories WHERE (category_id = @x_category_id)

  IF (@category_name IS NULL)
  BEGIN
    SELECT @category_name = name
    FROM msdb.dbo.syscategories
    WHERE (category_id = 99)
  END

  -- Turn [nullable] empty string parameters into NULLs
  IF (@email_address   = N'') SELECT @email_address   = NULL
  IF (@pager_address   = N'') SELECT @pager_address   = NULL
  IF (@netsend_address = N'') SELECT @netsend_address = NULL
  IF (@category_name   = N'') SELECT @category_name   = NULL

  -- Verify the operator
  EXECUTE @return_code = sp_verify_operator @new_name,
                                            @enabled,
                                            @pager_days,
                                            @weekday_pager_start_time,
                                            @weekday_pager_end_time,
                                            @saturday_pager_start_time,
                                            @saturday_pager_end_time,
                                            @sunday_pager_start_time,
                                            @sunday_pager_end_time,
                                            @category_name,
                                            @category_id OUTPUT
  IF (@return_code <> 0)
    RETURN(1) -- Failure

  -- If no new name is supplied, use the old one
  -- NOTE: We must do this AFTER calling sp_verify_operator.
  IF (@new_name IS NULL)
    SELECT @new_name = @name
  ELSE
  BEGIN
    -- You can't rename the MSXOperator
    IF (@name = N'MSXOperator')
    BEGIN
      RAISERROR(14222, 16, 1, 'MSXOperator')
      RETURN(1) -- Failure
    END
  END

  -- Do the UPDATE
  UPDATE msdb.dbo.sysoperators
  SET name                      = @new_name,
      enabled                   = @enabled,
      email_address             = @email_address,
      pager_address             = @pager_address,
      weekday_pager_start_time  = @weekday_pager_start_time,
      weekday_pager_end_time    = @weekday_pager_end_time,
      saturday_pager_start_time = @saturday_pager_start_time,
      saturday_pager_end_time   = @saturday_pager_end_time,
      sunday_pager_start_time   = @sunday_pager_start_time,
      sunday_pager_end_time     = @sunday_pager_end_time,
      pager_days                = @pager_days,
      netsend_address           = @netsend_address,
      category_id               = @category_id
  WHERE (name = @name)

  -- Check if the operator is 'MSXOperator', in which case we need to re-enlist all the targets
  -- so that they will download the new MSXOperator details
  IF ((@name = N'MSXOperator') AND ((SELECT COUNT(*) FROM msdb.dbo.systargetservers) > 0))
    EXECUTE msdb.dbo.sp_post_msx_operation 'RE-ENLIST', 'SERVER', 0x00

  -- Check if this operator is the FailSafe Operator
  EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                         N'AlertFailSafeOperator',
                                         @alert_fail_safe_operator OUTPUT,
                                         N'no_output'

  -- If it is, we update the 4 'AlertFailSafe...' registry entries and AlertNotificationMethod
  IF (LTRIM(RTRIM(@alert_fail_safe_operator)) = @name)
  BEGIN
    -- Update AlertFailSafeX values
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'AlertFailSafeOperator',
                                            N'REG_SZ',
                                            @new_name
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'AlertFailSafeEmailAddress',
                                            N'REG_SZ',
                                            @email_address
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'AlertFailSafePagerAddress',
                                            N'REG_SZ',
                                            @pager_address
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'AlertFailSafeNetSendAddress',
                                            N'REG_SZ',
                                            @netsend_address

    -- Update AlertNotificationMethod values
    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                           N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                           N'AlertNotificationMethod',
                                           @notification_method OUTPUT,
                                           N'no_output'
    IF (LTRIM(RTRIM(@email_address)) IS NULL)
      SELECT @notification_method = @notification_method & ~1
    IF (LTRIM(RTRIM(@pager_address)) IS NULL)
      SELECT @notification_method = @notification_method & ~2
    IF (LTRIM(RTRIM(@netsend_address)) IS NULL)
      SELECT @notification_method = @notification_method & ~4
    EXECUTE master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'AlertNotificationMethod',
                                            N'REG_DWORD',
                                            @notification_method

    -- And finally, let SQLServerAgent know of the changes
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type = N'G'
  END

  RETURN(0) -- Success
END
go


/**************************************************************/
/* SP_HELP_OPERATOR                                           */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_operator...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_operator')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_operator
go
CREATE PROCEDURE sp_help_operator
  @operator_name sysname = NULL,
  @operator_id   INT     = NULL
AS
BEGIN
  DECLARE @operator_id_as_char VARCHAR(10)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @operator_name = LTRIM(RTRIM(@operator_name))
  IF (@operator_name = '') SELECT @operator_name = NULL

  -- Check operator name
  IF (@operator_name IS NOT NULL)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysoperators
                    WHERE (name = @operator_name)))
    BEGIN
      RAISERROR(14262, -1, -1, '@operator_name', @operator_name)
      RETURN(1) -- Failure
    END
  END

  -- Check operator id
  IF (@operator_id IS NOT NULL)
  BEGIN
    IF (NOT EXISTS (SELECT *
                    FROM msdb.dbo.sysoperators
                    WHERE (id = @operator_id)))
    BEGIN
      SELECT @operator_id_as_char = CONVERT(VARCHAR, @operator_id)
      RAISERROR(14262, -1, -1, '@operator_id', @operator_id_as_char)
      RETURN(1) -- Failure
    END
  END

  SELECT so.id,
         so.name,
         so.enabled,
         so.email_address,
         so.last_email_date,
         so.last_email_time,
         so.pager_address,
         so.last_pager_date,
         so.last_pager_time,
         so.weekday_pager_start_time,
         so.weekday_pager_end_time,
         so.saturday_pager_start_time,
         so.saturday_pager_end_time,
         so.sunday_pager_start_time,
         so.sunday_pager_end_time,
         so.pager_days,
         so.netsend_address,
         so.last_netsend_date,
         so.last_netsend_time,
         category_name = sc.name
  FROM msdb.dbo.sysoperators                  so
       LEFT OUTER JOIN msdb.dbo.syscategories sc ON (so.category_id = sc.category_id)
  WHERE ((@operator_name IS NULL) OR (so.name = @operator_name))
    AND ((@operator_id IS NULL) OR (so.id = @operator_id))
  ORDER BY so.name

  RETURN(@@error) -- 0 means success
END
go

/**************************************************************/
/* SP_HELP_OPERATOR_JOBS                                      */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_operator_jobs...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_help_operator_jobs')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_operator_jobs
go
CREATE PROCEDURE sp_help_operator_jobs
  @operator_name sysname = NULL
AS
BEGIN
  DECLARE @operator_id INT

  SET NOCOUNT ON

  -- Check operator name
  SELECT @operator_id = id
  FROM msdb.dbo.sysoperators
  WHERE (name = @operator_name)
  IF (@operator_id IS NULL)
  BEGIN
    RAISERROR(14262, -1, -1, '@operator_name', @operator_name)
    RETURN(1) -- Failure
  END

  -- Get the job info
  SELECT job_id, name, notify_level_email, notify_level_netsend, notify_level_page
  FROM msdb.dbo.sysjobs_view
  WHERE ((notify_email_operator_id = @operator_id)   AND (notify_level_email <> 0))
     OR ((notify_netsend_operator_id = @operator_id) AND (notify_level_netsend <> 0))
     OR ((notify_page_operator_id = @operator_id)    AND (notify_level_page <> 0))

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_VERIFY_OPERATOR_IDENTIFIERS                             */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_operator_identifiers...'
IF (NOT OBJECT_ID(N'dbo.sp_verify_operator_identifiers', 'P') IS NULL)
  DROP PROCEDURE dbo.sp_verify_operator_identifiers
go

CREATE PROCEDURE sp_verify_operator_identifiers
   @name_of_name_parameter [varchar](60),
   @name_of_id_parameter [varchar](60),
   @operator_name [sysname] OUTPUT,
   @operator_id [INT] OUTPUT
AS
BEGIN
  DECLARE @retval              INT
  DECLARE @operator_id_as_char NVARCHAR(36)

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @name_of_name_parameter = LTRIM(RTRIM(@name_of_name_parameter))
  SELECT @name_of_id_parameter   = LTRIM(RTRIM(@name_of_id_parameter))
  SELECT @operator_name             = LTRIM(RTRIM(@operator_name))

  IF (@operator_name = N'') SELECT @operator_name = NULL

  IF ((@operator_name IS NULL)     AND (@operator_id IS NULL)) OR
     ((@operator_name IS NOT NULL) AND (@operator_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, @name_of_id_parameter, @name_of_name_parameter)
    RETURN(1) -- Failure
  END

  -- Check job id
  IF (@operator_id IS NOT NULL)
  BEGIN
    SELECT @operator_name = name
    FROM msdb.dbo.sysoperators
    WHERE (id = @operator_id)
    IF (@operator_name IS NULL)
    BEGIN
     SELECT @operator_id_as_char = CONVERT(nvarchar(36), @operator_id)
      RAISERROR(14262, -1, -1, '@operator_id', @operator_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check proxy name
  IF (@operator_name IS NOT NULL)
  BEGIN
    -- The name is not ambiguous, so get the corresponding operator_id (if the job exists)
    SELECT @operator_id = id
    FROM msdb.dbo.sysoperators
    WHERE (name = @operator_name)
    IF (@operator_id IS NULL)
    BEGIN
      RAISERROR(14262, -1, -1, '@operator_name', @operator_name)
      RETURN(1) -- Failure
    END
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_NOTIFY_OPERATOR                                         */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_notify_operator...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_notify_operator')
              AND (type = 'P')))
  DROP PROCEDURE sp_notify_operator
go
CREATE PROCEDURE sp_notify_operator
  @profile_name           sysname       = NULL,
  --name of Database Mail profile to be used for sending email, cannot be null
  @id                     INT            = NULL,
  @name                   sysname        = NULL,
  --mutual exclusive, one and only one should be non null. Specify the operator whom mail adress will be used to send this email
  @subject                NVARCHAR(256)  = NULL,
  @body                   NVARCHAR(MAX)  = NULL,
  -- This is the body of the email message
  @file_attachments       NVARCHAR(512)  = NULL,
  @mail_database          sysname       = N'msdb'
  -- Have infrastructure in place to support this but disabled by default
  -- For first implementation we will have this parameters but using it will generate an error - not implemented yet.
AS
BEGIN
  DECLARE @retval INT
  DECLARE @email_address NVARCHAR(100)
  DECLARE @enabled TINYINT
  DECLARE @qualified_sp_sendmail sysname
  DECLARE @db_id INT

  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters
  SELECT @profile_name              = LTRIM(RTRIM(@profile_name))
  SELECT @name                      = LTRIM(RTRIM(@name))
  SELECT @file_attachments          = LTRIM(RTRIM(@file_attachments))
  SELECT @mail_database          = LTRIM(RTRIM(@mail_database))


  IF @profile_name       = ''    SELECT @profile_name      = NULL
  IF @name               = ''    SELECT @name              = NULL
  IF @file_attachments   = ''    SELECT @file_attachments  = NULL
  IF @mail_database      = ''    SELECT @mail_database      = NULL

  EXECUTE @retval = sp_verify_operator_identifiers '@name',
                                                   '@id',
                                                   @name OUTPUT,
                                                   @id   OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  --is operator enabled?
  SELECT @enabled = enabled, @email_address = email_address FROM sysoperators WHERE id = @id
  IF @enabled = 0
  BEGIN
    RAISERROR(14601, 16, 1, @name)
    RETURN 1
  END

  IF @email_address IS NULL
  BEGIN
    RAISERROR(14602, 16, 1, @name)
    RETURN 1
  END

  SELECT @qualified_sp_sendmail = @mail_database + '.dbo.sp_send_dbmail'

  EXEC   @retval = @qualified_sp_sendmail @profile_name = @profile_name,
                               @recipients       = @email_address,
                               @subject          = @subject,
                               @body              = @body,
                               @file_attachments = @file_attachments
  RETURN @retval
END
go

/**************************************************************/
/* SP_VERIFY_NOTIFICATION                                     */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_verify_notification...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_verify_notification')
              AND (type = 'P')))
  DROP PROCEDURE sp_verify_notification
go
CREATE PROCEDURE sp_verify_notification
  @alert_name          sysname,
  @operator_name       sysname,
  @notification_method TINYINT,
  @alert_id            INT OUTPUT,
  @operator_id         INT OUTPUT
AS
BEGIN
  DECLARE @res_valid_range NVARCHAR(100)

  SET NOCOUNT ON

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF ((@notification_method & 4 = 4) AND SERVERPROPERTY('EngineEdition') = 8)
  BEGIN
    RAISERROR(41914, -1, 14, 'NetSend')
    RETURN(1) -- Failure
  END

  SELECT @res_valid_range = FORMATMESSAGE(14208)

  -- Remove any leading/trailing spaces from parameters
  SELECT @alert_name    = LTRIM(RTRIM(@alert_name))
  SELECT @operator_name = LTRIM(RTRIM(@operator_name))

  -- Check if the AlertName is valid
  SELECT @alert_id = id
  FROM msdb.dbo.sysalerts
  WHERE (name = @alert_name)

  IF (@alert_id IS NULL)
  BEGIN
    RAISERROR(14262, 16, 1, '@alert_name', @alert_name)
    RETURN(1) -- Failure
  END

  -- Check if the OperatorName is valid
  SELECT @operator_id = id
  FROM msdb.dbo.sysoperators
  WHERE (name = @operator_name)

  IF (@operator_id IS NULL)
  BEGIN
    RAISERROR(14262, 16, 1, '@operator_name', @operator_name)
    RETURN(1) -- Failure
  END

  -- If we're at a TSX, we disallow using operator 'MSXOperator'
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.systargetservers)) AND
     (@operator_name = N'MSXOperator')
  BEGIN
    RAISERROR(14251, -1, -1, @operator_name)
    RETURN(1) -- Failure
  END

  -- Check if the NotificationMethod is valid
  IF ((@notification_method < 1) OR (@notification_method > 7))
  BEGIN
    RAISERROR(14266, 16, 1, '@notification_method', @res_valid_range)
    RETURN(1) -- Failure
  END

  RETURN(0) -- Success
END
go

/**************************************************************/
/* SP_ADD_NOTIFICATION                                        */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_notification...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_notification')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_notification
go
CREATE PROCEDURE sp_add_notification
  @alert_name          sysname,
  @operator_name       sysname,
  @notification_method TINYINT -- 1 = Email, 2 = Pager, 4 = NetSend, 7 = All
AS
BEGIN
  DECLARE @alert_id             INT
  DECLARE @operator_id          INT
  DECLARE @notification         NVARCHAR(512)
  DECLARE @retval               INT
  DECLARE @old_has_notification INT
  DECLARE @new_has_notification INT
  DECLARE @res_notification     NVARCHAR(100)

  SET NOCOUNT ON

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF ((@notification_method & 4 = 4) AND SERVERPROPERTY('EngineEdition') = 8)
  BEGIN
    RAISERROR(41914, -1, 15, 'NetSend')
    RETURN(1) -- Failure
  END

  SELECT @res_notification = FORMATMESSAGE(14210)

  -- Remove any leading/trailing spaces from parameters
  SELECT @alert_name    = LTRIM(RTRIM(@alert_name))
  SELECT @operator_name = LTRIM(RTRIM(@operator_name))

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check if the Notification is valid
  EXECUTE @retval = msdb.dbo.sp_verify_notification @alert_name,
                                                    @operator_name,
                                                    @notification_method,
                                                    @alert_id     OUTPUT,
                                                    @operator_id  OUTPUT
  IF (@retval <> 0)
    RETURN(1) -- Failure

  -- Check if this notification already exists
  -- NOTE: The unique index would catch this, but testing for the problem here lets us
  --       control the message.
  IF (EXISTS (SELECT *
              FROM msdb.dbo.sysnotifications
              WHERE (alert_id = @alert_id)
                AND (operator_id = @operator_id)))
  BEGIN
    SELECT @notification = @alert_name + N' / ' + @operator_name
    RAISERROR(14261, 16, 1, @res_notification, @notification)
    RETURN(1) -- Failure
  END

  SELECT @old_has_notification = has_notification
  FROM msdb.dbo.sysalerts
  WHERE (id = @alert_id)

  -- Do the INSERT
  INSERT INTO msdb.dbo.sysnotifications
         (alert_id,
          operator_id,
          notification_method)
  VALUES (@alert_id,
          @operator_id,
          @notification_method)

  SELECT @retval = @@error

  SELECT @new_has_notification = has_notification
  FROM msdb.dbo.sysalerts
  WHERE (id = @alert_id)

  -- Notify SQLServerAgent of the change - if any - to has_notifications
  IF (@old_has_notification <> @new_has_notification)
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type     = N'A',
                                        @alert_id    = @alert_id,
                                        @action_type = N'U'

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_UPDATE_NOTIFICATION                                     */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_update_notification...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_update_notification')
              AND (type = 'P')))
  DROP PROCEDURE sp_update_notification
go
CREATE PROCEDURE sp_update_notification
  @alert_name          sysname,
  @operator_name       sysname,
  @notification_method TINYINT -- 1 = Email, 2 = Pager, 4 = NetSend, 7 = All
AS
BEGIN
  DECLARE @alert_id             INT
  DECLARE @operator_id          INT
  DECLARE @notification         NVARCHAR(512)
  DECLARE @retval               INT
  DECLARE @old_has_notification INT
  DECLARE @new_has_notification INT
  DECLARE @res_notification     NVARCHAR(100)

  SET NOCOUNT ON

  -- If this is Azure SQL Database - Managed Instance throw an exception for unsupported option
  IF ((@notification_method & 4 = 4) AND SERVERPROPERTY('EngineEdition') = 8)
  BEGIN
    RAISERROR(41914, -1, 16, 'NetSend')
    RETURN(1) -- Failure
  END

  SELECT @res_notification = FORMATMESSAGE(14210)

  -- Remove any leading/trailing spaces from parameters
  SELECT @alert_name    = LTRIM(RTRIM(@alert_name))
  SELECT @operator_name = LTRIM(RTRIM(@operator_name))

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Check if the Notification is valid
  EXECUTE sp_verify_notification @alert_name,
                                 @operator_name,
                                 @notification_method,
                                 @alert_id     OUTPUT,
                                 @operator_id  OUTPUT

  -- Check if this notification exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysnotifications
                  WHERE (alert_id = @alert_id)
                    AND (operator_id = @operator_id)))
  BEGIN
    SELECT @notification = @alert_name + N' / ' + @operator_name
    RAISERROR(14262, 16, 1, @res_notification, @notification)
    RETURN(1) -- Failure
  END

  SELECT @old_has_notification = has_notification
  FROM msdb.dbo.sysalerts
  WHERE (id = @alert_id)

  -- Do the UPDATE
  UPDATE msdb.dbo.sysnotifications
  SET notification_method = @notification_method
  WHERE (alert_id = @alert_id)
    AND (operator_id = @operator_id)

  SELECT @retval = @@error

  SELECT @new_has_notification = has_notification
  FROM msdb.dbo.sysalerts
  WHERE (id = @alert_id)

  -- Notify SQLServerAgent of the change - if any - to has_notifications
  IF (@old_has_notification <> @new_has_notification)
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type       = N'A',
                                          @alert_id    = @alert_id,
                                          @action_type = N'U'

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_DELETE_NOTIFICATION                                     */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_notification...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_notification')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_notification
go
CREATE PROCEDURE sp_delete_notification
  @alert_name    sysname,
  @operator_name sysname
AS
BEGIN
  DECLARE @alert_id             INT
  DECLARE @operator_id          INT
  DECLARE @ignored              TINYINT
  DECLARE @notification         NVARCHAR(512)
  DECLARE @retval               INT
  DECLARE @old_has_notification INT
  DECLARE @new_has_notification INT
  DECLARE @res_notification     NVARCHAR(100)

  SET NOCOUNT ON

  SELECT @res_notification = FORMATMESSAGE(14210)

  -- Remove any leading/trailing spaces from parameters
  SELECT @alert_name    = LTRIM(RTRIM(@alert_name))
  SELECT @operator_name = LTRIM(RTRIM(@operator_name))

  -- Only a sysadmin can do this
  IF ((ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) <> 1))
  BEGIN
    RAISERROR(15003, 16, 1, N'sysadmin')
    RETURN(1) -- Failure
  END

  -- Get the alert and operator ID's
  EXECUTE sp_verify_notification @alert_name,
                                 @operator_name,
                                 7,           -- A dummy (but valid) value
                                 @alert_id    OUTPUT,
                                 @operator_id OUTPUT

  -- Check if this notification exists
  IF (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysnotifications
                  WHERE (alert_id = @alert_id)
                    AND (operator_id = @operator_id)))
  BEGIN
    SELECT @notification = @alert_name + N' / ' + @operator_name
    RAISERROR(14262, 16, 1, @res_notification, @notification)
    RETURN(1) -- Failure
  END

  SELECT @old_has_notification = has_notification
  FROM msdb.dbo.sysalerts
  WHERE (id = @alert_id)

  -- Do the Delete
  DELETE FROM msdb.dbo.sysnotifications
  WHERE (alert_id = @alert_id)
    AND (operator_id = @operator_id)

  SELECT @retval = @@error

  SELECT @new_has_notification = has_notification
  FROM msdb.dbo.sysalerts
  WHERE (id = @alert_id)

  -- Notify SQLServerAgent of the change - if any - to has_notifications
  IF (@old_has_notification <> @new_has_notification)
    EXECUTE msdb.dbo.sp_sqlagent_notify @op_type       = N'A',
                                          @alert_id    = @alert_id,
                                          @action_type = N'U'

  RETURN(@retval) -- 0 means success
END
go

/**************************************************************/
/* SP_HELP_NOTIFICATION                                       */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_help_notification...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_notification')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_notification
go
CREATE PROCEDURE sp_help_notification
  @object_type          CHAR(9),   -- Either 'ALERTS'    (enumerates Alerts for given Operator)
                                   --     or 'OPERATORS' (enumerates Operators for given Alert)
  @name                 sysname,   -- Either an Operator Name (if @object_type is 'ALERTS')
                                   --     or an Alert Name    (if @object_type is 'OPERATORS')
  @enum_type            CHAR(10),  -- Either 'ALL'    (enumerate all objects [eg. all alerts irrespective of whether 'Fred' receives a notification for them])
                                   --     or 'ACTUAL' (enumerate only the associated objects [eg. only the alerts which 'Fred' receives a notification for])
                                   --     or 'TARGET' (enumerate only the objects matching @target_name [eg. a single row showing how 'Fred' is notfied for alert 'Test'])
  @notification_method  TINYINT,   -- Either 1 (Email)   - Modifies the result set to only show use_email column
                                   --     or 2 (Pager)   - Modifies the result set to only show use_pager column
                                   --     or 4 (NetSend) - Modifies the result set to only show use_netsend column
                                   --     or 7 (All)     - Modifies the result set to show all the use_xxx columns
  @target_name   sysname = NULL    -- Either an Alert Name    (if @object_type is 'ALERTS')
                                   --     or an Operator Name (if @object_type is 'OPERATORS')
                                   -- NOTE: This parameter is only required if @enum_type is 'TARGET')
AS
BEGIN
  DECLARE @id              INT    -- We use this to store the decode of @name
  DECLARE @target_id       INT    -- We use this to store the decode of @target_name
  DECLARE @select_clause   NVARCHAR(1024)
  DECLARE @from_clause     NVARCHAR(512)
  DECLARE @where_clause    NVARCHAR(512)
  DECLARE @res_valid_range NVARCHAR(100)

  SET NOCOUNT ON

  SELECT @res_valid_range = FORMATMESSAGE(14208)

  -- Remove any leading/trailing spaces from parameters
  SELECT @object_type = UPPER(LTRIM(RTRIM(@object_type)) collate SQL_Latin1_General_CP1_CS_AS)
  SELECT @name        = LTRIM(RTRIM(@name))
  SELECT @enum_type   = UPPER(LTRIM(RTRIM(@enum_type)) collate SQL_Latin1_General_CP1_CS_AS)
  SELECT @target_name = LTRIM(RTRIM(@target_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@target_name = N'') SELECT @target_name = NULL

  -- Check ObjectType
  IF (@object_type NOT IN ('ALERTS', 'OPERATORS'))
  BEGIN
    RAISERROR(14266, 16, 1, '@object_type', 'ALERTS, OPERATORS')
    RETURN(1) -- Failure
  END

  -- Check AlertName
  IF (@object_type = 'OPERATORS') AND
     (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysalerts
                  WHERE (name = @name)))
  BEGIN
    RAISERROR(14262, 16, 1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Check OperatorName
  IF (@object_type = 'ALERTS') AND
     (NOT EXISTS (SELECT *
                  FROM msdb.dbo.sysoperators
                  WHERE (name = @name)))
  BEGIN
    RAISERROR(14262, 16, 1, '@name', @name)
    RETURN(1) -- Failure
  END

  -- Check EnumType
  IF (@enum_type NOT IN ('ALL', 'ACTUAL', 'TARGET'))
  BEGIN
    RAISERROR(14266, 16, 1, '@enum_type', 'ALL, ACTUAL, TARGET')
    RETURN(1) -- Failure
  END

  -- Check Notification Method
  IF ((@notification_method < 1) OR (@notification_method > 7))
  BEGIN
    RAISERROR(14266, 16, 1, '@notification_method', @res_valid_range)
    RETURN(1) -- Failure
  END

  -- If EnumType is 'TARGET', check if we have a @TargetName parameter
  IF (@enum_type = 'TARGET') AND (@target_name IS NULL)
  BEGIN
    RAISERROR(14502, 16, 1)
    RETURN(1) -- Failure
  END

  -- If EnumType isn't 'TARGET', we shouldn't have an @target_name parameter
  IF (@enum_type <> 'TARGET') AND (@target_name IS NOT NULL)
  BEGIN
    RAISERROR(14503, 16, 1)
    RETURN(1) -- Failure
  END

  -- Translate the Name into an ID
  IF (@object_type = 'ALERTS')
  BEGIN
    SELECT @id = id
    FROM msdb.dbo.sysoperators
    WHERE (name = @name)
  END
  IF (@object_type = 'OPERATORS')
  BEGIN
    SELECT @id = id
    FROM msdb.dbo.sysalerts
    WHERE (name = @name)
  END

  -- Translate the TargetName into a TargetID
  IF (@target_name IS NOT NULL)
  BEGIN
    IF (@object_type = 'OPERATORS')
    BEGIN
      SELECT @target_id = id
      FROM msdb.dbo.sysoperators
      WHERE (name = @target_name )
    END
    IF (@object_type = 'ALERTS')
    BEGIN
      SELECT @target_id = id
      FROM msdb.dbo.sysalerts
      WHERE (name = @target_name)
    END
    IF (@target_id IS NULL) -- IE. the Target Name is invalid
    BEGIN
      RAISERROR(14262, 16, 1, @object_type, @target_name)
      RETURN(1) -- Failure
    END
  END

  -- Ok, the parameters look good so generate the SQL then EXECUTE() it...

  -- Generate the 'stub' SELECT clause and the FROM clause
  IF (@object_type = 'OPERATORS') -- So we want a list of Operators for the supplied AlertID
  BEGIN
    SELECT @select_clause = N'SELECT operator_id = o.id, operator_name = o.name, '
    IF (@enum_type = 'ALL')
      SELECT @from_clause = N'FROM msdb.dbo.sysoperators o LEFT OUTER JOIN msdb.dbo.sysnotifications sn ON (o.id = sn.operator_id) '
    ELSE
      SELECT @from_clause = N'FROM msdb.dbo.sysoperators o, msdb.dbo.sysnotifications sn '
  END
  IF (@object_type = 'ALERTS') -- So we want a list of Alerts for the supplied OperatorID
  BEGIN
    SELECT @select_clause = N'SELECT alert_id = a.id, alert_name = a.name, '
    IF (@enum_type = 'ALL')
      SELECT @from_clause = N'FROM msdb.dbo.sysalerts a LEFT OUTER JOIN msdb.dbo.sysnotifications sn ON (a.id = sn.alert_id) '
    ELSE
      SELECT @from_clause = N'FROM msdb.dbo.sysalerts a, msdb.dbo.sysnotifications sn '
  END

  -- Add the required use_xxx columns to the SELECT clause
  IF (@notification_method & 1 = 1)
    SELECT @select_clause = @select_clause + N'use_email = ISNULL((sn.notification_method & 1) / POWER(2, 0), 0), '
  IF (@notification_method & 2 = 2)
    SELECT @select_clause = @select_clause + N'use_pager = ISNULL((sn.notification_method & 2) / POWER(2, 1), 0), '
  IF (@notification_method & 4 = 4)
    SELECT @select_clause = @select_clause + N'use_netsend = ISNULL((sn.notification_method & 4) / POWER(2, 2), 0), '

  -- Remove the trailing comma
  SELECT @select_clause = SUBSTRING(@select_clause, 1, (DATALENGTH(@select_clause) / 2) - 2) + N' '

  -- Generate the WHERE clause
  IF (@object_type = 'OPERATORS')
  BEGIN
    IF (@enum_type = 'ALL')
      SELECT @from_clause = @from_clause + N' AND (sn.alert_id = ' + CONVERT(VARCHAR(10), @id) + N')'

    IF (@enum_type = 'ACTUAL')
      SELECT @where_clause = N'WHERE (o.id = sn.operator_id) AND (sn.alert_id = ' + CONVERT(VARCHAR(10), @id) + N') AND (sn.notification_method & ' + CONVERT(VARCHAR, @notification_method) + N' <> 0)'

    IF (@enum_type = 'TARGET')
      SELECT @where_clause = N'WHERE (o.id = sn.operator_id) AND (sn.operator_id = ' + CONVERT(VARCHAR(10), @target_id) + N') AND (sn.alert_id = ' + CONVERT(VARCHAR(10), @id) + N')'
  END
  IF (@object_type = 'ALERTS')
  BEGIN
    IF (@enum_type = 'ALL')
      SELECT @from_clause = @from_clause + N' AND (sn.operator_id = ' + CONVERT(VARCHAR(10), @id) + N')'

    IF (@enum_type = 'ACTUAL')
      SELECT @where_clause = N'WHERE (a.id = sn.alert_id) AND (sn.operator_id = ' + CONVERT(VARCHAR(10), @id) + N') AND (sn.notification_method & ' + CONVERT(VARCHAR, @notification_method) + N' <> 0)'

    IF (@enum_type = 'TARGET')
      SELECT @where_clause = N'WHERE (a.id = sn.alert_id) AND (sn.alert_id = ' + CONVERT(VARCHAR(10), @target_id) + N') AND (sn.operator_id = ' + CONVERT(VARCHAR(10), @id) + N')'
  END

  -- Add the has_email and has_pager columns to the SELECT clause
  IF (@object_type = 'OPERATORS')
  BEGIN
    SELECT @select_clause = @select_clause + N', has_email = PATINDEX(N''%[^ ]%'', ISNULL(o.email_address, N''''))'
    SELECT @select_clause = @select_clause + N', has_pager = PATINDEX(N''%[^ ]%'', ISNULL(o.pager_address, N''''))'
    SELECT @select_clause = @select_clause + N', has_netsend = PATINDEX(N''%[^ ]%'', ISNULL(o.netsend_address, N''''))'
  END
  IF (@object_type = 'ALERTS')
  BEGIN
    -- NOTE: We return counts so that the UI can detect 'unchecking' the last notification
    SELECT @select_clause = @select_clause + N', has_email = (SELECT COUNT(*) FROM sysnotifications WHERE (alert_id = a.id) AND ((notification_method & 1) = 1)) '
    SELECT @select_clause = @select_clause + N', has_pager = (SELECT COUNT(*) FROM sysnotifications WHERE (alert_id = a.id) AND ((notification_method & 2) = 2)) '
    SELECT @select_clause = @select_clause + N', has_netsend = (SELECT COUNT(*) FROM sysnotifications WHERE (alert_id = a.id) AND ((notification_method & 4) = 4)) '
  END

  EXECUTE (@select_clause + @from_clause + @where_clause)

  RETURN(@@error) -- 0 means success
END
go

PRINT ''
PRINT 'Creating procedure sp_help_jobactivity...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_help_jobactivity')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobactivity
go
CREATE PROCEDURE sp_help_jobactivity
  @job_id     UNIQUEIDENTIFIER = NULL,  -- If provided should NOT also provide job_name
  @job_name   sysname          = NULL,  -- If provided should NOT also provide job_id
  @session_id INT = NULL
AS
BEGIN
  DECLARE @retval          INT
  DECLARE @session_id_as_char NVARCHAR(16)
  SET NOCOUNT ON

  -- Remove any leading/trailing spaces from parameters (except @owner_login_name)
  SELECT @job_name         = LTRIM(RTRIM(@job_name))

  -- Turn [nullable] empty string parameters into NULLs
  IF (@job_name         = N'') SELECT @job_name = NULL

  IF ((@job_id IS NOT NULL) OR (@job_name IS NOT NULL))
  BEGIN
    EXECUTE @retval = sp_verify_job_identifiers '@job_name',
                                                '@job_id',
                                                 @job_name OUTPUT,
                                                 @job_id   OUTPUT
    IF (@retval <> 0)
      RETURN(1) -- Failure
  END



  IF @session_id IS NULL
    SELECT TOP(1) @session_id = session_id FROM syssessions ORDER by agent_start_date DESC
  ELSE IF NOT EXISTS( SELECT * FROM syssessions WHERE session_id = @session_id)
  BEGIN
    SELECT @session_id_as_char = CONVERT(NVARCHAR(16), @session_id)
    RAISERROR(14262, -1, -1, '@session_id', @session_id_as_char)
    RETURN(1) --failure
  END

  SELECT
      ja.session_id,
      ja.job_id,
    j.name AS job_name,
    ja.run_requested_date,
    ja.run_requested_source,
    ja.queued_date,
    ja.start_execution_date,
    ja.last_executed_step_id,
    ja.last_executed_step_date,
    ja.stop_execution_date,
    ja.next_scheduled_run_date,
    ja.job_history_id,
    jh.message,
    jh.run_status,
    jh.operator_id_emailed,
    jh.operator_id_netsent,
    jh.operator_id_paged
  FROM
    (msdb.dbo.sysjobactivity ja LEFT JOIN msdb.dbo.sysjobhistory jh ON ja.job_history_id = jh.instance_id)
    join msdb.dbo.sysjobs_view j on ja.job_id = j.job_id
  WHERE
    (@job_id IS NULL OR ja.job_id = @job_id) AND
     ja.session_id = @session_id

  RETURN(0)
END
go
/**************************************************************/
/*                                                            */
/*                    T  R  I G  G  E  R  S                   */
/*                                                            */
/**************************************************************/

/**************************************************************/
/* DROP THE OLD 6.x TRIGGERS                                  */
/* [multiple triggers of the same type are allowed in 7.0]    */
/**************************************************************/

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'NewOrChangedNotification')
              AND (type = 'TR')))
  DROP TRIGGER NewOrChangedNotification

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'RemovedNotification')
              AND (type = 'TR')))
  DROP TRIGGER RemovedNotification
go

/**************************************************************/
/* TRIG_NOTIFICATION_INS_OR_UPD                               */
/**************************************************************/

PRINT ''
PRINT 'Creating trigger trig_notification_ins_or_upd...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_notification_ins_or_upd')
              AND (type = 'TR')))
  DROP TRIGGER trig_notification_ins_or_upd
go
CREATE TRIGGER trig_notification_ins_or_upd
ON msdb.dbo.sysnotifications
FOR INSERT,
    UPDATE
AS
BEGIN
  SET NOCOUNT ON

  -- First, throw out 'non-notification' rows
  DELETE FROM msdb.dbo.sysnotifications
  WHERE (notification_method = 0)

  -- Reset the has_notification flag for the affected alerts
  UPDATE msdb.dbo.sysalerts
  SET has_notification = 0
  FROM inserted           i,
       msdb.dbo.sysalerts sa
  WHERE (i.alert_id = sa.id)

  -- Update sysalerts.has_notification (for email)
  UPDATE msdb.dbo.sysalerts
  SET has_notification = has_notification | 1
  FROM msdb.dbo.sysalerts        sa,
       msdb.dbo.sysnotifications sn,
       inserted                  i
  WHERE (sa.id = sn.alert_id)
    AND (sa.id = i.alert_id)
    AND ((sn.notification_method & 1) = 1)

  -- Update sysalerts.has_notification (for pager)
  UPDATE msdb.dbo.sysalerts
  SET has_notification = has_notification | 2
  FROM msdb.dbo.sysalerts        sa,
       msdb.dbo.sysnotifications sn,
       inserted                  i
  WHERE (sa.id = sn.alert_id)
    AND (sa.id = i.alert_id)
    AND ((sn.notification_method & 2) = 2)

  -- Update sysalerts.has_notification (for netsend)
  UPDATE msdb.dbo.sysalerts
  SET has_notification = has_notification | 4
  FROM msdb.dbo.sysalerts        sa,
       msdb.dbo.sysnotifications sn,
       inserted                  i
  WHERE (sa.id = sn.alert_id)
    AND (sa.id = i.alert_id)
    AND ((sn.notification_method & 4) = 4)
END
go

/**************************************************************/
/* TRIG_NOTIFICATION_DELETE                                   */
/**************************************************************/

PRINT ''
PRINT 'Creating trigger trig_notification_delete...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_notification_delete')
              AND (type = 'TR')))
  DROP TRIGGER trig_notification_delete
go
CREATE TRIGGER trig_notification_delete
ON msdb.dbo.sysnotifications
FOR DELETE
AS
BEGIN
  SET NOCOUNT ON

  -- Reset the has_notification flag for the affected alerts
  UPDATE msdb.dbo.sysalerts
  SET has_notification = 0
  FROM deleted            d,
       msdb.dbo.sysalerts sa
  WHERE (d.alert_id = sa.id)

  -- Update sysalerts.has_notification (for email)
  UPDATE msdb.dbo.sysalerts
  SET has_notification = has_notification | 1
  FROM msdb.dbo.sysalerts        sa,
       msdb.dbo.sysnotifications sn,
       deleted                   d
  WHERE (sa.id = sn.alert_id)
    AND (sa.id = d.alert_id)
    AND ((sn.notification_method & 1) = 1)

  -- Update sysalerts.has_notification (for pager)
  UPDATE msdb.dbo.sysalerts
  SET has_notification = has_notification | 2
  FROM msdb.dbo.sysalerts        sa,
       msdb.dbo.sysnotifications sn,
       deleted                   d
  WHERE (sa.id = sn.alert_id)
    AND (sa.id = d.alert_id)
    AND ((sn.notification_method & 2) = 2)

  -- Update sysalerts.has_notification (for netsend)
  UPDATE msdb.dbo.sysalerts
  SET has_notification = has_notification | 4
  FROM msdb.dbo.sysalerts        sa,
       msdb.dbo.sysnotifications sn,
       deleted                   d
  WHERE (sa.id = sn.alert_id)
    AND (sa.id = d.alert_id)
    AND ((sn.notification_method & 4) = 4)
END
go


/**************************************************************/
/**                                                          **/
/**           O B J E C T    P E R M I S S I O N S           **/
/**                                                          **/
/**************************************************************/

--------------------------------------------------------------
-- SQL Agent roles and procs
--------------------------------------------------------------
PRINT ''
PRINT 'Setting object permissions...'
go
-- Create the TargetServers role (for use by target servers when downloading jobs / uploading status)
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysusers
            WHERE (name = N'TargetServersRole')
              AND (issqlrole = 1)))
BEGIN
  -- If there are no members in the role, then drop and re-create it
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.sysusers   su,
            msdb.dbo.sysmembers sm
       WHERE (su.uid = sm.groupuid)
         AND (su.name = N'TargetServersRole')
         AND (su.issqlrole = 1)) = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_droprole @rolename = N'TargetServersRole'
    EXECUTE msdb.dbo.sp_addrole @rolename = N'TargetServersRole'
  END
END
ELSE
  EXECUTE msdb.dbo.sp_addrole @rolename = N'TargetServersRole'


 -- Create the SQLAgentUserRole role
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysusers
            WHERE (name = N'SQLAgentUserRole')
              AND (issqlrole = 1)))
BEGIN
  -- If there are no members in the role, then drop and re-create it
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.sysusers   su,
            msdb.dbo.sysmembers sm
       WHERE (su.uid = sm.groupuid)
         AND (su.name = N'SQLAgentUserRole')
         AND (su.issqlrole = 1)) = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_droprole @rolename = N'SQLAgentUserRole'
    EXECUTE msdb.dbo.sp_addrole @rolename = N'SQLAgentUserRole'
  END
END
ELSE
  EXECUTE msdb.dbo.sp_addrole @rolename = N'SQLAgentUserRole'

-- Create the SQLAgentReaderRole role
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysusers
            WHERE (name = N'SQLAgentReaderRole')
              AND (issqlrole = 1)))
BEGIN
  -- If there are no members in the role, then drop and re-create it
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.sysusers   su,
            msdb.dbo.sysmembers sm
       WHERE (su.uid = sm.groupuid)
         AND (su.name = N'SQLAgentReaderRole')
         AND (su.issqlrole = 1)) = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_droprole @rolename = N'SQLAgentReaderRole'
    EXECUTE msdb.dbo.sp_addrole @rolename = N'SQLAgentReaderRole'
  END
END
ELSE
  EXECUTE msdb.dbo.sp_addrole @rolename = N'SQLAgentReaderRole'

-- Create the SQLAgentOperatorRole role
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysusers
            WHERE (name = N'SQLAgentOperatorRole')
              AND (issqlrole = 1)))
BEGIN
  -- If there are no members in the role, then drop and re-create it
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.sysusers   su,
            msdb.dbo.sysmembers sm
       WHERE (su.uid = sm.groupuid)
         AND (su.name = N'SQLAgentOperatorRole')
         AND (su.issqlrole = 1)) = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_droprole @rolename = N'SQLAgentOperatorRole'
    EXECUTE msdb.dbo.sp_addrole @rolename = N'SQLAgentOperatorRole'
  END
END
ELSE
  EXECUTE msdb.dbo.sp_addrole @rolename = N'SQLAgentOperatorRole'


-- Add roles to each other.
-- SQLAgentReaderRole is also SQLAgentUserRole
-- SQLAgentOperatorRole is also SQLAgentReaderRole and SQLAgentUserRole

EXECUTE sp_addrolemember @rolename = 'SQLAgentUserRole' ,
                   @membername = 'SQLAgentReaderRole'

EXECUTE sp_addrolemember @rolename = 'SQLAgentReaderRole' ,
                   @membername = 'SQLAgentOperatorRole'
go

GRANT EXECUTE ON sp_notify_operator          TO SQLAgentUserRole

-- Permissions a non-SA needs to create/update/delete a job
GRANT EXECUTE ON sp_get_sqlagent_properties  TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_category            TO SQLAgentUserRole
GRANT EXECUTE ON sp_enum_sqlagent_subsystems TO SQLAgentUserRole
GRANT EXECUTE ON sp_add_jobserver            TO SQLAgentUserRole
GRANT EXECUTE ON sp_delete_jobserver         TO SQLAgentUserRole
GRANT SELECT  ON syscategories               TO SQLAgentUserRole

GRANT EXECUTE ON sp_help_jobhistory  TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobhistory_full TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobhistory_summary TO SQLAgentUserRole

GRANT EXECUTE ON sp_purge_jobhistory  TO SQLAgentOperatorRole

GRANT EXECUTE ON sp_add_jobstep    TO SQLAgentUserRole
GRANT EXECUTE ON sp_update_jobstep TO SQLAgentUserRole
GRANT EXECUTE ON sp_delete_jobstep TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobstep   TO SQLAgentUserRole
GRANT EXECUTE ON sp_agent_get_jobstep   TO SQLAgentUserRole

GRANT EXECUTE ON sp_help_jobsteplog      TO SQLAgentUserRole
GRANT EXECUTE ON sp_delete_jobsteplog    TO SQLAgentUserRole

--Schedule related SP's
GRANT EXECUTE ON sp_add_schedule       TO SQLAgentUserRole
GRANT EXECUTE ON sp_update_schedule       TO SQLAgentUserRole
GRANT EXECUTE ON sp_delete_schedule       TO SQLAgentUserRole
GRANT EXECUTE ON sp_attach_schedule       TO SQLAgentUserRole
GRANT EXECUTE ON sp_detach_schedule       TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_schedule         TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobcount         TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobs_in_schedule TO SQLAgentUserRole

GRANT EXECUTE ON sp_add_jobschedule    TO SQLAgentUserRole
GRANT EXECUTE ON sp_update_jobschedule TO SQLAgentUserRole
GRANT EXECUTE ON sp_delete_jobschedule TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_jobschedule   TO SQLAgentUserRole

GRANT EXECUTE ON sp_add_job             TO SQLAgentUserRole
GRANT EXECUTE ON sp_update_job          TO SQLAgentUserRole
GRANT EXECUTE ON sp_delete_job          TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_job            TO SQLAgentUserRole
GRANT EXECUTE ON sp_start_job           TO SQLAgentUserRole
GRANT EXECUTE ON sp_stop_job            TO SQLAgentUserRole

--alert spocs
GRANT EXECUTE ON sp_help_alert              TO SQLAgentOperatorRole

--proxy sprocs
GRANT EXECUTE ON sp_help_proxy           TO SQLAgentUserRole
GRANT EXECUTE ON sp_enum_login_for_proxy TO SQLAgentOperatorRole


--other
GRANT EXECUTE ON sp_help_jobserver        TO SQLAgentUserRole
GRANT EXECUTE ON sp_help_targetserver    TO SQLAgentOperatorRole
GRANT EXECUTE ON sp_help_notification    TO SQLAgentOperatorRole

GRANT EXECUTE ON sp_check_for_owned_jobs     TO SQLAgentUserRole
GRANT EXECUTE ON sp_check_for_owned_jobsteps TO SQLAgentUserRole
GRANT EXECUTE ON sp_get_jobstep_db_username  TO SQLAgentUserRole
GRANT EXECUTE ON sp_get_job_alerts           TO SQLAgentUserRole

GRANT EXECUTE ON sp_uniquetaskname TO SQLAgentUserRole
GRANT EXECUTE ON sp_addtask        TO SQLAgentUserRole
GRANT EXECUTE ON sp_droptask       TO SQLAgentUserRole

GRANT SELECT ON sysjobs_view                    TO SQLAgentUserRole
GRANT SELECT ON sysschedules_localserver_view   TO SQLAgentUserRole

GRANT SELECT  ON sysnotifications        TO SQLAgentOperatorRole
GRANT SELECT  ON sysoperators            TO SQLAgentOperatorRole
GRANT SELECT  ON sysalerts               TO SQLAgentOperatorRole
GRANT SELECT  ON sysalerts_performance_counters_view TO SQLAgentOperatorRole
GRANT SELECT  ON sysproxies              TO TargetServersRole

REVOKE ALL ON systargetservers                 FROM PUBLIC
REVOKE ALL ON systargetservers_view            FROM PUBLIC
REVOKE ALL ON systargetservergroups            FROM PUBLIC
REVOKE ALL ON systargetservergroupmembers      FROM PUBLIC
REVOKE INSERT, UPDATE, DELETE ON syscategories FROM PUBLIC
REVOKE ALL ON sysalerts                        FROM PUBLIC
REVOKE ALL ON sysalerts_performance_counters_view FROM PUBLIC
REVOKE ALL ON sysoperators                     FROM PUBLIC
REVOKE ALL ON sysnotifications                 FROM PUBLIC

REVOKE ALL ON systargetservers                 FROM SQLAgentUserRole
REVOKE ALL ON systargetservers_view            FROM SQLAgentUserRole
REVOKE ALL ON systargetservergroups            FROM SQLAgentUserRole
REVOKE ALL ON systargetservergroupmembers      FROM SQLAgentUserRole
REVOKE INSERT, UPDATE, DELETE ON syscategories FROM SQLAgentUserRole

REVOKE ALL ON sysalerts                        FROM SQLAgentUserRole
REVOKE ALL ON sysalerts_performance_counters_view FROM SQLAgentUserRole
REVOKE ALL ON sysoperators                     FROM SQLAgentUserRole
REVOKE ALL ON sysnotifications                 FROM SQLAgentUserRole

--DENY TargetServerRole permission that would allow modifying of jobs
DENY ALL ON sp_add_jobserver     TO TargetServersRole
DENY ALL ON sp_delete_jobserver  TO TargetServersRole

DENY ALL ON sp_add_jobstep    TO TargetServersRole
DENY ALL ON sp_update_jobstep TO TargetServersRole
DENY ALL ON sp_delete_jobstep TO TargetServersRole

DENY ALL ON sp_add_jobschedule    TO TargetServersRole
DENY ALL ON sp_update_jobschedule TO TargetServersRole
DENY ALL ON sp_delete_jobschedule TO TargetServersRole

DENY ALL ON sp_add_job    TO TargetServersRole
DENY ALL ON sp_update_job TO TargetServersRole
DENY ALL ON sp_delete_job TO TargetServersRole
DENY ALL ON sp_start_job  TO TargetServersRole
DENY ALL ON sp_stop_job   TO TargetServersRole
DENY ALL ON sp_post_msx_operation       TO TargetServersRole

DENY ALL ON sp_addtask        TO TargetServersRole
DENY ALL ON sp_droptask       TO TargetServersRole

GRANT SELECT, UPDATE, DELETE ON sysdownloadlist               TO TargetServersRole
GRANT SELECT, UPDATE         ON sysjobservers                 TO TargetServersRole
GRANT SELECT, UPDATE         ON systargetservers              TO TargetServersRole
GRANT EXECUTE                ON sp_downloaded_row_limiter     TO TargetServersRole
GRANT SELECT                 ON sysjobs                       TO TargetServersRole
GRANT EXECUTE                ON sp_help_jobstep               TO TargetServersRole
GRANT EXECUTE                ON sp_agent_get_jobstep          TO TargetServersRole
GRANT EXECUTE                ON sp_help_jobschedule           TO TargetServersRole
GRANT EXECUTE                ON sp_sqlagent_refresh_job       TO TargetServersRole
GRANT EXECUTE                ON sp_sqlagent_probe_msx         TO TargetServersRole
GRANT EXECUTE                ON sp_sqlagent_check_msx_version TO TargetServersRole
GRANT EXECUTE                ON sp_enlist_tsx                 TO TargetServersRole
GRANT SELECT                 ON syssubsystems                 TO TargetServersRole

GRANT EXECUTE                ON sp_help_jobactivity           TO SQLAgentUserRole
GRANT EXECUTE                ON sp_help_operator              TO SQLAgentUserRole

go

/**************************************************************/
/* SP_SEM_ADD_MESSAGE [used by SEM only]                      */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sem_add_message...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_sem_add_message')
              AND (type = 'P')))
  DROP PROCEDURE sp_sem_add_message
go
CREATE PROCEDURE sp_sem_add_message
  @msgnum   INT           = NULL,
  @severity SMALLINT      = NULL,
  @msgtext  NVARCHAR(255) = NULL,
  @lang     sysname       = NULL, -- Message language name
  @with_log VARCHAR(5)    = 'FALSE',
  @replace  VARCHAR(7)    = NULL
AS
BEGIN
  DECLARE @retval        INT
  DECLARE @language_name sysname

  SET NOCOUNT ON

  SET ROWCOUNT 1
  SELECT @language_name = name
  FROM sys.syslanguages
  WHERE msglangid = (SELECT number
                     FROM master.dbo.spt_values
                     WHERE (type = 'LNG')
                       AND (name = @lang))
  SET ROWCOUNT 0

  SELECT @language_name = ISNULL(@language_name, 'us_english')
  EXECUTE @retval = master.dbo.sp_addmessage @msgnum, @severity, @msgtext, @language_name, @with_log, @replace
  RETURN(@retval)
END
go

/**************************************************************/
/* SP_SEM_DROP_MESSAGE [used by SEM only]                     */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_sem_drop_message...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_sem_drop_message')
              AND (type = 'P')))
  DROP PROCEDURE sp_sem_drop_message
go
CREATE PROCEDURE sp_sem_drop_message
  @msgnum int     = NULL,
  @lang   sysname = NULL -- Message language name
AS
BEGIN
  DECLARE @retval        INT
  DECLARE @language_name sysname

  SET NOCOUNT ON

  SET ROWCOUNT 1
  SELECT @language_name = name
  FROM sys.syslanguages
  WHERE msglangid = (SELECT number
                     FROM master.dbo.spt_values
                     WHERE (type = 'LNG')
                       AND (name = @lang))
  SET ROWCOUNT 0

  SELECT @language_name = ISNULL(@language_name, 'us_english')
  EXECUTE @retval = master.dbo.sp_dropmessage @msgnum, @language_name
  RETURN(@retval)
END
go

/**************************************************************/
/* SP_GET_MESSAGE_DESCRIPTION [used by SEM only]              */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_get_message_description...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = 'sp_get_message_description')
              AND (type = 'P')))
  DROP PROCEDURE sp_get_message_description
go
CREATE PROCEDURE sp_get_message_description
  @error INT
AS
BEGIN
  IF EXISTS (SELECT * FROM master.dbo.sysmessages WHERE (error = @error) AND (msglangid = (SELECT msglangid FROM sys.syslanguages WHERE (langid = @@langid))))
    SELECT description FROM master.dbo.sysmessages WHERE (error = @error) AND (msglangid = (SELECT msglangid FROM sys.syslanguages WHERE (langid = @@langid)))
  ELSE
    SELECT description FROM master.dbo.sysmessages WHERE (error = @error) AND (msglangid = 1033)
END
go

/**************************************************************/
/* SP_HELP_JOBHISTORY_SEM                                     */
/**************************************************************/
use [msdb]

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_jobhistory_sem')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_jobhistory_sem
go

CREATE PROCEDURE sp_help_jobhistory_sem
               @job_id               UNIQUEIDENTIFIER,
               @job_name             sysname,
               @step_id              INT,
               @sql_message_id       INT,
               @sql_severity         INT,
               @start_run_date       INT,
               @end_run_date         INT,
               @start_run_time       INT,
               @end_run_time         INT,
               @minimum_run_duration INT,
               @run_status           INT,
               @minimum_retries      INT,
               @oldest_first         INT,
               @server               sysname,
               @mode                 VARCHAR(7),
               @order_by             INT,
               @distributed_job_history BIT
AS
-- SQL Enterprise Manager format
IF(@distributed_job_history = 1)
  SELECT sj.job_id,
     null as step_name,
     sjh.last_outcome_message as message,
     sjh.last_run_outcome as run_status,
     sjh.last_run_date as run_date,
     sjh.last_run_time as run_time,
     sjh.last_run_duration as run_duration,
     null as operator_emailed,
     null as operator_netsentname,
     null as operator_paged
  FROM msdb.dbo.sysjobservers                sjh
  JOIN msdb.dbo.systargetservers sts ON (sts.server_id = sjh.server_id)
  JOIN msdb.dbo.sysjobs_view     sj  ON(sj.job_id = sjh.job_id)
  WHERE 
  (@job_id = sjh.job_id)
ELSE
  SELECT sjh.step_id,
     sjh.step_name,
     sjh.message,
     sjh.run_status,
     sjh.run_date,
     sjh.run_time,
     sjh.run_duration,
     operator_emailed = so1.name,
     operator_netsent = so2.name,
     operator_paged = so3.name
  FROM msdb.dbo.sysjobhistory                sjh
     LEFT OUTER JOIN msdb.dbo.sysoperators so1  ON (sjh.operator_id_emailed = so1.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so2  ON (sjh.operator_id_netsent = so2.id)
     LEFT OUTER JOIN msdb.dbo.sysoperators so3  ON (sjh.operator_id_paged = so3.id),
     msdb.dbo.sysjobs_view                 sj
  WHERE (sj.job_id = sjh.job_id)
  AND (@job_id = sjh.job_id)
  ORDER BY (sjh.instance_id * @order_by)
GO

-- Add permissions for this SP.
GRANT EXECUTE ON sp_help_jobhistory_sem TO SQLAgentUserRole

/**************************************************************/
/*                                                            */
/*   S  U  P  P  O  R  T     P  R  O  C  E  D  U  R  E  S     */
/*                                                            */
/**************************************************************/

/**************************************************************/
/* SP_CONVERT_JOBID_TO_CHAR [used by SEM only]                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_convert_jobid_to_char...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_convert_jobid_to_char')
              AND (type = 'P')))
  DROP PROCEDURE sp_convert_jobid_to_char
go
CREATE PROCEDURE sp_convert_jobid_to_char
  @job_id         UNIQUEIDENTIFIER,
  @job_id_as_char NVARCHAR(34) OUTPUT -- 34 because of the leading '0x'
AS
BEGIN
  DECLARE @job_id_as_binary BINARY(16)
  DECLARE @temp             NCHAR(8)
  DECLARE @counter          INT
  DECLARE @byte_value       INT
  DECLARE @high_word        INT
  DECLARE @low_word         INT
  DECLARE @high_high_nybble INT
  DECLARE @high_low_nybble  INT
  DECLARE @low_high_nybble  INT
  DECLARE @low_low_nybble   INT

  SET NOCOUNT ON

  SELECT @job_id_as_binary = CONVERT(BINARY(16), @job_id)
  SELECT @temp = CONVERT(NCHAR(8), @job_id_as_binary)

  SELECT @job_id_as_char = N''
  SELECT @counter = 1

  WHILE (@counter <= (DATALENGTH(@temp) / 2))
  BEGIN
    SELECT @byte_value       = CONVERT(INT, CONVERT(BINARY(2), SUBSTRING(@temp, @counter, 1)))
    SELECT @high_word        = (@byte_value & 0xff00) / 0x100
    SELECT @low_word         = (@byte_value & 0x00ff)
    SELECT @high_high_nybble = (@high_word & 0xff) / 16
    SELECT @high_low_nybble  = (@high_word & 0xff) % 16
    SELECT @low_high_nybble  = (@low_word & 0xff) / 16
    SELECT @low_low_nybble   = (@low_word & 0xff) % 16

    IF (@high_high_nybble < 10)
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('0') + @high_high_nybble)
    ELSE
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('A') + (@high_high_nybble - 10))

    IF (@high_low_nybble < 10)
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('0') + @high_low_nybble)
    ELSE
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('A') + (@high_low_nybble - 10))

    IF (@low_high_nybble < 10)
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('0') + @low_high_nybble)
    ELSE
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('A') + (@low_high_nybble - 10))

    IF (@low_low_nybble < 10)
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('0') + @low_low_nybble)
    ELSE
      SELECT @job_id_as_char = @job_id_as_char + NCHAR(ASCII('A') + (@low_low_nybble - 10))

    SELECT @counter = @counter + 1
  END

  SELECT @job_id_as_char = N'0x' + LOWER(@job_id_as_char)
END
go

/**************************************************************/
/*                                                            */
/*                  D A T A B A S E    M A I L                */
/*                                                            */
/**************************************************************/

/**************************************************************/
/*                                                            */
/*  Database Mail Tables                                      */
/*                                                            */
/**************************************************************/

----------------------------------------------------------------
-- Database Mail: general configuraiton tables
----------------------------------------------------------------

IF (OBJECT_ID(N'dbo.sysmail_profile', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysmail_profile...'
   
   CREATE TABLE dbo.sysmail_profile
   (
      profile_id int identity not null,
      name sysname not null,
      description nvarchar(256) null,
      last_mod_datetime datetime not null default getdate(),
      last_mod_user sysname not null default suser_sname()

      CONSTRAINT [SYSMAIL_PROFILE_IDMustBeUnique] PRIMARY KEY(profile_id),
      CONSTRAINT [SYSMAIL_PROFILE_NameMustBeUnique] UNIQUE (name)
   )
END
ELSE
BEGIN
  ALTER TABLE dbo.sysmail_profile ALTER COLUMN description nvarchar(256) null
END
go

IF (OBJECT_ID(N'dbo.sysmail_principalprofile', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysmail_principalprofile...'
   
   CREATE TABLE dbo.sysmail_principalprofile
   (
      profile_id int not null references sysmail_profile(profile_id) on delete cascade,
      principal_sid varbinary(85) not null, -- sys.database_principals.sid
      is_default bit not null default 0,
      last_mod_datetime datetime not null default getdate(),
      last_mod_user sysname not null default suser_sname()

      CONSTRAINT [SYSMAIL_PRINCIPALPROFILE_ProfilePrincipalMustBeUnique] PRIMARY KEY(profile_id,principal_sid),
   )
END
ELSE
BEGIN
   -- add principal_sid column
   IF NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='principal_sid' and id =
        (SELECT OBJECT_ID(N'dbo.sysmail_principalprofile', 'U')))
   BEGIN
      ALTER TABLE dbo.sysmail_principalprofile ADD principal_sid varbinary(85) not null default 0xFFFF
   END
END
go

IF (OBJECT_ID(N'dbo.sysmail_account', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysmail_account...'
   
   CREATE TABLE dbo.sysmail_account
   (
      account_id int identity not null,
      name sysname not null,
      description nvarchar(256) null,
      email_address nvarchar(128) not null,
      display_name nvarchar(128) null,
      replyto_address nvarchar(128) null,      
      last_mod_datetime datetime not null default getdate(),
      last_mod_user sysname not null default suser_sname()

      CONSTRAINT [SYSMAIL_ACCOUNT_IDMustBeUnique] PRIMARY KEY(account_id),
      CONSTRAINT [SYSMAIL_ACCOUNT_NameMustBeUnique] UNIQUE (name)
   )
END
ELSE
BEGIN
  ALTER TABLE dbo.sysmail_account ALTER COLUMN description nvarchar(256) null
  ALTER TABLE dbo.sysmail_account ALTER COLUMN display_name nvarchar(128) null
  ALTER TABLE dbo.sysmail_account ALTER COLUMN replyto_address nvarchar(128) null
END
go

IF (OBJECT_ID(N'dbo.sysmail_profileaccount', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysmail_profileaccount...'

   CREATE TABLE dbo.sysmail_profileaccount
   (
      profile_id int not null,
      account_id int not null references sysmail_account(account_id) on delete cascade,
      sequence_number int null,
      last_mod_datetime datetime not null default getdate(),
      last_mod_user sysname not null default suser_sname()

      CONSTRAINT [SYSMAIL_ACCOUNT_ProfileAccountMustBeUnique] PRIMARY KEY(profile_id,account_id)
   )  
END
ELSE
BEGIN
  ALTER TABLE dbo.sysmail_profileaccount ALTER COLUMN sequence_number int null
END
go

IF (OBJECT_ID(N'dbo.sysmail_servertype', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysmail_servertype...'

   CREATE TABLE dbo.sysmail_servertype
   (
      servertype sysname not null,
      is_incoming bit not null default 0,
      is_outgoing bit not null default 1,
      last_mod_datetime datetime not null default getdate(),
      last_mod_user sysname not null default suser_sname()

      CONSTRAINT [SYSMAIL_SERVERTYPE_TypeMustBeUnique] PRIMARY KEY(servertype),
   )
END
go

IF (OBJECT_ID(N'dbo.sysmail_server', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysmail_server...'
   
   CREATE TABLE dbo.sysmail_server
   (
      account_id int not null references sysmail_account(account_id) on delete cascade,
      servertype sysname not null references sysmail_servertype(servertype),
      servername sysname not null,
      port int not null default 25,
      username nvarchar(128) null,
      credential_id int null,
      use_default_credentials bit not null default 0,
      enable_ssl bit not null default 0,
      flags int not null default 0,
      timeout int NULL,
      last_mod_datetime datetime not null default getdate(),
      last_mod_user sysname not null default suser_sname()

      CONSTRAINT [SYSMAIL_ACCOUNT_AccountServerTypeMustBeUnique] PRIMARY KEY(account_id,servertype)
   )
END
ELSE -- check if we need to add missing columns
BEGIN
   IF NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='use_default_credentials' and id =
        (SELECT OBJECT_ID(N'dbo.sysmail_server', 'U')))
   BEGIN
      ALTER TABLE dbo.sysmail_server ADD use_default_credentials bit not null default 0
   END

   IF NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='enable_ssl' and id =
        (SELECT OBJECT_ID(N'dbo.sysmail_server', 'U')))
   BEGIN
      ALTER TABLE dbo.sysmail_server ADD enable_ssl bit not null default 0
   END

   IF NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='flags' and id =
        (SELECT OBJECT_ID(N'dbo.sysmail_server', 'U')))
   BEGIN
      ALTER TABLE dbo.sysmail_server ADD flags int not null default 0
   END
   IF NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='timeout' and id =
        (SELECT OBJECT_ID(N'dbo.sysmail_server', 'U')))
   BEGIN
      ALTER TABLE dbo.sysmail_server ADD timeout int NULL
   END
END
go

IF (OBJECT_ID(N'dbo.sysmail_configuration', 'U') IS NULL)
BEGIN
   PRINT ''
   PRINT 'Creating table sysmail_configuration...'

   CREATE TABLE dbo.sysmail_configuration
   (
      paramname nvarchar(256) not null,
      paramvalue nvarchar(256) null,
      description nvarchar(256) null,
      last_mod_datetime datetime not null default getdate(),
      last_mod_user sysname not null default suser_sname()

      CONSTRAINT [SYSMAIL_CONFIGURATION_ParamnameMustBeUnique] PRIMARY KEY(paramname)
   )
END
ELSE
BEGIN
  ALTER TABLE dbo.sysmail_configuration ALTER COLUMN paramvalue nvarchar(256) null
  ALTER TABLE dbo.sysmail_configuration ALTER COLUMN description nvarchar(256) null
END
go

-- populate default configuration settings
DECLARE @description NVARCHAR(256)

SELECT @description = FORMATMESSAGE(14642)
IF NOT EXISTS(SELECT * FROM dbo.sysmail_configuration WHERE paramname = N'DefaultAttachmentEncoding')
  INSERT INTO dbo.sysmail_configuration (paramname,paramvalue,description) VALUES (N'DefaultAttachmentEncoding', N'MIME', @description)
ELSE
  UPDATE dbo.sysmail_configuration SET description = @description WHERE paramname = N'DefaultAttachmentEncoding'

-- maximum size of an Database Mail atachement 1MB
SELECT @description = FORMATMESSAGE(14644)
IF NOT EXISTS(SELECT * FROM dbo.sysmail_configuration WHERE paramname = N'MaxFileSize')
  INSERT INTO dbo.sysmail_configuration (paramname,paramvalue,description) VALUES (N'MaxFileSize', N'1000000', @description)
ELSE
  UPDATE dbo.sysmail_configuration SET description = @description WHERE paramname = N'MaxFileSize'

SELECT @description = FORMATMESSAGE(14645)
IF NOT EXISTS(SELECT * FROM dbo.sysmail_configuration WHERE paramname = N'ProhibitedExtensions')
  INSERT INTO dbo.sysmail_configuration (paramname,paramvalue,description) VALUES (N'ProhibitedExtensions', N'exe,dll,vbs,js', @description)
ELSE
  UPDATE dbo.sysmail_configuration SET description = @description WHERE paramname = N'ProhibitedExtensions'

SELECT @description = FORMATMESSAGE(14646)
IF NOT EXISTS(SELECT * FROM dbo.sysmail_configuration WHERE paramname = N'AccountRetryAttempts')
  INSERT INTO dbo.sysmail_configuration (paramname,paramvalue,description) VALUES (N'AccountRetryAttempts', N'1', @description)
ELSE
  UPDATE dbo.sysmail_configuration SET description = @description WHERE paramname = N'AccountRetryAttempts'

SELECT @description = FORMATMESSAGE(14647)
IF NOT EXISTS(SELECT * FROM dbo.sysmail_configuration WHERE paramname = N'AccountRetryDelay')
  INSERT INTO dbo.sysmail_configuration (paramname,paramvalue,description) VALUES (N'AccountRetryDelay', N'60', @description)
ELSE
  UPDATE dbo.sysmail_configuration SET description = @description WHERE paramname = N'AccountRetryDelay'

SELECT @description = FORMATMESSAGE(14648)
IF NOT EXISTS(SELECT * FROM dbo.sysmail_configuration WHERE paramname = N'DatabaseMailExeMinimumLifeTime')
  INSERT INTO dbo.sysmail_configuration (paramname,paramvalue,description) VALUES (N'DatabaseMailExeMinimumLifeTime', N'600', @description)
ELSE
  UPDATE dbo.sysmail_configuration SET description = @description WHERE paramname = N'DatabaseMailExeMinimumLifeTime'

-- component logging level: normal - 1, extended - 2 (default), verbose - 3
SELECT @description = FORMATMESSAGE(14664)
IF NOT EXISTS(SELECT * FROM dbo.sysmail_configuration WHERE paramname = N'LoggingLevel')
  INSERT INTO dbo.sysmail_configuration (paramname,paramvalue,description) VALUES (N'LoggingLevel', N'2', @description)
ELSE
  UPDATE dbo.sysmail_configuration SET description = @description WHERE paramname = N'LoggingLevel'

go

----------------------------------------------------------------
-- Database Mail: mail host database specific tables
----------------------------------------------------------------

-----------------------------------------------------------
-- TABLE sysmail_mailitems      
-----------------------------------------------------------
-- sysmail_mailitems      : Contains one row for each mail sent using sp_send_dbmail.
--                          Contains mails that are waiting to be sent and the sent mail.
--                          sent_status determines its status
--
-- mailitem_id            : Id for the row. Auto-generated.
-- profile_id             : ID of profile to use to send the mail.
-- recipients             : People on the To list.
-- copy_recipients        : People on the Cc list.
-- blind_copy_recipients  : People on the Bcc list.
-- subject                : Subject of the email.
-- body                   : Body of the email.
-- body_format         : Body format. 0 (Text), 1(Html)                        
-- importance             : Importance of the email:
--                          0(Low), 1(Normal), 2(High).
-- sensitivity            : Sensitivity of the email:
--                          0(Normal), 1(Personal), 2(Private), 3(Confidential).
-- attachment_encoding    : Encoding to use for mail and attachments: 
--                          0(MIME), 1(UUEncode), 2(BINHEX), 3(S/MIME).
-- query                  : SQL query that was executed in this mail 
-- execute_query_database : The database to execute the query in  
-- attach_query_result_as_file : Option for attaching the query result 
--                               as a file instead of in the mail body
-- query_result_header    : Option for including query result column headers
-- query_result_width     : The query result overall width in characters
-- query_result_separator : The query result column separaror character
-- exclude_query_output   : Option for supressing query output being returned to
--                          the client that is sending the mail
-- append_query_error     : Option for appending query error messages to the mail item
-- send_request_date   : Date this mail item was created
-- send_request_user      : The user that created this mail item
-- sent_account_id        : The account_id that was used to send this mail item 
-- sent_status            : The current status of the mail item. 
--                        : 0(PendingSend), 1(SendSuccessful), 2(SendFailed), 3(AttemptingSendRetry)
-- sent_date              : Date the mail item was sent or failed to be sent
-- from_adress            : Optional override of the from header.
-- reply_to               : Optional setting of the reply-to header.
-----------------------------------------------------------
IF(OBJECT_ID('dbo.sysmail_mailitems', 'U') IS NULL)
BEGIN
  PRINT 'Creating TABLE sysmail_mailitems'

    CREATE TABLE sysmail_mailitems
    (
       mailitem_id                 INT                     IDENTITY(1,1) NOT NULL,
       profile_id                  INT                     NOT NULL,        
       recipients                  VARCHAR(MAX)    NULL, 
       copy_recipients             VARCHAR(MAX)    NULL,
       blind_copy_recipients       VARCHAR(MAX)    NULL,
       subject                     NVARCHAR(255)      NULL,
       from_address                VARCHAR(MAX)    NULL,
       reply_to                    VARCHAR(MAX)    NULL,
       body                        NVARCHAR(MAX)      NULL, 
       body_format                 VARCHAR(20)             NULL, 
       importance                  VARCHAR(6)              NULL,
       sensitivity                 VARCHAR(12)             NULL,
       file_attachments            NVARCHAR(MAX)      NULL,  
       attachment_encoding         VARCHAR(20)             NULL,
       query                       NVARCHAR(MAX)      NULL,
       execute_query_database      sysname                 NULL,  
       attach_query_result_as_file BIT                     NULL,
       query_result_header         BIT        NULL,
       query_result_width          INT                     NULL,  
       query_result_separator      CHAR(1)         NULL,
       exclude_query_output        BIT       NULL,
       append_query_error          BIT       NULL,
       send_request_date           DATETIME     NOT NULL DEFAULT GETDATE(),
       send_request_user           sysname              NOT NULL DEFAULT SUSER_SNAME(),
       sent_account_id             INT       NULL,
       sent_status                 TINYINT         NULL     DEFAULT 0,
       sent_date                   DATETIME     NULL,
       last_mod_date               DATETIME     NOT NULL DEFAULT GETDATE(),
       last_mod_user               sysname              NOT NULL DEFAULT SUSER_SNAME(),

       CONSTRAINT [sysmail_mailitems_id_MustBeUnique] PRIMARY KEY(mailitem_id),
       CONSTRAINT [sysmail_OutMailMustHaveAtleastOneRecipient]
                            CHECK (NOT (recipients IS NULL AND copy_recipients IS NULL AND blind_copy_recipients IS NULL)),
       CONSTRAINT [sysmail_OutMailRecipientCannotBeEmpty]           
                            CHECK (DATALENGTH(ISNULL(recipients, ''))      + 
            DATALENGTH(ISNULL(copy_recipients, '')) +  
            DATALENGTH(ISNULL(blind_copy_recipients, '')) <> 0),
       CONSTRAINT [sysmail_OutMailAttachmentEncodingMustBeValid]
             CHECK (attachment_encoding IN ('MIME', 'S/MIME', 'BINHEX', 'UUENCODE')),       
       CONSTRAINT [sysmail_OutMailImportanceMustBeValid]
             CHECK (importance IN ('LOW', 'NORMAL', 'HIGH')),
       CONSTRAINT [sysmail_OutMailSensitivityMustBeValid]           
             CHECK (sensitivity IN('NORMAL', 'PERSONAL', 'PRIVATE', 'CONFIDENTIAL'))
    )
END
ELSE
BEGIN
    -- handle schema upgrade for sysmail_mailitems table
    IF NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='profile_id' and id =
        (SELECT OBJECT_ID(N'dbo.sysmail_mailitems', 'U')))
    BEGIN
       ALTER TABLE dbo.sysmail_mailitems ADD profile_id INT NOT NULL DEFAULT -1
    END

    IF NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='from_address' and id =
        (SELECT OBJECT_ID(N'dbo.sysmail_mailitems', 'U')))
    BEGIN
       ALTER TABLE dbo.sysmail_mailitems ADD from_address VARCHAR(MAX) NULL
       ALTER TABLE dbo.sysmail_mailitems ADD reply_to VARCHAR(MAX) NULL
    END
END
GO

/**************************************************************/
/* sysmail_allitems                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating view sysmail_allitems...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysmail_allitems')
              AND (type = 'V')))
  DROP VIEW sysmail_allitems
go

CREATE VIEW sysmail_allitems
AS
SELECT mailitem_id,
       profile_id,
       recipients,
       copy_recipients,
       blind_copy_recipients,
       subject,
       body,
       body_format,
       importance,
       sensitivity,
       file_attachments,
       attachment_encoding,
       query,
       execute_query_database,
       attach_query_result_as_file,
       query_result_header,
       query_result_width,
       query_result_separator,
       exclude_query_output,
       append_query_error,
       send_request_date,
       send_request_user,
       sent_account_id,
       CASE sent_status 
          WHEN 0 THEN 'unsent' 
          WHEN 1 THEN 'sent' 
          WHEN 3 THEN 'retrying' 
          ELSE 'failed' 
       END as sent_status,
       sent_date,
       last_mod_date,
       last_mod_user
FROM msdb.dbo.sysmail_mailitems
WHERE (send_request_user = SUSER_SNAME()) OR (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)

GO

/**************************************************************/
/* sysmail_sentitems                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating view sysmail_sentitems...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysmail_sentitems')
              AND (type = 'V')))
  DROP VIEW sysmail_sentitems
go

CREATE VIEW sysmail_sentitems
AS
SELECT * FROM msdb.dbo.sysmail_allitems WHERE sent_status = 'sent'

go

/**************************************************************/
/* sysmail_unsentitems                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating view sysmail_unsentitems...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysmail_unsentitems')
              AND (type = 'V')))
  DROP VIEW sysmail_unsentitems
go

CREATE VIEW sysmail_unsentitems
AS
SELECT * FROM msdb.dbo.sysmail_allitems WHERE (sent_status = 'unsent' OR sent_status = 'retrying')

go

/**************************************************************/
/* sysmail_faileditems                                               */
/**************************************************************/

PRINT ''
PRINT 'Creating view sysmail_faileditems...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysmail_faileditems')
              AND (type = 'V')))
  DROP VIEW sysmail_faileditems
go

CREATE VIEW sysmail_faileditems
AS
SELECT * FROM msdb.dbo.sysmail_allitems WHERE sent_status = 'failed'

go

-----------------------------------------------------------
-- procedure sysmail_delete_mailitems_sp
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sysmail_delete_mailitems_sp', 'P') IS NULL
    DROP PROCEDURE dbo.sysmail_delete_mailitems_sp
GO
-----
PRINT 'Creating sysmail_delete_mailitems_sp'
-----
GO
CREATE PROCEDURE sysmail_delete_mailitems_sp
   @sent_before DATETIME   = NULL, -- sent before
   @sent_status varchar(8)   = NULL -- sent status
AS
BEGIN

   SET @sent_status       = LTRIM(RTRIM(@sent_status))
   IF @sent_status           = '' SET @sent_status = NULL

   IF ( (@sent_status IS NOT NULL) AND
         (LOWER(@sent_status collate SQL_Latin1_General_CP1_CS_AS) NOT IN ( 'unsent', 'sent', 'failed', 'retrying') ) )
   BEGIN
      RAISERROR(14266, -1, -1, '@sent_status', 'unsent, sent, failed, retrying')
      RETURN(1) -- Failure
   END

   IF ( @sent_before IS NULL AND @sent_status IS NULL )
   BEGIN
      RAISERROR(14608, -1, -1, '@sent_before', '@sent_status')  
      RETURN(1) -- Failure
   END

   DELETE FROM msdb.dbo.sysmail_allitems 
   WHERE 
        ((@sent_before IS NULL) OR ( send_request_date < @sent_before))
   AND ((@sent_status IS NULL) OR (sent_status = @sent_status))

   DECLARE @localmessage nvarchar(255)
    SET @localmessage = FORMATMESSAGE(14665, SUSER_SNAME(), @@ROWCOUNT)
    exec msdb.dbo.sysmail_logmailevent_sp @event_type=1, @description=@localmessage

END
GO

-----------------------------------------------------------
-- TABLE sysmail_attachments
-----------------------------------------------------------
-- sysmail_attachments  : Contains mail item attachments
--
-- attachment_id        : Id for the row. Auto-generated
-- mailitem_id          : Optional key to the mail items that this entry is a about
-- filename             : The filename of the attachment
-- filesize             : Size of the file
-- encoded_attachment   : The file data encoded in base64
----------------------------------------------------------------
IF (OBJECT_ID('dbo.sysmail_attachments', 'U') IS NULL)
BEGIN
  PRINT 'Creating TABLE sysmail_attachments'

    CREATE TABLE sysmail_attachments
    (
        attachment_id       INT   IDENTITY(1, 1) NOT NULL,
        mailitem_id      INT  NOT NULL CONSTRAINT 
         FK_sysmail_mailitems_mailitem_id 
                    FOREIGN KEY (mailitem_id) REFERENCES sysmail_mailitems(mailitem_id)  ON DELETE CASCADE,
        filename            NVARCHAR(260)       NOT NULL,
        filesize            INT                 NOT NULL,
        attachment          VARBINARY(MAX)      NULL,
        last_mod_date       DATETIME            NOT NULL DEFAULT GETDATE(),
        last_mod_user       sysname             NOT NULL DEFAULT SUSER_SNAME()
    )
END
ELSE
BEGIN
   BEGIN TRAN

   -- remove any old constraint
   declare @fkName sysname
   DECLARE @sql NVARCHAR(4000)
   SELECT @fkName = cstr.name
      FROM
         sys.tables AS tbl
      INNER JOIN sys.foreign_keys AS cstr ON cstr.parent_object_id=tbl.object_id
      LEFT OUTER JOIN sys.indexes AS ki ON ki.index_id = cstr.key_index_id and ki.object_id = cstr.referenced_object_id
      INNER JOIN sys.tables rtbl ON rtbl.object_id = cstr.referenced_object_id
      WHERE
      tbl.name=N'sysmail_attachments' 

   IF (@fkName IS NOT NULL)
   BEGIN
      select @sql = N'ALTER TABLE sysmail_attachments DROP CONSTRAINT ' + QUOTENAME(@fkName)
      EXEC (@sql)
   END
   
   ALTER TABLE sysmail_attachments ADD CONSTRAINT FK_sysmail_mailitems_mailitem_id 
                    FOREIGN KEY (mailitem_id) REFERENCES sysmail_mailitems(mailitem_id) ON DELETE CASCADE
   COMMIT
END
GO


/**************************************************************/
/* sysmail_mailattachments                                */
/**************************************************************/

PRINT ''
PRINT 'Creating view sysmail_mailattachments...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysmail_mailattachments')
              AND (type = 'V')))
  DROP VIEW sysmail_mailattachments
go

CREATE VIEW sysmail_mailattachments
AS
SELECT attachment_id,
       sa.mailitem_id,
       filename,
       filesize,
       attachment,
       sa.last_mod_date,
       sa.last_mod_user
  FROM msdb.dbo.sysmail_attachments sa
  JOIN msdb.dbo.sysmail_mailitems sm ON sa.mailitem_id = sm.mailitem_id
  WHERE (sm.send_request_user = SUSER_SNAME()) OR (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1)
GO

-----------------------------------------------------------
-- TABLE sysmail_send_retries
-----------------------------------------------------------
-- sysmail_send_retries : Contains send mail retry history
--
-- conversation_handle  : The conversation handle that initiated the retry
-- mailitem_id          : Optional key to the mail items that this entry is a about
-- send_attempts        : The current number of send attempts
-- last_send_attempt_date : date of the last send attempt
----------------------------------------------------------------
IF (OBJECT_ID('dbo.sysmail_send_retries', 'U') IS NULL)
BEGIN
    PRINT 'Creating TABLE sysmail_send_retries'
    CREATE TABLE sysmail_send_retries
    (
        conversation_handle     uniqueidentifier PRIMARY KEY NOT NULL,
        mailitem_id             INT              NOT NULL CONSTRAINT FK_mailitems_mailitem_id 
                    FOREIGN KEY (mailitem_id) REFERENCES sysmail_mailitems(mailitem_id) ON DELETE CASCADE,
        send_attempts           INT              NOT NULL DEFAULT 1,
        last_send_attempt_date  DATETIME         NOT NULL DEFAULT GETDATE()
    )
END
ELSE
BEGIN
   BEGIN TRAN

   -- remove any old constraint
   declare @fkName sysname
   DECLARE @sql NVARCHAR(4000)
   SELECT @fkName = cstr.name
      FROM
         sys.tables AS tbl
      INNER JOIN sys.foreign_keys AS cstr ON cstr.parent_object_id=tbl.object_id
      LEFT OUTER JOIN sys.indexes AS ki ON ki.index_id = cstr.key_index_id and ki.object_id = cstr.referenced_object_id
      INNER JOIN sys.tables rtbl ON rtbl.object_id = cstr.referenced_object_id
      WHERE
      tbl.name=N'sysmail_send_retries' 

   IF (@fkName IS NOT NULL)
   BEGIN
      SET @sql = N'ALTER TABLE sysmail_send_retries DROP CONSTRAINT ' + @fkName
      EXECUTE (@sql)
   END

   ALTER TABLE sysmail_send_retries ADD CONSTRAINT FK_mailitems_mailitem_id 
                    FOREIGN KEY (mailitem_id) REFERENCES sysmail_mailitems(mailitem_id) ON DELETE CASCADE

   COMMIT
END
GO

-----------------------------------------------------------
-- TABLE sysmail_log
-----------------------------------------------------------
-- sysmail_log      : Contains error and event logging 
--
-- log_id           : Id for the row. Auto-generated.
-- event_type       : The event type for this record
--                    0(Success), 1(information), 2(Warning), 3(error)

-- log_date         : Create date of this log entry
-- description      : The text description of this entry
-- process_id       : The DatabaseMail (exe) process id that added this entry 
-- mailitem_id      : Optional key to the mail items that this entry is a about
-- account_id       : Optional account_id hat this entry is a about
----------------------------------------------------------------
IF (OBJECT_ID('dbo.sysmail_log', 'U') IS NULL)
BEGIN
  PRINT 'Creating TABLE sysmail_log'

    CREATE TABLE sysmail_log
    (
        log_id         INT             IDENTITY(1, 1) NOT NULL,
        event_type      INT             NOT NULL,
        log_date        DATETIME        NOT NULL    DEFAULT GETDATE(),
        description     NVARCHAR(max)   NULL,
        process_id      INT             NULL,
        mailitem_id     INT             NULL,
        account_id      INT             NULL,
        last_mod_date   DATETIME        NOT NULL DEFAULT GETDATE(),
        last_mod_user   sysname     NOT NULL DEFAULT SUSER_SNAME(),

        CONSTRAINT [sysmail_log_id_MustBeUnique] PRIMARY KEY(log_id),
    )
END
ELSE
BEGIN
   -- remove any old constraint
   declare @fkName sysname
   DECLARE @sql NVARCHAR(4000)
   SELECT @fkName = cstr.name
      FROM
         sys.tables AS tbl
      INNER JOIN sys.foreign_keys AS cstr ON cstr.parent_object_id=tbl.object_id
      LEFT OUTER JOIN sys.indexes AS ki ON ki.index_id = cstr.key_index_id and ki.object_id = cstr.referenced_object_id
      INNER JOIN sys.tables rtbl ON rtbl.object_id = cstr.referenced_object_id
      WHERE
      tbl.name=N'sysmail_log' 

   IF (@fkName IS NOT NULL)
   begin
      select @sql = N'ALTER TABLE sysmail_log DROP CONSTRAINT ' + QUOTENAME(@fkName)
      EXEC (@sql)
   end
END
GO

/**************************************************************/
/* sysmail_event_log                                         */
/**************************************************************/
use msdb
go

PRINT ''
PRINT 'Creating view sysmail_event_log...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysmail_event_log')
              AND (type = 'V')))
  DROP VIEW sysmail_event_log
go

CREATE VIEW sysmail_event_log
AS
SELECT log_id,
       CASE event_type 
          WHEN 0 THEN 'success' 
          WHEN 1 THEN 'information' 
          WHEN 2 THEN 'warning' 
          ELSE 'error' 
       END as event_type,
       log_date,
       description,
       process_id,
       sl.mailitem_id,
       account_id,
       sl.last_mod_date,
       sl.last_mod_user
FROM [dbo].[sysmail_log]  sl
WHERE (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1) OR 
      (EXISTS ( SELECT mailitem_id FROM [dbo].[sysmail_allitems] ai WHERE sl.mailitem_id = ai.mailitem_id ))

GO

-----------------------------------------------------------
-- procedure sysmail_delete_log_sp
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sysmail_delete_log_sp', 'P') IS NULL
    DROP PROCEDURE dbo.sysmail_delete_log_sp
GO
-----
PRINT 'Creating sysmail_delete_log_sp'
-----
GO
CREATE PROCEDURE sysmail_delete_log_sp
   @logged_before DATETIME   = NULL, 
   @event_type varchar(15)   = NULL
AS
BEGIN

   SET @event_type       = LTRIM(RTRIM(@event_type))
   IF @event_type        = '' SET @event_type = NULL
   DECLARE @event_type_numeric INT

   IF ( (@event_type IS NOT NULL) AND
         (LOWER(@event_type collate SQL_Latin1_General_CP1_CS_AS) NOT IN ( 'success', 'warning', 'error', 'information' ) ) )
   BEGIN
        RAISERROR(14266, -1, -1, '@event_type', 'success, warning, error, information')
      RETURN(1) -- Failure
   END   
   
   IF ( @event_type IS NOT NULL)
   BEGIN
      SET @event_type_numeric = ( SELECT CASE 
                           WHEN @event_type = 'success' THEN 0
                           WHEN @event_type = 'information' THEN 1
                           WHEN @event_type = 'warning' THEN 2
                           ELSE 3 END 
                        )
   END
   ELSE
      SET @event_type_numeric = NULL

   DELETE FROM msdb.dbo.sysmail_log 
   WHERE 
        ((@logged_before IS NULL) OR ( log_date < @logged_before))
   AND ((@event_type_numeric IS NULL) OR (@event_type_numeric = event_type))
END
GO

-----------------------------------------------------------
-- sysmail_query_transfer : Table used to transfer data between a helper xp's and the calling sp's.
--                   Rows are created and deleted in the context of each call
--
-- uid              : guid for the row. Generated by the user  
-- text_data        : Attachment data in binary form
----------------------------------------------------------------
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysmail_query_transfer')
                  AND (type = 'U')))
BEGIN
PRINT 'Creating TABLE sysmail_query_transfer'
CREATE TABLE sysmail_query_transfer
(
    uid             uniqueidentifier    NOT NULL PRIMARY KEY,
    text_data       NVARCHAR(max)       NULL,
    create_date     DATETIME            NOT NULL DEFAULT GETDATE()
)
END
GO

-----------------------------------------------------------
-- sysmail_attachments_transfer : Table used to transfer data between a helper xp's
--                        and the calling sp's. Rows are created and deleted 
--                        in the context of each call
--
-- uid              : guid for the row. Generated by the user  
-- filename         : Attachment file name
-- filesize         : Attachment file size in bytes
-- attachment       : Attachment data in binary form
----------------------------------------------------------------
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysmail_attachments_transfer')
                  AND (type = 'U')))
BEGIN
PRINT 'Creating TABLE sysmail_attachments_transfer'
CREATE TABLE sysmail_attachments_transfer
(
    transfer_id       INT                 IDENTITY(1, 1) NOT NULL PRIMARY KEY,
    uid         uniqueidentifier    NOT NULL,
    filename        NVARCHAR(260)       NOT NULL,
    filesize        INT                 NOT NULL,
    attachment      VARBINARY(MAX)      NULL,
    create_date     DATETIME            NOT NULL DEFAULT GETDATE()
)
END
GO

/*************************************************************************/
/*                                                                       */
/*  Database Mail Triggers                                               */
/*                                                                       */
/*************************************************************************/

------------------------------------------------------------
-- Database Mail: triggers on general configuration tables
------------------------------------------------------------
PRINT ''
PRINT 'Creating trigger trig_sysmail_profile...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_sysmail_profile')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_sysmail_profile
go

CREATE TRIGGER trig_sysmail_profile
ON msdb.dbo.sysmail_profile
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_profile'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_profile 
      SET last_mod_datetime = getdate(),last_mod_user = suser_sname() 
      FROM sysmail_profile p, inserted i
      WHERE p.profile_id = i.profile_id
   END
END
go

PRINT ''
PRINT 'Creating trigger trig_principalprofile...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_principalprofile')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_principalprofile
go

CREATE TRIGGER trig_principalprofile
ON msdb.dbo.sysmail_principalprofile
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_principalprofile'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_principalprofile 
      SET last_mod_datetime = getdate(),last_mod_user = suser_sname() 
      FROM sysmail_principalprofile p, inserted i
      WHERE p.profile_id = i.profile_id and p.principal_sid = i.principal_sid
   END
END
go

PRINT ''
PRINT 'Creating trigger trig_sysmail_account...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_sysmail_account')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_sysmail_account
go

CREATE TRIGGER trig_sysmail_account
ON msdb.dbo.sysmail_account
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_account'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_account 
      SET last_mod_datetime = getdate(),last_mod_user = suser_sname() 
      FROM sysmail_account a, inserted i
      WHERE a.account_id = i.account_id
   END
END
go

PRINT ''
PRINT 'Creating trigger trig_sysmail_profileaccount...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_sysmail_profileaccount')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_sysmail_profileaccount
go

CREATE TRIGGER trig_sysmail_profileaccount
ON msdb.dbo.sysmail_profileaccount
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_profileaccount'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_profileaccount 
      SET last_mod_datetime = getdate(),last_mod_user = suser_sname() 
      FROM sysmail_profileaccount p, inserted i
      WHERE p.profile_id = i.profile_id and p.account_id = i.account_id
   END
END
go

PRINT ''
PRINT 'Creating trigger trig_sysmail_profile_delete...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_sysmail_profile_delete')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_sysmail_profile_delete
go

CREATE TRIGGER trig_sysmail_profile_delete
ON msdb.dbo.sysmail_profile
FOR DELETE
AS
BEGIN
   DELETE FROM msdb.dbo.sysmail_profileaccount
   WHERE profile_id IN (SELECT profile_id FROM deleted)
END
go

PRINT ''
PRINT 'Creating trigger trig_sysmail_servertype...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_sysmail_servertype')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_sysmail_servertype
go

CREATE TRIGGER trig_sysmail_servertype
ON msdb.dbo.sysmail_servertype
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_servertype'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_servertype 
      SET last_mod_datetime = getdate(),last_mod_user = suser_sname() 
      FROM sysmail_servertype s, inserted i
      where s.servertype = i.servertype
   END
END
go

SET NOCOUNT ON
IF NOT EXISTS(SELECT * FROM dbo.sysmail_servertype WHERE servertype = N'SMTP')
BEGIN
    INSERT INTO dbo.sysmail_servertype (servertype) VALUES (N'SMTP')
END
SET NOCOUNT OFF
go

PRINT ''
PRINT 'Creating trigger trig_sysmail_server...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_sysmail_server')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_sysmail_server
go

CREATE TRIGGER trig_sysmail_server
ON msdb.dbo.sysmail_server
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_server'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_server 
      SET last_mod_datetime = getdate(),last_mod_user = suser_sname() 
      FROM sysmail_server s, inserted i
      WHERE s.account_id = i.account_id and s.servertype = i.servertype
   END
END
go

PRINT ''
PRINT 'Creating trigger trig_sysmail_configuration...'

IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'trig_sysmail_configuration')
              AND (type = 'TR')))
   DROP TRIGGER dbo.trig_sysmail_configuration
go

CREATE TRIGGER trig_sysmail_configuration
ON msdb.dbo.sysmail_configuration
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_configuration'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_configuration 
      SET last_mod_datetime = getdate(),last_mod_user = suser_sname() 
      FROM sysmail_configuration c, inserted i
      WHERE c.paramname = i.paramname
   END
END
go

-------------------------------------------------------------------------
-- Database Mail: triggers on general mail host database specific tables
-------------------------------------------------------------------------
IF (OBJECT_ID('dbo.trig_sysmail_mailitems', 'TR') IS NOT NULL)
    DROP TRIGGER dbo.trig_sysmail_mailitems
GO

CREATE TRIGGER trig_sysmail_mailitems
ON msdb.dbo.sysmail_mailitems
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_mailitems'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_mailitems 
      SET last_mod_date = GETDATE(), last_mod_user = SUSER_SNAME() 
      FROM sysmail_mailitems m, inserted i
      WHERE m.mailitem_id = i.mailitem_id
   END
END
GO

IF (OBJECT_ID('dbo.trig_sysmail_attachments', 'TR') IS NOT NULL)
    DROP TRIGGER dbo.trig_sysmail_attachments
GO

CREATE TRIGGER trig_sysmail_attachments
ON msdb.dbo.sysmail_attachments
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_attachments'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_attachments 
      SET last_mod_date = GETDATE(), last_mod_user = SUSER_SNAME() 
      FROM sysmail_attachments a, inserted i
      WHERE a.attachment_id = i.attachment_id
   END
END
GO

IF (OBJECT_ID('dbo.trig_sysmail_log', 'TR') IS NOT NULL)
    DROP TRIGGER dbo.trig_sysmail_log
GO

CREATE TRIGGER trig_sysmail_log
ON msdb.dbo.sysmail_log
FOR UPDATE
AS
BEGIN
   SET NOCOUNT ON  

   IF (TRIGGER_NESTLEVEL( OBJECT_ID('dbo.trig_sysmail_log'), 'AFTER' , 'DML' ) <= 1) 
   BEGIN  
      UPDATE msdb.dbo.sysmail_log 
      SET last_mod_date = GETDATE(), last_mod_user = SUSER_SNAME() 
      FROM sysmail_log l, inserted i
      WHERE l.log_id = i.log_id
   END
END
GO

/*********************************************************************************/
/*                                                                               */
/*  Database Mail Utility Functions                                              */
/*                                                                               */
/*********************************************************************************/
-----------------------------------------------------------
-- ConvertToInt : Converts a string to integer. Returns null
--                if the input string is not a valid int.
--
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.ConvertToInt', 'FN') IS NULL
    DROP FUNCTION dbo.ConvertToInt
GO

CREATE FUNCTION dbo.ConvertToInt(@string nvarchar(255), @maxValue int, @defValue int) RETURNS int
AS
BEGIN
    DECLARE @value bigint   
    SET @value = @defValue 
    SET @string = LTRIM(RTRIM(@string))

    -- Check if there is any character other than 0-9 in the string.
    IF ((@string IS NOT NULL AND @string <> N'') AND (@string NOT LIKE '%[^0-9]%'))
    BEGIN
        --INT's have a max of 10 digits
        IF(LEN(@string) <= 10)
        BEGIN
        -- Try converting to bigint. Return default if the value is bigger than @maxValue
        SET @value = CONVERT(bigint, @string)
        IF(@value > CONVERT(bigint, @maxValue))
            SET @value = @defValue
        END
    END

    RETURN CONVERT(int, @value)
END
GO

/*********************************************************************************/
/*                                                                               */
/*  Database Mail Stored Procedures                                              */
/*                                                                               */
/*********************************************************************************/

-------------------------------------------------------
-- Database Mail: configuration stored procedures
-------------------------------------------------------
PRINT ''
PRINT 'Creating procedure sysmail_verify_accountparams_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_verify_accountparams_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_verify_accountparams_sp
go

CREATE PROCEDURE dbo.sysmail_verify_accountparams_sp
   @use_default_credentials bit,
   @mailserver_type sysname      OUTPUT,  -- @mailserver_type must be provided. Usually SMTP
   @username      nvarchar(128)  OUTPUT, -- returns trimmed value, NULL if empty
   @password      nvarchar(128)  OUTPUT  -- returns trimmed value,  NULL if empty
AS
   SET @username = LTRIM(RTRIM(@username))
   SET @password = LTRIM(RTRIM(@password))
   SET @mailserver_type = LTRIM(RTRIM(@mailserver_type))

    IF(@username = N'')         SET @username = NULL
    IF(@password = N'')         SET @password = NULL
    IF(@mailserver_type = N'')  SET @mailserver_type = NULL

   IF(@mailserver_type IS NULL)
   BEGIN
      RAISERROR(14614, -1, -1, @mailserver_type)   
      RETURN (1)  
   END

   -- default credentials should supercede any explicit credentials passed in
   IF((@use_default_credentials = 1) AND (@username IS NOT NULL))
   BEGIN
      RAISERROR(14666, -1, -1)   
      RETURN (1)
   END  

   --If a password is specified then @username must be a non empty string
   IF((@password IS NOT NULL) AND (@username IS NULL))
   BEGIN
      RAISERROR(14615, -1, -1)   
      RETURN (1)
   END  

   RETURN(0) -- SUCCESS
go

--
-- Stored Proc:
--   sysmail_verify_addressparams_sp
--   It is an internal SP that verifies if the given address is in the correct format.
--
--   Some valid email addresses are:
--
--     user@host
--     "Display Name" <user@host>
--     "Doe, John" <user@host>
--  
--
--   Note: 
--     Originally, DBMail supported only the first format: user@host, which is separated by a
--     delimiter semicolon (;). This was a quick fix for VSTS#160781 (explanation: "Since the
--     comma and semicolon are most confusing delimiters to users, this function only checks
--     comma (,)"). However, recently users reported that this can break some scenarios. So,
--     in hotfix #12107460 we lift the restriction and we support the case where the comma (,)
--     is enclosed in double quotes. as the invalid delimiter to fix the bug VSTS 160781.
--     If the comma (,) appears outside of the double quotes, we still raise an error.
--     There is no plan to support nested double quotes or escaped double quotes, assuming such
--     things are even possible to do (and ultimately supported by DBMail).
--
--     For generality and future extensions, the function name is reserved to support formats
--     other than the ones listed above.
--   
--
-- Parameters:
--   @address        -- The string of address that will be verified
--   @parameter_name -- The name of parameter that will be shown in error message to tell users
--                      which parameter is wrong. 
--                      For example, the name is '@recipients' or '@replyto_address'.
--                        
PRINT ''
PRINT 'Creating procedure sysmail_verify_addressparams_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_verify_addressparams_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_verify_addressparams_sp
go

CREATE PROCEDURE dbo.sysmail_verify_addressparams_sp
  @address          VARCHAR(MAX),
  @parameter_name   NVARCHAR(32)
AS
  IF ((@address IS NOT NULL) AND (@address != N''))
  BEGIN

    DECLARE @curr_char NVARCHAR(1)
    DECLARE @curr_char_index INT
    DECLARE @in_double_quotes BIT
    DECLARE @error_at INT

    SET @curr_char = N''        -- current character being analyzed
    SET @curr_char_index = 1    -- position of current character being analyzed
    SET @in_double_quotes = 0   -- flag (1=within double quotes; 0 otherwise)
    SET @error_at = 0           -- position (starting at 1) where the illegal comma was detected

    WHILE @curr_char_index <= len(@address)
    BEGIN
        SET @curr_char = substring(@address, @curr_char_index, 1)
        IF @curr_char = N'"'
          SET @in_double_quotes = CASE @in_double_quotes WHEN 0 THEN 1 ELSE 0 END

        IF @curr_char = N','
          SET @error_at = CASE @in_double_quotes WHEN 1 THEN 0 ELSE @curr_char_index END

        IF @error_at > 0 BREAK

        SET @curr_char_index = @curr_char_index + 1
    END

    IF @error_at > 0 
    BEGIN
      -- Comma is the wrong format to separate addresses. Users should use the semicolon ";".
      RAISERROR(14613, 16, 1, @parameter_name, @address)
      RETURN(1)
    END

  END

  RETURN(0) -- SUCCESS
go

PRINT ''
PRINT 'Creating procedure sysmail_verify_principal_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_verify_principal_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_verify_principal_sp
go

CREATE PROCEDURE dbo.sysmail_verify_principal_sp
   @principal_id int,
   @principal_name sysname,
   @allow_both_nulls bit,
   @principal_sid varbinary(85) OUTPUT
AS
   IF @allow_both_nulls = 0
   BEGIN
      -- at least one parameter must be supplied
      IF (@principal_id IS NULL AND @principal_name IS NULL)
      BEGIN
         RAISERROR(14604, -1, -1, 'principal')  
         RETURN(1)
      END
   END

   DECLARE @principalid int

   IF (@principal_id IS NOT NULL AND @principal_name IS NOT NULL) -- both parameters supplied
   BEGIN
     SELECT @principalid=principal_id FROM msdb.sys.database_principals 
            WHERE type in ('U','S','G') AND principal_id = @principal_id AND name = @principal_name

      IF (@principalid IS NULL)
      BEGIN
         RAISERROR(14605, -1, -1, 'principal')  
         RETURN(2)
      END
   END
   ELSE IF (@principal_id IS NOT NULL) -- use id
   BEGIN
     SELECT @principalid=principal_id FROM msdb.sys.database_principals 
            WHERE type in ('U','S','G') AND principal_id = @principal_id

      IF (@principalid IS NULL)
      BEGIN
         RAISERROR(14606, -1, -1, 'principal')
         RETURN(3)
      END      
   END
   ELSE IF (@principal_name IS NOT NULL)  -- use name
   BEGIN
     SELECT @principalid=principal_id FROM msdb.sys.database_principals 
            WHERE type in ('U','S','G') AND name = @principal_name

      IF (@principalid IS NULL)
      BEGIN
         RAISERROR(14607, -1, -1, 'principal')
         RETURN(4)
      END      
   END

   -- populate return variable
   SELECT @principal_sid = dbo.get_principal_sid(@principalid)

   RETURN(0) -- SUCCESS
go

PRINT ''
PRINT 'Creating procedure sysmail_verify_profile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_verify_profile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_verify_profile_sp
go

CREATE PROCEDURE dbo.sysmail_verify_profile_sp
   @profile_id int,
   @profile_name sysname,
   @allow_both_nulls bit,
   @allow_id_name_mismatch bit,
   @profileid int OUTPUT
AS
   IF @allow_both_nulls = 0
   BEGIN
      -- at least one parameter must be supplied
      IF (@profile_id IS NULL AND @profile_name IS NULL)
      BEGIN
         RAISERROR(14604, -1, -1, 'profile') 
         RETURN(1)
      END
   END
   
   IF ((@allow_id_name_mismatch = 0) AND (@profile_id IS NOT NULL AND @profile_name IS NOT NULL)) -- use both parameters
   BEGIN
      SELECT @profileid = profile_id FROM msdb.dbo.sysmail_profile WHERE profile_id=@profile_id AND name=@profile_name
      IF (@profileid IS NULL) -- id and name do not match
      BEGIN
         RAISERROR(14605, -1, -1, 'profile')
         RETURN(2)
      END      
   END
   ELSE IF (@profile_id IS NOT NULL) -- use id
   BEGIN
      SELECT @profileid = profile_id FROM msdb.dbo.sysmail_profile WHERE profile_id=@profile_id
      IF (@profileid IS NULL) -- id is invalid
      BEGIN
         RAISERROR(14606, -1, -1, 'profile')
         RETURN(3)
      END      
   END
   ELSE IF (@profile_name IS NOT NULL) -- use name
   BEGIN
      SELECT @profileid = profile_id FROM msdb.dbo.sysmail_profile WHERE name=@profile_name
      IF (@profileid IS NULL) -- name is invalid
      BEGIN
         RAISERROR(14607, -1, -1, 'profile')
         RETURN(4)
      END      
   END
   RETURN(0) -- SUCCESS
go

PRINT ''
PRINT 'Creating procedure sysmail_verify_account_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_verify_account_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_verify_account_sp
go

CREATE PROCEDURE dbo.sysmail_verify_account_sp
   @account_id int,
   @account_name sysname,
   @allow_both_nulls bit,
   @allow_id_name_mismatch bit,
   @accountid int OUTPUT
AS
   IF @allow_both_nulls = 0
   BEGIN
      -- at least one parameter must be supplied
      IF (@account_id IS NULL AND @account_name IS NULL)
      BEGIN
         RAISERROR(14604, -1, -1, 'account') 
         RETURN(1)
      END
   END
   
   IF ((@allow_id_name_mismatch = 0) AND (@account_id IS NOT NULL AND @account_name IS NOT NULL)) -- use both parameters
   BEGIN
      SELECT @accountid = account_id FROM msdb.dbo.sysmail_account WHERE account_id=@account_id AND name=@account_name
      IF (@accountid IS NULL) -- id and name do not match
      BEGIN
         RAISERROR(14605, -1, -1, 'account')
         RETURN(2)
      END      
   END
   ELSE IF (@account_id IS NOT NULL) -- use id
   BEGIN
      SELECT @accountid = account_id FROM msdb.dbo.sysmail_account WHERE account_id=@account_id
      IF (@accountid IS NULL) -- id is invalid
      BEGIN
         RAISERROR(14606, -1, -1, 'account')
         RETURN(3)
      END      
   END
   ELSE IF (@account_name IS NOT NULL) -- use name
   BEGIN
      SELECT @accountid = account_id FROM msdb.dbo.sysmail_account WHERE name=@account_name
      IF (@accountid IS NULL) -- name is invalid
      BEGIN
         RAISERROR(14607, -1, -1, 'account')
         RETURN(4)
      END      
   END
   RETURN(0) -- SUCCESS
go

PRINT ''
PRINT 'Creating procedure sysmail_add_profile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_add_profile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_add_profile_sp
go

CREATE PROCEDURE dbo.sysmail_add_profile_sp
   @profile_name sysname,
   @description nvarchar(256) = NULL,
   @profile_id int = NULL OUTPUT 
AS
   SET NOCOUNT ON

   -- insert new profile record, rely on primary key constraint to error out
   INSERT INTO msdb.dbo.sysmail_profile (name,description) VALUES (@profile_name, @description)
   
   -- fetch back profile_id
   SELECT @profile_id = profile_id FROM msdb.dbo.sysmail_profile WHERE name = @profile_name

   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_update_profile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_update_profile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_update_profile_sp
go

CREATE PROCEDURE dbo.sysmail_update_profile_sp
   @profile_id int = NULL, -- must provide either id or name
   @profile_name sysname = NULL,
   @description nvarchar(256) = NULL
AS
   SET NOCOUNT ON
  
   DECLARE @rc int
   DECLARE @profileid int
   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 0, 1, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)
   
   IF (@profile_name IS NOT NULL AND @description IS NOT NULL)
      UPDATE msdb.dbo.sysmail_profile 
      SET name=@profile_name, description = @description
      WHERE profile_id = @profileid
      
   ELSE IF (@profile_name IS NOT NULL)
      UPDATE msdb.dbo.sysmail_profile 
      SET name=@profile_name
      WHERE profile_id = @profileid

   ELSE IF (@description IS NOT NULL)
      UPDATE msdb.dbo.sysmail_profile 
      SET description = @description
      WHERE profile_id = @profileid
      
   ELSE
   BEGIN
      RAISERROR(14610, -1, -1)   
      RETURN(1)
   END

   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_delete_profile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_delete_profile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_delete_profile_sp
go

USE [msdb]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO


CREATE PROCEDURE [dbo].[sysmail_delete_profile_sp]
   @profile_id int = NULL, -- must provide either id or name
   @profile_name sysname = NULL,
   @force_delete BIT = 1
AS
   SET NOCOUNT ON
  
   DECLARE @rc int
   DECLARE @profileid int
   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 0, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)

IF(EXISTS (select * from sysmail_unsentitems WHERE 
   sysmail_unsentitems.profile_id = @profileid) AND @force_delete <> 1)
BEGIN
    IF(@profile_name IS NULL)
    BEGIN
        select @profile_name = name from dbo.sysmail_profile WHERE profile_id = @profileid
    END
    RAISERROR(14668, -1, -1, @profile_name)
    RETURN (1)   
END

UPDATE [msdb].[dbo].[sysmail_mailitems]
SET [sent_status] = 2, [sent_date] = getdate()
WHERE profile_id = @profileid AND sent_status <> 1
     
   DELETE FROM msdb.dbo.sysmail_profile 
   WHERE profile_id = @profileid
   RETURN(0)
GO

PRINT ''
PRINT 'Creating procedure sysmail_help_profile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_help_profile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_help_profile_sp
go

CREATE PROCEDURE dbo.sysmail_help_profile_sp
   @profile_id int = NULL,
   @profile_name sysname = NULL
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @profileid int
   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 1, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)

   IF (@profileid IS NOT NULL)
      SELECT profile_id, name, description 
      FROM msdb.dbo.sysmail_profile 
      WHERE profile_id = @profileid
      
   ELSE -- don't filter the output
      SELECT profile_id, name, description      
      FROM msdb.dbo.sysmail_profile 

   RETURN(0)
go


PRINT ''
PRINT 'Creating procedure sysmail_create_user_credential_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_create_user_credential_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_create_user_credential_sp
go

CREATE PROCEDURE dbo.sysmail_create_user_credential_sp
   @username      nvarchar(128),
   @password      nvarchar(128),
   @credential_id int            OUTPUT
AS
   SET NOCOUNT ON
   DECLARE @rc int
   DECLARE @credential_name UNIQUEIDENTIFIER
   DECLARE @credential_name_as_str varchar(40)
   DECLARE @sql NVARCHAR(max)

   -- create a GUID as the name for the credential
   SET @credential_name = newid()
   SET @credential_name_as_str = convert(varchar(40), @credential_name)
   SET @sql = N'CREATE CREDENTIAL [' + @credential_name_as_str
            + N'] WITH IDENTITY = ' + QUOTENAME(@username, '''')
            + N', SECRET = ' + QUOTENAME(ISNULL(@password, N''), '''')

   EXEC @rc = sp_executesql @statement = @sql
   IF(@rc <> 0)
      RETURN @rc

   SELECT @credential_id = credential_id 
   FROM sys.credentials
   WHERE name = convert(sysname, @credential_name)
    IF(@credential_id IS NULL)
   BEGIN
      RAISERROR(14616, -1, -1, @credential_name_as_str)
      RETURN 1
   END

   RETURN(0)
go



PRINT ''
PRINT 'Creating procedure sysmail_alter_user_credential_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_alter_user_credential_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_alter_user_credential_sp
go

CREATE PROCEDURE dbo.sysmail_alter_user_credential_sp
   @credential_name sysname,
   @username      nvarchar(128),
   @password      nvarchar(128)
AS
   SET NOCOUNT ON
   DECLARE @rc int
   DECLARE @sql NVARCHAR(max)

   -- alter credential DDL
   SET @sql = N'ALTER CREDENTIAL ' + QUOTENAME(@credential_name)
         + N' WITH IDENTITY = ' + QUOTENAME(@username, '''')
         + N', SECRET = ' + QUOTENAME(ISNULL(@password, N''), '''')

   EXEC @rc = sp_executesql @statement = @sql
   IF(@rc <> 0)
      RETURN @rc

   RETURN(0)
go


PRINT ''
PRINT 'Creating procedure sysmail_drop_user_credential_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_drop_user_credential_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_drop_user_credential_sp
go

CREATE PROCEDURE dbo.sysmail_drop_user_credential_sp
   @credential_name sysname
AS
   SET NOCOUNT ON
   DECLARE @rc int
   DECLARE @sql NVARCHAR(max)

   -- Drop credential DDL
   SET @sql = N'DROP CREDENTIAL ' + QUOTENAME(@credential_name)

   EXEC @rc = sp_executesql @statement = @sql
   IF(@rc <> 0)
      RETURN @rc

   RETURN(0)
go

         
PRINT ''
PRINT 'Creating procedure sysmail_add_account_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_add_account_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_add_account_sp
go

CREATE PROCEDURE dbo.sysmail_add_account_sp
   @account_name sysname,
   @email_address nvarchar(128),
   @display_name nvarchar(128) = NULL,
   @replyto_address nvarchar(128) = NULL,
   @description nvarchar(256) = NULL,  
   @mailserver_name sysname = NULL, -- the following fields are part of server definition
   @mailserver_type sysname = N'SMTP',
   @port int = 25,
   @username nvarchar(128) = NULL,
   @password nvarchar(128) = NULL,
   @use_default_credentials bit = 0,
   @enable_ssl bit = 0,
   @account_id int = NULL OUTPUT
AS
   SET NOCOUNT ON
   DECLARE @rc int
   DECLARE @credential_id int

   EXEC @rc = msdb.dbo.sysmail_verify_accountparams_sp
            @use_default_credentials = @use_default_credentials,
            @mailserver_type = @mailserver_type OUTPUT, -- validates and returns trimmed value
            @username = @username OUTPUT, -- returns trimmed value, NULL if empty
            @password = @password OUTPUT  -- returns trimmed value, NULL if empty
   IF(@rc <> 0)
      RETURN (1)

   EXEC @rc = msdb.dbo.sysmail_verify_addressparams_sp @address = @replyto_address, @parameter_name='@replyto_address'
   IF (@rc <> 0)
      RETURN @rc

   --transact this in case sysmail_create_user_credential_sp fails
   BEGIN TRANSACTION

   -- insert new account record, rely on primary key constraint to error out
   INSERT INTO msdb.dbo.sysmail_account (name,description,email_address,display_name,replyto_address) 
   VALUES (@account_name,@description,@email_address,@display_name,@replyto_address)
   IF (@@ERROR <> 0)
   BEGIN
      ROLLBACK TRANSACTION
      RETURN (2)
   END
   
   -- fetch back account_id
   SELECT @account_id = account_id FROM msdb.dbo.sysmail_account WHERE name = @account_name

   IF (@mailserver_name IS NULL) -- use local server as default
      SELECT @mailserver_name=@@SERVERNAME

   --create a credential in the credential store if a password needs to be stored
   IF(@username IS NOT NULL)
   BEGIN
      EXEC @rc = msdb.dbo.sysmail_create_user_credential_sp
                     @username,
                     @password,
                     @credential_id OUTPUT
      IF(@rc <> 0)
      BEGIN 
         ROLLBACK TRANSACTION
         RETURN (3)
      END
   END
   
   INSERT INTO msdb.dbo.sysmail_server (account_id,servertype,servername,port,username,credential_id,use_default_credentials,enable_ssl) 
   VALUES (@account_id,@mailserver_type,@mailserver_name,@port,@username,@credential_id,@use_default_credentials,@enable_ssl)
   IF (@@ERROR <> 0)
   BEGIN
      ROLLBACK TRANSACTION
      RETURN (4)
   END

   COMMIT TRANSACTION
   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_update_account_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_update_account_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_update_account_sp
go

USE [msdb]
GO
/****** Object:  StoredProcedure [dbo].[sysmail_update_account_sp]    Script Date: 06/26/2006 10:45:40 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO

CREATE PROCEDURE [dbo].[sysmail_update_account_sp]
   @account_id int = NULL, -- must provide either id or name
   @account_name sysname = NULL,
   @email_address nvarchar(128) = NULL,
   @display_name nvarchar(128) = NULL,
   @replyto_address nvarchar(128) = NULL,
   @description nvarchar(256) = NULL,
   @mailserver_name sysname = NULL,  
   @mailserver_type sysname = NULL,  
   @port int = NULL,
   @username sysname = NULL,
   @password sysname = NULL,
   @use_default_credentials bit = NULL,
   @enable_ssl bit = NULL,
   @timeout int = NULL,
   @no_credential_change bit = NULL  -- BOOL
  -- WITH EXECUTE AS OWNER --Allows access to sys.credentials
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @accountid int
   DECLARE @credential_id int
   DECLARE @credential_name sysname

   SELECT @no_credential_change = ISNULL(@no_credential_change, 0)

   exec @rc = msdb.dbo.sysmail_verify_account_sp @account_id, @account_name, 0, 1, @accountid OUTPUT
   IF @rc <> 0
      RETURN(1)

   IF(@email_address IS NULL)
   BEGIN
   SELECT @email_address = email_address FROM msdb.dbo.sysmail_account WHERE account_id=@accountid
   END


   IF(@display_name IS NULL)
   BEGIN
   SELECT @display_name = display_name FROM msdb.dbo.sysmail_account WHERE account_id=@accountid
   END

   IF(@replyto_address IS NULL)
   BEGIN
   SELECT @replyto_address = replyto_address FROM msdb.dbo.sysmail_account WHERE account_id=@accountid
   END

   EXEC @rc = msdb.dbo.sysmail_verify_addressparams_sp @address = @replyto_address, @parameter_name='@replyto_address' 
   IF (@rc <> 0)
      RETURN @rc

   IF(@description IS NULL)
   BEGIN
   SELECT @description = description FROM msdb.dbo.sysmail_account WHERE account_id=@accountid
   END


   IF(@port IS NULL)
   BEGIN
   SELECT @port = port FROM msdb.dbo.sysmail_server WHERE account_id=@accountid
   END

   IF(@use_default_credentials IS NULL)
   BEGIN
   SELECT @use_default_credentials = use_default_credentials FROM msdb.dbo.sysmail_server WHERE account_id=@accountid
   END

   IF(@enable_ssl IS NULL)
   BEGIN
   SELECT @enable_ssl = enable_ssl FROM msdb.dbo.sysmail_server WHERE account_id=@accountid
   END

   IF(@timeout IS NULL)
   BEGIN
   SELECT @timeout = timeout FROM msdb.dbo.sysmail_server WHERE account_id=@accountid
   END
   
   IF(@mailserver_type IS NULL)
   BEGIN
   SELECT @mailserver_type = servertype FROM msdb.dbo.sysmail_server WHERE account_id=@accountid
   END

 
   EXEC @rc = msdb.dbo.sysmail_verify_accountparams_sp 
            @use_default_credentials = @use_default_credentials,
            @mailserver_type = @mailserver_type OUTPUT, -- validates and returns trimmed value
            @username = @username OUTPUT, -- returns trimmed value
            @password = @password OUTPUT  -- returns empty string if @username is given and @password is null 
   IF(@rc <> 0)
      RETURN (1)

   --transact this in case credential updates fail
   BEGIN TRAN
   -- update account table
   IF (@account_name IS NOT NULL)
      IF (@email_address IS NOT NULL)
         UPDATE msdb.dbo.sysmail_account
         SET name=@account_name, description=@description, email_address=@email_address, display_name=@display_name, replyto_address=@replyto_address
         WHERE account_id=@accountid
      ELSE
         UPDATE msdb.dbo.sysmail_account
         SET name=@account_name, description=@description, display_name=@display_name, replyto_address=@replyto_address
         WHERE account_id=@accountid

   ELSE
      IF (@email_address IS NOT NULL)
         UPDATE msdb.dbo.sysmail_account
         SET description=@description, email_address=@email_address, display_name=@display_name, replyto_address=@replyto_address
         WHERE account_id=@accountid
      ELSE
         UPDATE msdb.dbo.sysmail_account
         SET description=@description, display_name=@display_name, replyto_address=@replyto_address
         WHERE account_id=@accountid
      
   -- see if a credential has been stored for this account
   SELECT @credential_name = name,
         @credential_id = c.credential_id
   FROM sys.credentials as c
     JOIN msdb.dbo.sysmail_server as ms
       ON c.credential_id = ms.credential_id
   WHERE account_id = @accountid 
     AND servertype = @mailserver_type

   --update the credential store
   IF((@credential_name IS NOT NULL) AND (@no_credential_change = 0))
   BEGIN
      --Remove the unneed credential
      IF(@username IS NULL)
      BEGIN
         SET @credential_id = NULL
         EXEC @rc = msdb.dbo.sysmail_drop_user_credential_sp 
                        @credential_name = @credential_name
      END
      -- Update the credential
      ELSE
      BEGIN
         EXEC @rc = msdb.dbo.sysmail_alter_user_credential_sp
                        @credential_name = @credential_name,
                        @username = @username,
                        @password = @password
      END

      IF(@rc <> 0)
      BEGIN 
         ROLLBACK TRAN
         RETURN (1)
      END
   END
   -- create a new credential if one doesn't exist
   ELSE IF(@credential_name IS NULL AND @username IS NOT NULL)
   BEGIN
      EXEC @rc = msdb.dbo.sysmail_create_user_credential_sp
                     @username = @username,
                     @password = @password,
                     @credential_id = @credential_id  OUTPUT
      IF(@rc <> 0)
      BEGIN 
         ROLLBACK TRAN
         RETURN (1)
      END
   END

   -- update server table
   IF (@no_credential_change = 0)
   BEGIN
   IF (@mailserver_name IS NOT NULL)
      UPDATE msdb.dbo.sysmail_server 
         SET servername=@mailserver_name, port=@port, username=@username, credential_id = @credential_id, use_default_credentials = @use_default_credentials, enable_ssl = @enable_ssl, timeout = @timeout
      WHERE account_id=@accountid AND servertype=@mailserver_type
   
   ELSE
      UPDATE msdb.dbo.sysmail_server 
         SET port=@port, username=@username, credential_id = @credential_id, use_default_credentials = @use_default_credentials, enable_ssl = @enable_ssl, timeout = @timeout
      WHERE account_id=@accountid AND servertype=@mailserver_type
   END
   ELSE
   BEGIN
      -- Since no_credential_change is true, @username is empty. Do not pass it.
      -- If we gave @username, sysmail_server would be set with the empty @username.
      IF (@mailserver_name IS NOT NULL)
         UPDATE msdb.dbo.sysmail_server 
         SET servername=@mailserver_name, port=@port, credential_id = @credential_id, use_default_credentials = @use_default_credentials, enable_ssl = @enable_ssl, timeout = @timeout
         WHERE account_id=@accountid AND servertype=@mailserver_type
   
      ELSE
         UPDATE msdb.dbo.sysmail_server 
         SET port=@port, credential_id = @credential_id, use_default_credentials = @use_default_credentials, enable_ssl = @enable_ssl, timeout = @timeout
         WHERE account_id=@accountid AND servertype=@mailserver_type
   END
      
   COMMIT TRAN
   RETURN(0)
go


PRINT ''
PRINT 'Creating procedure sysmail_delete_account_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_delete_account_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_delete_account_sp
go

CREATE PROCEDURE dbo.sysmail_delete_account_sp
   @account_id int = NULL, -- must provide either id or name
   @account_name sysname = NULL
AS
   SET NOCOUNT ON
  
   DECLARE @rc int
   DECLARE @accountid int
   DECLARE @credential_name sysname

   exec @rc = msdb.dbo.sysmail_verify_account_sp @account_id, @account_name, 0, 0, @accountid OUTPUT
   IF @rc <> 0
      RETURN(1)

   -- Get all the credentials has been stored for this account
   DECLARE cur CURSOR FOR
      SELECT c.name
      FROM sys.credentials as c
      JOIN msdb.dbo.sysmail_server as ms
         ON c.credential_id = ms.credential_id
      WHERE account_id = @accountid

   OPEN cur
   FETCH NEXT FROM cur INTO @credential_name
   WHILE @@FETCH_STATUS = 0
   BEGIN
      -- drop the credential
      EXEC msdb.dbo.sysmail_drop_user_credential_sp @credential_name = @credential_name

      FETCH NEXT FROM cur INTO @credential_name
   END

   CLOSE cur
   DEALLOCATE cur
     
   DELETE FROM msdb.dbo.sysmail_account
   WHERE account_id = @accountid
   
   RETURN(0)
go


PRINT ''
PRINT 'Creating procedure sysmail_help_account_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_help_account_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_help_account_sp
go

CREATE PROCEDURE dbo.sysmail_help_account_sp
   @account_id int = NULL,
   @account_name sysname = NULL
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @accountid int
   exec @rc = msdb.dbo.sysmail_verify_account_sp @account_id, @account_name, 1, 0, @accountid OUTPUT
   IF @rc <> 0
      RETURN(1)

   IF (@accountid IS NOT NULL)
      SELECT a.account_id, a.name, a.description, a.email_address, a.display_name, a.replyto_address, s.servertype, s.servername, s.port, s.username, s.use_default_credentials, s.enable_ssl 
      FROM msdb.dbo.sysmail_account a, msdb.dbo.sysmail_server s
      WHERE a.account_id = s.account_id AND a.account_id = @accountid
      
   ELSE
      SELECT a.account_id, a.name, a.description, a.email_address, a.display_name, a.replyto_address, s.servertype, s.servername, s.port, s.username, s.use_default_credentials, s.enable_ssl
      FROM msdb.dbo.sysmail_account a, msdb.dbo.sysmail_server s
      WHERE a.account_id = s.account_id

   RETURN(0)
go

-- Access to the complete account info. Required by databasemail.exe
PRINT ''
PRINT 'Creating procedure sysmail_help_admin_account_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_help_admin_account_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_help_admin_account_sp
go

CREATE PROCEDURE dbo.sysmail_help_admin_account_sp
   @account_id int
AS
   SET NOCOUNT ON

   DECLARE @rc         int,
      @acc_id         int,
      @name           sysname,
      @description    nvarchar(256),
      @email_address  nvarchar(128),
      @display_name   nvarchar(128),
      @replyto_address nvarchar(128),  
      @servertype     sysname,
      @servername     sysname,
      @port           int,
      @username       nvarchar(128),
      @passwordsize   int,  
      @cryptpassword  varbinary(1024),
      @credential_id  int,
      @use_default_credentials bit,
      @enable_ssl     bit,
      @timeout     int

    SET @passwordsize = 0

   EXEC @rc = msdb.dbo.sysmail_verify_account_sp @account_id, NULL, 1, 0, NULL
   IF @rc <> 0
      RETURN(1)

   SELECT 
      @acc_id         = a.account_id,
      @name           = a.name, 
      @description    = a.description, 
      @email_address  = a.email_address, 
      @display_name   = a.display_name, 
      @replyto_address= a.replyto_address,
      @servertype     = s.servertype, 
      @servername     = s.servername, 
      @port           = s.port, 
      @username       = s.username,
      @credential_id  = s.credential_id,
      @use_default_credentials = s.use_default_credentials,
      @enable_ssl     = s.enable_ssl,
      @timeout        = s.timeout
   FROM msdb.dbo.sysmail_account a, msdb.dbo.sysmail_server s
   WHERE (a.account_id = s.account_id) AND 
      (a.account_id = @account_id)
    
    --get the encrypted password if required 
    IF(@username IS NOT NULL)
    BEGIN
        DECLARE @cred TABLE([size] INT, blob VARBINARY(1024));

        INSERT @cred
        EXEC @rc = master.dbo.sp_PostAgentInfo @credential_id
        IF @rc <> 0
        BEGIN
          RETURN(1)
        END
        
        SELECT @passwordsize = [size], @cryptpassword = [blob] 
        FROM @cred
    END
    
    --All done return result
    SELECT
        @acc_id         as 'account_id',
        @name           as 'name',  
        @description    as 'description',
        @email_address  as 'email_address',
        @display_name   as 'display_name',
        @replyto_address as 'replyto_address',
        @servertype     as 'servertype',
        @servername     as 'servername',
        @port           as 'port',
        @username       as 'username',
        @passwordsize   as 'password_size',
        @cryptpassword  as 'password_crypt',
        @use_default_credentials as 'use_default_credentials',
        @enable_ssl     as 'enable_ssl',
        @timeout        as 'timeout'

   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_add_profileaccount_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_add_profileaccount_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_add_profileaccount_sp
go

CREATE PROCEDURE dbo.sysmail_add_profileaccount_sp
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL,
   @account_id int = NULL, -- must provide id or name
   @account_name sysname = NULL,
   @sequence_number int -- account with the lowest sequence number is picked as default
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @profileid int
   DECLARE @accountid int

   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 0, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)

   exec @rc = msdb.dbo.sysmail_verify_account_sp @account_id, @account_name, 0, 0, @accountid OUTPUT
   IF @rc <> 0
      RETURN(2)

   -- insert new account record, rely on primary key constraint to error out
   INSERT INTO msdb.dbo.sysmail_profileaccount (profile_id,account_id,sequence_number)
   VALUES (@profileid,@accountid,@sequence_number)
   
   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_update_profileaccount_sp...'
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sysmail_update_profileaccount_sp')
              AND (type = 'P')))
  DROP PROCEDURE dbo.sysmail_update_profileaccount_sp
go

CREATE PROCEDURE dbo.sysmail_update_profileaccount_sp
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL,
   @account_id int = NULL, -- must provide id or name
   @account_name sysname = NULL,
   @sequence_number int -- account with the lowest sequence number is picked as default
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @profileid int
   DECLARE @accountid int

   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 0, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)

   exec @rc = msdb.dbo.sysmail_verify_account_sp @account_id, @account_name, 0, 0, @accountid OUTPUT
   IF @rc <> 0
      RETURN(2)

   IF (@sequence_number IS NULL)
   BEGIN
      RAISERROR(14611, -1, -1)   
      RETURN(3)
   END
   
   UPDATE msdb.dbo.sysmail_profileaccount
   SET sequence_number=@sequence_number
   WHERE profile_id=@profileid AND account_id=@accountid
   
   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_delete_profileaccount_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_delete_profileaccount_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_delete_profileaccount_sp
go

CREATE PROCEDURE dbo.sysmail_delete_profileaccount_sp
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL,
   @account_id int = NULL, -- must provide id or name
   @account_name sysname = NULL
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @profileid int
   DECLARE @accountid int

   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 1, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)

   exec @rc = msdb.dbo.sysmail_verify_account_sp @account_id, @account_name, 1, 0, @accountid OUTPUT
   IF @rc <> 0
      RETURN(2)

   IF (@profileid IS NOT NULL AND @accountid IS NOT NULL) -- both parameters supplied for deletion
      DELETE FROM msdb.dbo.sysmail_profileaccount
      WHERE profile_id=@profileid AND account_id=@accountid

   ELSE IF (@profileid IS NOT NULL) -- profile id is supplied
      DELETE FROM msdb.dbo.sysmail_profileaccount
      WHERE profile_id=@profileid

   ELSE IF (@accountid IS NOT NULL) -- account id is supplied
      DELETE FROM msdb.dbo.sysmail_profileaccount
      WHERE account_id=@accountid

   ELSE -- no parameters are supplied for deletion
   BEGIN
      RAISERROR(14608, -1, -1, 'profile', 'account')  
      RETURN(3)   
   END

   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_help_profileaccount_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_help_profileaccount_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_help_profileaccount_sp
go

CREATE PROCEDURE dbo.sysmail_help_profileaccount_sp
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL,
   @account_id int = NULL, -- must provide id or name
   @account_name sysname = NULL
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @profileid int
   DECLARE @accountid int

   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 1, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)

   exec @rc = msdb.dbo.sysmail_verify_account_sp @account_id, @account_name, 1, 0, @accountid OUTPUT
   IF @rc <> 0
      RETURN(2)

   IF (@profileid IS NOT NULL AND @accountid IS NOT NULL)
      SELECT p.profile_id,profile_name=p.name,a.account_id,account_name=a.name,c.sequence_number
      FROM msdb.dbo.sysmail_profile p, msdb.dbo.sysmail_account a, msdb.dbo.sysmail_profileaccount c
      WHERE p.profile_id=c.profile_id AND a.account_id=c.account_id AND c.profile_id=@profileid AND c.account_id=@accountid
   
   ELSE IF (@profileid IS NOT NULL)
      SELECT p.profile_id,profile_name=p.name,a.account_id,account_name=a.name,c.sequence_number
      FROM msdb.dbo.sysmail_profile p, msdb.dbo.sysmail_account a, msdb.dbo.sysmail_profileaccount c
      WHERE p.profile_id=c.profile_id AND a.account_id=c.account_id AND c.profile_id=@profileid

   ELSE IF (@accountid IS NOT NULL)
      SELECT p.profile_id,profile_name=p.name,a.account_id,account_name=a.name,c.sequence_number
      FROM msdb.dbo.sysmail_profile p, msdb.dbo.sysmail_account a, msdb.dbo.sysmail_profileaccount c
      WHERE p.profile_id=c.profile_id AND a.account_id=c.account_id AND c.account_id=@accountid

   ELSE
      SELECT p.profile_id,profile_name=p.name,a.account_id,account_name=a.name,c.sequence_number
      FROM msdb.dbo.sysmail_profile p, msdb.dbo.sysmail_account a, msdb.dbo.sysmail_profileaccount c
      WHERE p.profile_id=c.profile_id AND a.account_id=c.account_id
      
   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_configure_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_configure_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_configure_sp
go

CREATE PROCEDURE dbo.sysmail_configure_sp
   @parameter_name nvarchar(256),
   @parameter_value nvarchar(256),
   @description nvarchar(256) = NULL
AS
   SET NOCOUNT ON
   
   IF (@description IS NOT NULL)
      UPDATE msdb.dbo.sysmail_configuration
      SET paramvalue=@parameter_value, description=@description
      WHERE paramname=@parameter_name
   ELSE
      UPDATE msdb.dbo.sysmail_configuration
      SET paramvalue=@parameter_value
      WHERE paramname=@parameter_name

   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_help_configure_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_help_configure_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_help_configure_sp
go

CREATE PROCEDURE dbo.sysmail_help_configure_sp
   @parameter_name nvarchar(256) = NULL
AS
   SET NOCOUNT ON

    SELECT paramname, paramvalue, description
    FROM msdb.dbo.sysmail_configuration
    WHERE paramname = ISNULL(@parameter_name, paramname)

    RETURN(0)
go


PRINT ''
PRINT 'Creating procedure sysmail_help_configure_value_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_help_configure_value_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_help_configure_value_sp
go

CREATE PROCEDURE dbo.sysmail_help_configure_value_sp
   @parameter_name nvarchar(256),
   @parameter_value nvarchar(256) OUTPUT
AS
   SET NOCOUNT ON
   SET @parameter_value = NULL
    
   IF (@parameter_name IS NULL)
   BEGIN
      RAISERROR(14618, 16, 1, '@parameter_name')
      RETURN(1)   
   END

    SELECT @parameter_value = paramvalue
    FROM msdb.dbo.sysmail_configuration
    WHERE paramname = @parameter_name

   RETURN(0)
go


PRINT ''
PRINT 'Creating procedure sysmail_add_principalprofile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_add_principalprofile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_add_principalprofile_sp
go

CREATE PROCEDURE dbo.sysmail_add_principalprofile_sp
   @principal_id int = NULL, -- must provide id or name
   @principal_name sysname = NULL,
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL,
   @is_default bit
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @principal_sid varbinary(85)
   DECLARE @profileid int

   IF (@principal_id IS NOT NULL AND @principal_id = 0) OR (@principal_name IS NOT NULL AND @principal_name = N'public')
   BEGIN
      IF (@principal_id IS NOT NULL AND @principal_id <> 0) OR (@principal_name IS NOT NULL AND @principal_name <> N'public')
      BEGIN
         RAISERROR(14605, -1, -1, 'principal')  -- id and name do not match
      END
      SET @principal_sid = 0x00 -- public
   END
   ELSE
   BEGIN
      exec @rc = msdb.dbo.sysmail_verify_principal_sp @principal_id, @principal_name, 0, @principal_sid OUTPUT
      IF @rc <> 0
         RETURN(2)
   END
   
   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 0, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(3)

   -- insert new account record, rely on primary key constraint to error out
   INSERT INTO msdb.dbo.sysmail_principalprofile (principal_sid,profile_id,is_default)
   VALUES (@principal_sid,@profileid,@is_default)

   IF (@is_default IS NOT NULL AND @is_default = 1 )
   BEGIN
      -- a principal can only have one default profile so reset other, if there are any
      UPDATE msdb.dbo.sysmail_principalprofile
      SET is_default=0
      WHERE principal_sid = @principal_sid AND profile_id <> @profileid
   END
   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_update_principalprofile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_update_principalprofile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_update_principalprofile_sp
go

CREATE PROCEDURE dbo.sysmail_update_principalprofile_sp
   @principal_id int = NULL, -- must provide id or name
   @principal_name sysname = NULL,
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL,
   @is_default bit
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @principal_sid varbinary(85)
   DECLARE @profileid int

   IF (@principal_id IS NOT NULL AND @principal_id = 0) OR (@principal_name IS NOT NULL AND @principal_name = N'public')
   BEGIN
      IF (@principal_id IS NOT NULL AND @principal_id <> 0) OR (@principal_name IS NOT NULL AND @principal_name <> N'public')
      BEGIN
         RAISERROR(14605, -1, -1, 'principal')  -- id and name do not match
      END
      SET @principal_sid = 0x00 -- public
   END
   ELSE
   BEGIN
      exec @rc = msdb.dbo.sysmail_verify_principal_sp @principal_id, @principal_name, 0, @principal_sid OUTPUT
      IF @rc <> 0
         RETURN(1)
   END
   
   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 0, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(2)

   UPDATE msdb.dbo.sysmail_principalprofile
   SET is_default=@is_default
   WHERE principal_sid = @principal_sid AND profile_id = @profileid

   IF (@is_default IS NOT NULL AND @is_default = 1)
   BEGIN
      -- a principal can only have one default profile so reset others (if there are any)
      UPDATE msdb.dbo.sysmail_principalprofile
      SET is_default=0
      WHERE principal_sid = @principal_sid AND profile_id <> @profileid
   END

   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_delete_principalprofile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_delete_principalprofile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_delete_principalprofile_sp
go

CREATE PROCEDURE dbo.sysmail_delete_principalprofile_sp
   @principal_id int = NULL, -- must provide id or name
   @principal_name sysname = NULL,
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @principal_sid varbinary(85)
   DECLARE @profileid int

   IF (@principal_id IS NOT NULL AND @principal_id = 0) OR (@principal_name IS NOT NULL AND @principal_name = N'public')
   BEGIN
      IF (@principal_id IS NOT NULL AND @principal_id <> 0) OR (@principal_name IS NOT NULL AND @principal_name <> N'public')
      BEGIN
         RAISERROR(14605, -1, -1, 'principal')  -- id and name do not match
      END
      SET @principal_sid = 0x00 -- public
   END
   ELSE
   BEGIN
      IF (@principal_id IS NOT NULL OR @principal_name IS NOT NULL) 
      BEGIN
         exec @rc = msdb.dbo.sysmail_verify_principal_sp @principal_id, @principal_name, 1, @principal_sid OUTPUT
         IF @rc <> 0
            RETURN(1)
      END
   END
   
   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 1, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(2)

   IF ((@principal_id IS NOT NULL OR @principal_name IS NOT NULL) AND @profileid IS NOT NULL)
   BEGIN
      DELETE FROM msdb.dbo.sysmail_principalprofile WHERE principal_sid=@principal_sid AND profile_id = @profileid
   END
   ELSE IF (@principal_id IS NOT NULL OR @principal_name IS NOT NULL)
   BEGIN
      DELETE FROM msdb.dbo.sysmail_principalprofile WHERE principal_sid=@principal_sid
   END
   ELSE IF (@profileid IS NOT NULL)
   BEGIN
      DELETE FROM msdb.dbo.sysmail_principalprofile WHERE profile_id = @profileid
   END
   ELSE -- no parameters are supplied for deletion
   BEGIN
      RAISERROR(14608, -1, -1, 'principal', 'profile') 
      RETURN(6)
   END
   
   RETURN(0)
go

PRINT ''
PRINT 'Creating procedure sysmail_help_principalprofile_sp...'
IF (NOT OBJECT_ID(N'dbo.sysmail_help_principalprofile_sp', 'P') IS NULL)
  DROP PROCEDURE dbo.sysmail_help_principalprofile_sp
go

CREATE PROCEDURE dbo.sysmail_help_principalprofile_sp
   @principal_id int = NULL, -- must provide id or name
   @principal_name sysname = NULL,
   @profile_id int = NULL, -- must provide id or name
   @profile_name sysname = NULL
AS
   SET NOCOUNT ON

   DECLARE @rc int
   DECLARE @principal_sid varbinary(85)
   DECLARE @profileid int

   exec @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id, @profile_name, 1, 0, @profileid OUTPUT
   IF @rc <> 0
      RETURN(1)

   IF (@principal_id IS NOT NULL AND @principal_id = 0) OR (@principal_name IS NOT NULL AND @principal_name = N'public')
   BEGIN
      IF (@principal_id IS NOT NULL AND @principal_id <> 0) OR (@principal_name IS NOT NULL AND @principal_name <> N'public')
      BEGIN
         RAISERROR(14605, -1, -1, 'principal')  -- id and name do not match
      END
      SET @principal_sid = 0x00 -- public

      IF (@profileid IS NOT NULL)
      BEGIN
         SELECT principal_id=0, 
                principal_name = N'public', 
                prof.profile_id, 
                profile_name=prof.name, 
                prin.is_default
         FROM msdb.dbo.sysmail_principalprofile prin, msdb.dbo.sysmail_profile prof 
         WHERE prin.profile_id=prof.profile_id AND 
               prin.principal_sid = @principal_sid AND 
               prof.profile_id=@profileid
      END
      ELSE
      BEGIN
         SELECT principal_id=0, 
                principal_name = N'public', 
                prof.profile_id, 
                profile_name=prof.name, 
                prin.is_default
         FROM msdb.dbo.sysmail_principalprofile prin, msdb.dbo.sysmail_profile prof 
         WHERE prin.profile_id=prof.profile_id AND 
               prin.principal_sid = @principal_sid
      END
   END
   ELSE -- non-public profiles
   BEGIN
      IF (@principal_id IS NOT NULL OR @principal_name IS NOT NULL)      
      BEGIN
            exec @rc = msdb.dbo.sysmail_verify_principal_sp @principal_id, @principal_name, 1, @principal_sid OUTPUT
            IF @rc <> 0
               RETURN(2)
      END

      IF ((@principal_id IS NOT NULL OR @principal_name IS NOT NULL) AND @profileid IS NOT NULL)
      BEGIN
            SELECT principal_id=dbprin.principal_id,
                   principal_name=dbprin.name,
                   prof.profile_id,
                   profile_name=prof.name,
                   prinprof.is_default
            FROM sys.database_principals dbprin, msdb.dbo.sysmail_principalprofile prinprof, msdb.dbo.sysmail_profile prof
            WHERE dbprin.principal_id = dbo.get_principal_id(prinprof.principal_sid) AND
                  (prinprof.principal_sid = @principal_sid OR prinprof.principal_sid = 0x00) AND
                  prof.profile_id = prinprof.profile_id AND
                  prinprof.profile_id = @profileid
      END
      ELSE IF (@principal_id IS NOT NULL OR @principal_name IS NOT NULL)
      BEGIN
            SELECT principal_id=dbprin.principal_id,
                   principal_name=dbprin.name,
                   prof.profile_id,
                   profile_name=prof.name,
                   prinprof.is_default
            FROM sys.database_principals dbprin, msdb.dbo.sysmail_principalprofile prinprof, msdb.dbo.sysmail_profile prof
            WHERE dbprin.principal_id = dbo.get_principal_id(prinprof.principal_sid) AND
                  (prinprof.principal_sid = @principal_sid OR prinprof.principal_sid = 0x00) AND
                  prof.profile_id = prinprof.profile_id
      END
      ELSE IF (@profileid IS NOT NULL)
      BEGIN
            SELECT principal_id=dbprin.principal_id,
                   principal_name=dbprin.name,
                   prof.profile_id,
                   profile_name=prof.name,
                   prinprof.is_default
            FROM sys.database_principals dbprin, msdb.dbo.sysmail_principalprofile prinprof, msdb.dbo.sysmail_profile prof
            WHERE dbprin.principal_id = dbo.get_principal_id(prinprof.principal_sid) AND
                  prof.profile_id = prinprof.profile_id AND
                  prinprof.profile_id = @profileid
      END
      ELSE -- no parameters are supplied for filtering
      BEGIN
            SELECT principal_id=dbprin.principal_id,
                   principal_name=dbprin.name,
                   prof.profile_id,
                   profile_name=prof.name,
                   prinprof.is_default
            FROM sys.database_principals dbprin, msdb.dbo.sysmail_principalprofile prinprof, msdb.dbo.sysmail_profile prof
            WHERE dbprin.principal_id = dbo.get_principal_id(prinprof.principal_sid) AND
                  prof.profile_id = prinprof.profile_id
      END
   END
   RETURN(0)
go

-----------------------------------------------------------
-- Database Mail: mail host database stored procedures
-----------------------------------------------------------

-----------------------------------------------------------
-- procedure sysmail_logmailevent_sp
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sysmail_logmailevent_sp', 'P') IS NULL
    DROP PROCEDURE dbo.sysmail_logmailevent_sp
GO
-----
PRINT 'Creating sysmail_logmailevent_sp'
-----
GO
-- sysmail_logmailevent_sp : inserts an entry in the sysmail_log table
CREATE PROCEDURE sysmail_logmailevent_sp
    @event_type     INT,
    @description    NVARCHAR(max)   = NULL, 
    @process_id     INT             = NULL,
    @mailitem_id    INT             = NULL,
    @account_id     INT             = NULL
AS
    SET NOCOUNT ON

    --Try and get the optional logging level for the DatabaseMail process
    DECLARE @loggingLevel nvarchar(256)
    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'LoggingLevel', 
                                                  @parameter_value = @loggingLevel OUTPUT

    DECLARE @loggingLevelInt int   
    SET @loggingLevelInt = dbo.ConvertToInt(@loggingLevel, 3, 2) 

    IF (@event_type = 3) OR                           -- error
       (@event_type = 2 AND @loggingLevelInt >= 2) OR -- warning with extended logging
       (@event_type = 1 AND @loggingLevelInt >= 2) OR -- info with extended logging
       (@event_type = 0 AND @loggingLevelInt >= 3)    -- success with verbose logging
    BEGIN
       INSERT sysmail_log(event_type, description, process_id, mailitem_id, account_id) 
       VALUES(@event_type, @description, @process_id , @mailitem_id, @account_id)
    END
RETURN 0
GO

-----------------------------------------------------------
-- procedure sysmail_start_sp
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sysmail_start_sp', 'P') IS NULL
    DROP PROCEDURE dbo.sysmail_start_sp
GO
-----
PRINT 'Creating sysmail_start_sp'
-----
GO
-- sysmail_start_sp : allows databasemail to process mail from the queue 
CREATE PROCEDURE sysmail_start_sp
AS
    SET NOCOUNT ON
    DECLARE @rc INT 
   DECLARE @localmessage nvarchar(255)

    ALTER QUEUE ExternalMailQueue WITH STATUS = ON
    SELECT @rc = @@ERROR
    IF(@rc = 0)
    BEGIN
      ALTER QUEUE ExternalMailQueue WITH ACTIVATION (STATUS = ON);
       SET @localmessage = FORMATMESSAGE(14639, SUSER_SNAME())
       exec msdb.dbo.sysmail_logmailevent_sp @event_type=1, @description=@localmessage
    END
RETURN @rc
GO

-----------------------------------------------------------
-- procedure sysmail_stop_sp
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sysmail_stop_sp', 'P') IS NULL
    DROP PROCEDURE dbo.sysmail_stop_sp
GO
-----
PRINT 'Creating sysmail_stop_sp'
-----
GO
-- sysmail_stop_sp : stops the DatabaseMail process. Mail items remain in the queue until sqlmail started 
CREATE PROCEDURE sysmail_stop_sp
AS
    SET NOCOUNT ON
    DECLARE @rc INT
   DECLARE @localmessage nvarchar(255)
  
    ALTER QUEUE ExternalMailQueue WITH ACTIVATION (STATUS = OFF);
    SELECT @rc = @@ERROR
    IF(@rc = 0)
    BEGIN
       ALTER QUEUE ExternalMailQueue WITH STATUS = OFF;
       SELECT @rc = @@ERROR
       IF(@rc = 0)
       BEGIN
          SET @localmessage = FORMATMESSAGE(14640, SUSER_SNAME())
          exec msdb.dbo.sysmail_logmailevent_sp @event_type=1, @description=@localmessage
       END
    END
RETURN @rc
GO


-----------------------------------------------------------
-- procedure sysmail_help_status_sp
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sysmail_help_status_sp', 'P') IS NULL
    DROP PROCEDURE dbo.sysmail_help_status_sp
GO
-----
PRINT 'Creating sysmail_help_status_sp'
-----
GO
CREATE PROCEDURE sysmail_help_status_sp
  WITH EXECUTE AS 'dbo'
AS
BEGIN
    IF NOT EXISTS (SELECT * FROM sys.service_queues WHERE name = N'ExternalMailQueue' AND is_receive_enabled = 1)
       SELECT 'STOPPED' AS Status
    ELSE
       SELECT 'STARTED' AS Status
END
GO


-----------------------------------------------------------
-- procedure sysmail_help_queue_sp
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sysmail_help_queue_sp', 'P') IS NULL
    DROP PROCEDURE dbo.sysmail_help_queue_sp
GO
-----
PRINT 'Creating sysmail_help_queue_sp'
-----
GO
CREATE PROCEDURE sysmail_help_queue_sp
   @queue_type nvarchar(6) = NULL -- Type of queue
AS
BEGIN

   SELECT @queue_type       = LTRIM(RTRIM(@queue_type))
   IF @queue_type           = '' SELECT @queue_type = NULL

   IF ( (@queue_type IS NOT NULL) AND
         (LOWER(@queue_type collate SQL_Latin1_General_CP1_CS_AS) NOT IN ( N'mail', N'status') ) )
   BEGIN
        RAISERROR(14266, -1, -1, '@queue_type', 'mail, status')
      RETURN(1) -- Failure
   END   

   DECLARE @depth      int
   DECLARE @temp TABLE (
                        queue_type nvarchar(6),
                        length INT NOT NULL, 
                        state nvarchar(64), 
                        last_empty_rowset_time DATETIME,
                        last_activated_time DATETIME
                       )
  
   IF ( (@queue_type IS NULL) OR (LOWER(@queue_type collate SQL_Latin1_General_CP1_CS_AS) = N'mail' ) )
   BEGIN
      SET @depth = (SELECT COUNT(*) FROM ExternalMailQueue WITH (NOWAIT NOLOCK READUNCOMMITTED))

      INSERT INTO @temp
         SELECT 
                N'mail',
                @depth, 
            qm.state as state,
            qm.last_empty_rowset_time as last_empty_rowset_time,
            qm.last_activated_time as last_activated_time
         FROM sys.dm_broker_queue_monitors qm
         JOIN sys.service_queues sq ON sq.object_id = qm.queue_id
         WHERE sq.name = 'ExternalMailQueue'
   END

   IF ( (@queue_type IS NULL) OR (LOWER(@queue_type collate SQL_Latin1_General_CP1_CS_AS) = N'status' ) )
   BEGIN
      SET @depth = (SELECT COUNT(*) FROM InternalMailQueue WITH (NOWAIT NOLOCK READUNCOMMITTED))

      INSERT INTO @temp
         SELECT 
                N'status',
                @depth, 
            qm.state as state,
            qm.last_empty_rowset_time as last_empty_rowset_time,
            qm.last_activated_time as last_activated_time
         FROM sys.dm_broker_queue_monitors qm
         JOIN sys.service_queues sq ON sq.object_id = qm.queue_id
         WHERE sq.name = 'InternalMailQueue'
   END

   SELECT * from @temp
END
GO

-----------------------------------------------------------
-- procedure sp_SendMailMessage
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_SendMailMessage', 'P') IS NULL
    DROP PROCEDURE dbo.sp_SendMailMessage
GO
-----
PRINT 'Creating sp_SendMailMessage'
-----
GO
-- sp_SendMailMessage : Sends a request on the mail items SSB queue
CREATE PROCEDURE sp_SendMailMessage
    @contract_name sysname, -- Name of contract
    @message_type sysname,  -- Type of message
    @request varchar(max) -- XML message to send
  WITH EXECUTE AS 'dbo'
AS

SET NOCOUNT ON

DECLARE @conversationHandle uniqueidentifier;
DECLARE @error int

-- Start a conversation with the remote service
BEGIN DIALOG  @conversationHandle
    FROM SERVICE    [InternalMailService]
    TO SERVICE      'ExternalMailService'
    ON CONTRACT     @contract_name
    WITH ENCRYPTION=OFF

-- Check error
SET @error = @@ERROR
IF @error <> 0
BEGIN
    RETURN @error
END

-- Send message
;SEND ON CONVERSATION @conversationHandle MESSAGE TYPE @message_type (@request)

-- Check error
SET @error = @@ERROR
IF @error <> 0
BEGIN
    RETURN @error
END

RETURN 0
GO

-----------------------------------------------------------
-- procedure sp_isprohibited
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_isprohibited', 'P') IS NULL
    DROP PROCEDURE dbo.sp_isprohibited
GO
-----
PRINT 'Creating sp_isprohibited'
-----
GO
-- sp_isprohibited : To test if the attachment is prohibited or not.
--
CREATE PROCEDURE sp_isprohibited
   @attachment nvarchar(max),
   @prohibitedextensions nvarchar(1000)
AS

DECLARE @extensionIndex int
DECLARE @extensionName nvarchar(255)

IF (@attachment IS NOT NULL AND LEN(@attachment) > 0) 
BEGIN
    SET @prohibitedextensions = UPPER(@prohibitedextensions)

   -- find @extensionName: the substring between the last '.' and the end of the string
   SET @extensionIndex = 0
   WHILE (1=1)
   BEGIN
      DECLARE @lastExtensionIndex int
      SET @lastExtensionIndex = CHARINDEX('.', @attachment, @extensionIndex+1)
      IF (@lastExtensionIndex = 0)
         BREAK
      SET @extensionIndex = @lastExtensionIndex
   END

   IF (@extensionIndex > 0)
   BEGIN
      SET @extensionName = SUBSTRING(@attachment, @extensionIndex + 1, (LEN(@attachment) - @extensionIndex)) 
      SET @extensionName = UPPER(@extensionName)

      -- compare @extensionName with each extension in the comma-separated @prohibitedextensions list
      DECLARE @currentExtensionStart int
      DECLARE @currentExtensionEnd int

      SET @currentExtensionStart = 0
      SET @currentExtensionEnd = 0
      WHILE (@currentExtensionEnd < LEN(@prohibitedextensions))
      BEGIN
         SET @currentExtensionEnd = CHARINDEX(',', @prohibitedextensions, @currentExtensionStart)

         IF (@currentExtensionEnd = 0) -- we have reached the last extension of the list, or the list was empty
            SET @currentExtensionEnd = LEN(@prohibitedextensions)+1

         DECLARE @prohibitedExtension nvarchar(1000)
         SET @prohibitedExtension = SUBSTRING(@prohibitedextensions, @currentExtensionStart, @currentExtensionEnd - @currentExtensionStart) 
         SET @prohibitedExtension = RTRIM(LTRIM(@prohibitedExtension))

         IF( @extensionName = @prohibitedExtension )
            RETURN 1

         SET @currentExtensionStart = @currentExtensionEnd + 1
      END
   END

   RETURN 0
END
GO

-----------------------------------------------------------
-- procedure sp_SendMailQueues
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_SendMailQueues', 'P') IS NULL
    DROP PROCEDURE dbo.sp_SendMailQueues
GO
-----
PRINT 'Creating sp_SendMailQueues'
-----
GO
-- sp_SendMailQueues : Writes a send mail request to the queue.
--
CREATE  PROCEDURE sp_SendMailQueues
    @message_data      varchar(max) -- The request in XML
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @contract_name nvarchar(128)
    DECLARE @message_type nvarchar(128)
    DECLARE @retValue int

    SET @message_type = '{//www.microsoft.com/databasemail/messages}SendMail'
    SET @contract_name = '//www.microsoft.com/databasemail/contracts/SendMail/v1.0'

    --Writes the message to the queue
    EXEC @retValue = sp_SendMailMessage @contract_name, @message_type, @message_data

    RETURN @retValue
END
GO


-----------------------------------------------------------
-- procedure sp_ProcessResponse
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_ProcessResponse', 'P') IS NULL
    DROP PROCEDURE dbo.sp_ProcessResponse
GO
-----
PRINT 'Creating sp_ProcessResponse'
-----
USE [msdb]
GO
SET ANSI_NULLS ON
GO
-- Turn on QUOTED IDENTIFIER to compile this stored proc that uses XQuery
SET QUOTED_IDENTIFIER ON


GO
-- Processes responses from dbmail 
--
CREATE PROCEDURE [dbo].[sp_ProcessResponse]
    @conv_handle        uniqueidentifier,
    @message_type_name  NVARCHAR(256),
    @xml_message_body   NVARCHAR(max)
AS
BEGIN
    DECLARE 
        @mailitem_id        INT,
        @sent_status        INT,
        @rc                 INT,
        @index              INT,
        @processId          INT,
        @sent_date          DATETIME,
        @localmessage       NVARCHAR(max),
        @LogMessage         NVARCHAR(max),
        @retry_hconv        uniqueidentifier,
        @paramStr           NVARCHAR(256),
        @accRetryDelay      INT

    --------------------------
    --Always send the response 
    ;SEND ON CONVERSATION @conv_handle MESSAGE TYPE @message_type_name (@xml_message_body)
    --
    -- Need to handle the case where a sent retry is requested. 
    -- This is done by setting a conversation timer, The timer with go off in the external queue

    --  $ISSUE: 325439 - DBMail: Use XML type  for all xml document handling in DBMail stored procs
    DECLARE @xmlblob xml
    SET  @xmlblob = CONVERT(xml, @xml_message_body)

    SELECT  @mailitem_id = MailResponses.Properties.value('(MailItemId/@Id)[1]', 'int'),
          @sent_status = MailResponses.Properties.value('(SentStatus/@Status)[1]', 'int')
    FROM @xmlblob.nodes('
    declare namespace responses="http://schemas.microsoft.com/databasemail/responses";
    /responses:SendMail') 
    AS MailResponses(Properties) 

    IF(@mailitem_id IS NULL OR @sent_status IS NULL)
    BEGIN  
        --Log error and continue.
        SET @localmessage = FORMATMESSAGE(14652, CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage

        GOTO ErrorHandler;
    END      

    -- Update the status of the email item
    UPDATE msdb.dbo.sysmail_mailitems
    SET sent_status = @sent_status
    WHERE mailitem_id = @mailitem_id

    --
    -- A send retry has been requested. Set a conversation timer
    IF(@sent_status = 3)
    BEGIN
        -- Get the associated mail item data for the given @conversation_handle (if it exists)
       SELECT @retry_hconv = conversation_handle
       FROM sysmail_send_retries as sr
            RIGHT JOIN sysmail_mailitems as mi
            ON sr.mailitem_id = mi.mailitem_id
       WHERE mi.mailitem_id = @mailitem_id

        --Must be the first retry attempt. Create a sysmail_send_retries record to track retries
        IF(@retry_hconv IS NULL)
        BEGIN
            INSERT sysmail_send_retries(conversation_handle, mailitem_id) --last_send_attempt_date
            VALUES(@conv_handle, @mailitem_id)
        END
        ELSE
        BEGIN
            --Update existing retry record
            UPDATE sysmail_send_retries
            SET last_send_attempt_date = GETDATE(),
                send_attempts = send_attempts + 1
            WHERE mailitem_id = @mailitem_id

        END

        --Get the global retry delay time
        EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'AccountRetryDelay', 
                                                    @parameter_value = @paramStr OUTPUT
        --ConvertToInt will return the default if @paramStr is null
        SET @accRetryDelay = dbo.ConvertToInt(@paramStr, 0x7fffffff, 300) -- 5 min default


        --Now set the dialog timer. This triggers the send retry
        ;BEGIN CONVERSATION TIMER (@conv_handle) TIMEOUT = @accRetryDelay 

    END
    ELSE
    BEGIN
        --Only end theconversation if a retry isn't being attempted
        END CONVERSATION @conv_handle
    END


    -- All done OK
    goto ExitProc;

    -----------------
    -- Error Handler
    -----------------
ErrorHandler:

    ------------------
    -- Exit Procedure
    ------------------
ExitProc:
    RETURN (@rc);

END
GO

GO
-- Turn off QUOTED IDENTIFIER after compiling stored proc that uses XQuery
SET QUOTED_IDENTIFIER OFF
GO


-----------------------------------------------------------
-- procedure sp_MailItemResultSets
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_MailItemResultSets', 'P') IS NULL
    DROP PROCEDURE dbo.sp_MailItemResultSets
GO
-----
PRINT 'Creating sp_MailItemResultSets'
-----
GO
-- sp_MailItemResultSets : 
--  Sends back multiple rowsets with the mail items data
CREATE PROCEDURE sp_MailItemResultSets
    @mailitem_id            INT,
    @profile_id             INT,
    @conversation_handle    uniqueidentifier,
   @service_contract_name  NVARCHAR(256),
   @message_type_name      NVARCHAR(256)
AS
BEGIN
    SET NOCOUNT ON
   --
   -- Send back multiple rowsets with the mail items data

   ----
   -- 1) MessageTypeName
   SELECT @message_type_name  as 'message_type_name',
      @service_contract_name as 'service_contract_name',
      @conversation_handle   as 'conversation_handle',
      @mailitem_id           as 'mailitem_id'

   -----
   -- 2) The mail item record from sysmail_mailitems.
   SELECT 
      mi.mailitem_id,
      mi.profile_id,
      (SELECT name FROM msdb.dbo.sysmail_profile p WHERE p.profile_id = mi.profile_id) as 'profile_name',
      mi.recipients,
      mi.copy_recipients,
      mi.blind_copy_recipients,
      mi.subject,
      mi.body, 
      mi.body_format, 
      mi.importance,
      mi.sensitivity,
      ISNULL(sr.send_attempts, 0) as retry_attempt,
      ISNULL(mi.from_address, '') as from_address,
      ISNULL(mi.reply_to, '')     as reply_to
   FROM sysmail_mailitems as mi
      LEFT JOIN sysmail_send_retries as sr
         ON sr.mailitem_id = mi.mailitem_id 
   WHERE mi.mailitem_id = @mailitem_id

   -----
   -- 3) Account information 
   SELECT a.account_id,
         a.name
   FROM msdb.dbo.sysmail_profileaccount as pa
   JOIN msdb.dbo.sysmail_account as a
      ON pa.account_id = a.account_id
   WHERE pa.profile_id = @profile_id
   ORDER BY pa.sequence_number 

   -----
   -- 4) Attachments if any
   SELECT attachment_id,
      mailitem_id,
      filename,
      filesize,
      attachment
   FROM sysmail_attachments
   WHERE mailitem_id = @mailitem_id
   

   RETURN 0
END
GO

-----------------------------------------------------------
-- procedure sp_process_DialogTimer
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_process_DialogTimer', 'P') IS NULL
    DROP PROCEDURE dbo.sp_process_DialogTimer
GO
-----
PRINT 'Creating sp_process_DialogTimer'
-----
GO
-- Processes a DialogTimer message from the the queue. This is used for send mail retries.
-- Returns the mail to be send if a retry is required or logs a failure if max retry count has been reached 
CREATE PROCEDURE sp_process_DialogTimer
    @conversation_handle    uniqueidentifier,
   @service_contract_name  NVARCHAR(256),
   @message_type_name      NVARCHAR(256)
AS
BEGIN
    SET NOCOUNT ON

    -- Declare all variables
    DECLARE 
        @mailitem_id        INT,
        @profile_id         INT,
        @send_attempts      INT,
        @mail_request_date  DATETIME,
        @localmessage       NVARCHAR(255),
        @paramStr           NVARCHAR(256),
        @accRetryAttempts   INT

    -- Get the associated mail item data for the given @conversation_handle
   SELECT @mailitem_id     = mi.mailitem_id,
      @profile_id         = mi.profile_id,
        @mail_request_date  = mi.send_request_date,
      @send_attempts      = sr.send_attempts
   FROM sysmail_send_retries as sr
      JOIN sysmail_mailitems as mi
        ON sr.mailitem_id = mi.mailitem_id
   WHERE sr.conversation_handle = @conversation_handle

   -- If not able to find a mailitem_id return and move to the next message.
    -- This could happen if the mail items table was cleared before the retry was fired
   IF(@mailitem_id IS NULL)
   BEGIN
        --Log warning and continue
        -- "mailitem_id on conversation %s was not found in the sysmail_send_retries table. This mail item will not be sent."
        SET @localmessage = FORMATMESSAGE(14662, convert(NVARCHAR(50), @conversation_handle))
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=2, @description=@localmessage

        -- Resource clean-up
        IF(@conversation_handle IS NOT NULL)
        BEGIN
           END CONVERSATION @conversation_handle;
        END

        -- return code has special meaning and will be propagated to the calling process
        RETURN 2;
    END


    --Get the retry attempt count from sysmailconfig.
    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'AccountRetryAttempts', 
                                                 @parameter_value = @paramStr OUTPUT
    --ConvertToInt will return the default if @paramStr is null
    SET @accRetryAttempts = dbo.ConvertToInt(@paramStr, 0x7fffffff, 1)


   --Check the send attempts and log and error if send_attempts >= retry count.
    --This shouldn't happen unless the retry configuration was changed
   IF(@send_attempts > @accRetryAttempts)
   BEGIN
        --Log warning and continue
        -- "Mail Id %d has exceeded the retry count. This mail item will not be sent."
        SET @localmessage = FORMATMESSAGE(14663, @mailitem_id)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=2, @description=@localmessage, @mailitem_id=@mailitem_id

        -- Resource clean-up
        IF(@conversation_handle IS NOT NULL)
        BEGIN
           END CONVERSATION @conversation_handle;
        END

        -- return code has special meaning and will be propagated to the calling process
        RETURN 3;
    END

    -- This returns the mail item to the client as multiple result sets
    EXEC sp_MailItemResultSets
            @mailitem_id            = @mailitem_id,
            @profile_id             = @profile_id,
            @conversation_handle    = @conversation_handle,
           @service_contract_name  = @service_contract_name,
           @message_type_name      = @message_type_name

   
   RETURN 0

END
GO

-----------------------------------------------------------
-- procedure sp_readrequest
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_readrequest', 'P') IS NULL
    DROP PROCEDURE dbo.sp_readrequest
GO
-----
PRINT 'Creating sp_readrequest'
-----
GO

-- Turn on QUOTED IDENTIFIER to compile this stored proc that uses XQuery
SET QUOTED_IDENTIFIER ON
GO

-- sp_readrequest : Reads a request from the the queue and returns its
--                  contents.
CREATE PROCEDURE sp_readrequest
    @receive_timeout    INT     -- the max time this read will wait for new message
AS
BEGIN
    SET NOCOUNT ON

    -- Table to store message information.
    DECLARE @msgs TABLE
    (
       [conversation_handle] uniqueidentifier,
       [service_contract_name] nvarchar(256),
       [message_type_name] nvarchar(256),
       [message_body] varbinary(max)
    )

    -- Declare variables to store row values fetched from the cursor
    DECLARE 
        @exit                   INT,
        @mailitem_id            INT,
        @profile_id             INT,
        @conversation_handle    uniqueidentifier,
        @service_contract_name  NVARCHAR(256),
        @message_type_name      NVARCHAR(256),
        @xml_message_body       VARCHAR(max),
        @timediff               INT,
        @rec_timeout            INT,
        @start_time             DATETIME,
        @localmessage           NVARCHAR(256),
        @rc                     INT

    --Init variables
    SELECT @start_time = GETDATE(),
           @timediff = 0,
           @exit = 0,
           @rc = 0

    WHILE (@timediff <= @receive_timeout)
    BEGIN
        -- Delete all messages from @msgs table
        DELETE FROM @msgs

        -- Pick all message from queue
        SET @rec_timeout = @receive_timeout - @timediff
        WAITFOR(RECEIVE conversation_handle, service_contract_name, message_type_name, message_body 
                FROM ExternalMailQueue INTO @msgs), TIMEOUT @rec_timeout

        -- Check if there was some error in reading from queue
        SET @rc = @@ERROR
        IF (@rc <> 0)
        BEGIN
           IF(@rc < 4) -- make sure return code is not in reserved range (1-3)
               SET @rc = 4

           --Note: we will get error no. 9617 if the service queue 'ExternalMailQueue' is currently disabled.
           BREAK
        END

       --If there is no message in the queue return 1 to indicate a timeout
        IF NOT EXISTS(SELECT * FROM @msgs)
        BEGIN
          SET @rc = 1
          BREAK
        END

        -- Create a cursor to iterate through the messages.
        DECLARE msgs_cursor CURSOR FOR
        SELECT conversation_handle, 
            service_contract_name, 
            message_type_name,
            CONVERT(VARCHAR(MAX), message_body)
        FROM @msgs;

        -- Open the cursor
        OPEN msgs_cursor;

        -- Perform the first fetch and store the values in the variables.
        FETCH NEXT FROM msgs_cursor
        INTO 
            @conversation_handle,
            @service_contract_name,
            @message_type_name,
            @xml_message_body

        -- Check @@FETCH_STATUS to see if there are any more rows to fetch.
        WHILE (@@FETCH_STATUS = 0)
        BEGIN
            -- Check if the message is a send mail message
            IF(@message_type_name = N'{//www.microsoft.com/databasemail/messages}SendMail')
            BEGIN
                DECLARE @xmlblob xml
                
                SET @xmlblob = CONVERT(xml, @xml_message_body) 
                    
                SELECT  @mailitem_id = MailRequest.Properties.value('(MailItemId)[1]', 'int') 
                FROM @xmlblob.nodes('
                declare namespace requests="http://schemas.microsoft.com/databasemail/requests";
                /requests:SendMail') 
                AS MailRequest(Properties) 
                				
                -- get account information 
                SELECT @profile_id = profile_id
                FROM sysmail_mailitems 
                WHERE mailitem_id = @mailitem_id

                IF(@profile_id IS NULL) -- mail item has been deleted from the database
                BEGIN
                   -- log warning
                   SET @localmessage = FORMATMESSAGE(14667, @mailitem_id)
                   exec msdb.dbo.sysmail_logmailevent_sp @event_type=2, @description=@localmessage

                   -- Resource clean-up
                   IF(@conversation_handle IS NOT NULL)
                      END CONVERSATION @conversation_handle;
                   
                   -- return code has special meaning and will be propagated to the calling process
                   SET @rc = 2
                END
                ELSE
                BEGIN
                   -- This returns the mail item to the client as multiple result sets
                   EXEC sp_MailItemResultSets
                           @mailitem_id            = @mailitem_id,
                           @profile_id             = @profile_id,
                           @conversation_handle    = @conversation_handle,
                           @service_contract_name  = @service_contract_name,
                           @message_type_name      = @message_type_name
                
                   SET @exit = 1 -- make sure we exit outer loop
                END

                -- always break from the loop upon processing SendMail message
                BREAK
            END
            -- Check if the message is a dialog timer. This is used for account retries
            ELSE IF(@message_type_name = N'http://schemas.microsoft.com/SQL/ServiceBroker/DialogTimer')
            BEGIN
                -- Handle the retry case. - DialogTimer is used for send mail reties
                EXEC @rc = sp_process_DialogTimer
                            @conversation_handle    = @conversation_handle,
                            @service_contract_name  = @service_contract_name,
                            @message_type_name      = N'{//www.microsoft.com/databasemail/messages}SendMail'

                -- Always break from the loop upon processing DialogTimer message
                -- In case of failure return code from procedure call will simply be propagated to the calling process
                SET @exit = 1 -- make sure we exit outer loop
                BREAK
            END
            -- Error case
            ELSE IF (@message_type_name = 'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
            -- Error in the conversation, hence ignore all the messages of this conversation.
                BREAK

            -- This is executed as long as fetch succeeds.
            FETCH NEXT FROM msgs_cursor
            INTO 
                @conversation_handle,
                @service_contract_name,
                @message_type_name,
                @xml_message_body
        END

        CLOSE msgs_cursor;
        DEALLOCATE msgs_cursor;

        -- Check if we read any request or only SSB generated messages
        -- If a valid request is read with or without an error break out of loop
        IF (@exit = 1 OR @rc <> 0)
            BREAK

       --Keep track of how long this sp has been running
        select @timediff = DATEDIFF(ms, @start_time, getdate()) 
    END

    -- propagate return code to the calling process
    RETURN @rc
END
GO

-- Turn off QUOTED IDENTIFIER after compiling stored proc that uses XQuery
SET QUOTED_IDENTIFIER OFF
GO

-----------------------------------------------------------
-- procedure sp_GetAttachmentData
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_GetAttachmentData', 'P') IS NULL
    DROP PROCEDURE dbo.sp_GetAttachmentData
GO
-----
PRINT 'Creating sp_GetAttachmentData'
-----
GO

CREATE PROCEDURE sp_GetAttachmentData
   @attachments           nvarchar(max),
   @temp_table_uid        uniqueidentifier,
   @exclude_query_output  BIT        = 0
AS
BEGIN
    SET NOCOUNT ON
    SET QUOTED_IDENTIFIER ON

    DECLARE @rc             INT,
            @prohibitedExts NVARCHAR(1000),
            @attachFilePath NVARCHAR(260),
            @scIndex        INT,
            @startLocation  INT,
            @fileSizeStr    NVARCHAR(256),
            @fileSize       INT,
            @mailDbName     sysname,
            @uidStr         VARCHAR(36)

    --Get the maximum file size allowed for attachments from sysmailconfig.
    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'MaxFileSize', 
                                                @parameter_value = @fileSizeStr OUTPUT
    --ConvertToInt will return the default if @fileSizeStr is null
    SET @fileSize = dbo.ConvertToInt(@fileSizeStr, 0x7fffffff, 100000)

    --May need this if attaching files
    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'ProhibitedExtensions', 
                                                @parameter_value = @prohibitedExts OUTPUT

    SET @mailDbName = DB_NAME()
    SET @uidStr = CONVERT(VARCHAR(36), @temp_table_uid)

   SET @attachments = @attachments + ';'
   SET @startLocation = 0
   SET @scIndex = CHARINDEX(';', @attachments, @startLocation)

   WHILE (@scIndex <> 0)
   BEGIN
      SET @attachFilePath = SUBSTRING(@attachments, @startLocation, (@scIndex - @startLocation))
      
      -- Make sure we have an attachment file name to work with, and that it hasn't been truncated
      IF (@scIndex - @startLocation > 260 )
      BEGIN
            RAISERROR(14628, 16, 1)
          RETURN 1 
      END

        IF ((@attachFilePath IS NULL) OR (LEN(@attachFilePath) = 0))
        BEGIN
            RAISERROR(14628, 16, 1)
          RETURN 1 
        END

      --Check if attachment ext is allowed 
      EXEC @rc = sp_isprohibited @attachFilePath, @prohibitedExts
      IF (@rc <> 0)
      BEGIN
          RAISERROR(14630, 16, 1, @attachFilePath, @prohibitedExts)
          RETURN @rc
      END

        DECLARE  @no_output_int  INT
        SET @no_output_int         = CONVERT(int, @exclude_query_output)

        -- return code checked after select and delete calls
        EXEC @rc = master..xp_sysmail_attachment_load @message       = @mailDbName, 
                                                      @attachments      = @attachFilePath,
                                                      @subject       = @uidStr,
                                                      @max_attachment_size = @fileSize,
                                                      @no_output = @no_output_int
      IF (@rc <> 0)
            RETURN (@rc)
               
        --Get next substring index
      SET @startLocation = @scIndex + 1
      SET @scIndex = CHARINDEX(';', @attachments, @startLocation)

      IF (@scIndex = 0)
         BREAK
   END

    RETURN 0
END
GO


-----------------------------------------------------------
-- procedure sp_RunMailQuery
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_RunMailQuery', 'P') IS NULL
    DROP PROCEDURE dbo.sp_RunMailQuery
GO
-----
PRINT 'Creating sp_RunMailQuery'
-----
USE msdb
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO

CREATE PROCEDURE [dbo].[sp_RunMailQuery]
   @query                      NVARCHAR(max),
   @attach_results             BIT,
   @query_attachment_filename  NVARCHAR(260) = NULL,
   @no_output                  BIT,
   @query_result_header        BIT,
   @separator                  VARCHAR(1),
   @echo_error                 BIT,
   @dbuse                      sysname,
   @width                      INT,
   @temp_table_uid             uniqueidentifier,
   @query_no_truncate          BIT,
   @query_result_no_padding    BIT
AS
BEGIN
    SET NOCOUNT ON
    SET QUOTED_IDENTIFIER ON

    DECLARE @rc             INT,
            @prohibitedExts NVARCHAR(1000),
            @fileSizeStr    NVARCHAR(256),
            @fileSize       INT,
            @attach_res_int INT,
            @no_output_int  INT,
            @no_header_int  INT,
            @echo_error_int INT,
            @query_no_truncate_int INT,
            @query_result_no_padding_int   INT,
            @mailDbName     sysname,
            @uid            uniqueidentifier,
            @uidStr         VARCHAR(36)

    -- Initialize return code value as 1
    SET @rc = 1

    --
    --Get config settings and verify parameters
    --
    SET @query_attachment_filename = LTRIM(RTRIM(@query_attachment_filename))

    --Get the maximum file size allowed for attachments from sysmailconfig.
    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'MaxFileSize', 
                                                @parameter_value = @fileSizeStr OUTPUT
    --ConvertToInt will return the default if @fileSizeStr is null
    SET @fileSize = dbo.ConvertToInt(@fileSizeStr, 0x7fffffff, 100000)

    IF (@attach_results = 1)
    BEGIN
        --Need this if attaching the query
        EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'ProhibitedExtensions', 
                                                    @parameter_value = @prohibitedExts OUTPUT

        -- If attaching query results to a file and a filename isn't given create one
        IF ((@query_attachment_filename IS NOT NULL) AND (LEN(@query_attachment_filename) > 0))
        BEGIN 
          EXEC @rc = sp_isprohibited @query_attachment_filename, @prohibitedExts
          IF (@rc <> 0)
          BEGIN
              RAISERROR(14630, 16, 1, @query_attachment_filename, @prohibitedExts)
              RETURN 2
          END
        END
        ELSE
        BEGIN
            --If queryfilename is not specified, generate a random name (doesn't have to be unique)
           SET @query_attachment_filename = 'QueryResults' + CONVERT(varchar, ROUND(RAND() * 1000000, 0)) + '.txt'
        END
    END

    --Init variables used in the query execution
    SET @mailDbName = db_name()
    SET @uidStr = convert(varchar(36), @temp_table_uid)

    SET @attach_res_int        = CONVERT(int, @attach_results)
    SET @no_output_int         = CONVERT(int, @no_output)
    IF(@query_result_header = 0) SET @no_header_int  = 1 ELSE SET @no_header_int  = 0
    SET @echo_error_int        = CONVERT(int, @echo_error)
    SET @query_no_truncate_int = CONVERT(int, @query_no_truncate)
    SET @query_result_no_padding_int = CONVERT(int, @query_result_no_padding )

    EXEC @rc = master..xp_sysmail_format_query  
            @query                      = @query,
            @message                    = @mailDbName,
            @subject                    = @uidStr,
            @dbuse                      = @dbuse, 
            @attachments                = @query_attachment_filename,
            @attach_results             = @attach_res_int,
            -- format params
            @separator                  = @separator,
            @no_header                  = @no_header_int,
            @no_output                  = @no_output_int,
            @echo_error                 = @echo_error_int,
            @max_attachment_size        = @fileSize,
            @width                      = @width, 
            @query_no_truncate          = @query_no_truncate_int,
            @query_result_no_padding    = @query_result_no_padding_int
   RETURN @rc
END
GO

-----------------------------------------------------------
-- procedure sp_validate_user, used by sp_send_dbmail
-----------------------------------------------------------
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_validate_user')))
  DROP PROCEDURE sp_validate_user
go
use msdb
GO
/****** Object:  StoredProcedure [dbo].sp_validate_user ********/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
CREATE PROCEDURE [dbo].[sp_validate_user]
    @send_request_user sysname,
    @user_sid varbinary(85) OUTPUT
  WITH EXECUTE AS 'dbo'
AS
BEGIN
    SET NOCOUNT ON
    -- And make sure ARITHABORT is on. This is the default for yukon DB's
    SET ARITHABORT ON

    declare @groupSid varbinary(85)
    declare @temp table
    ([account name] sysname, 
    [type] char(8),
    [privilege] char(9),
    [mapped login name] sysname,
    [permission path] sysname null)

    declare @sidlist table
    ([account name] sysname,
     [accountsid] varbinary(85) null,
     [permission path] sysname null)

    SET @user_sid = NULL
    SET @groupSid = NULL

    -- Lookup the Windows Group membership, if any, that grants this
    -- user access to SQL Server. xp_logininfo may fail if the sql
    -- server service account cannot talk to the domain controller to
    -- validate the windows username, or it may fail if the
    -- @send_request_user is not a valid windows user or group name.
    BEGIN TRY 
        insert @temp exec master.dbo.xp_logininfo @send_request_user, 'all'
        -- For a given account name, Get account name -> group accountsid mapping to a temp table variable
        insert @sidlist
            select [account name], suser_sid([permission path]),[permission path]
            from @temp
    END TRY
    BEGIN CATCH
        RETURN 2
    END CATCH

    -- for a given account name, there has to be atleast one account sid that is not null and
    -- there has to be atleast one mail profile for the list of account sids
    IF ((EXISTS(SELECT [account name] 
                FROM @sidlist
                WHERE accountsid is not NULL)
    AND (EXISTS(SELECT profile_id 
                FROM msdb.dbo.sysmail_principalprofile pp, @sidlist s
                WHERE s.accountsid = pp.principal_sid))))

    BEGIN
        -- Get the first account's sid that meets following criteria
        --  1) return first default profile (if available)
        --  2) if no default profile is  defined, then return the first non-default profile for this account
        SELECT TOP 1  @groupSid = accountsid 
        FROM @sidlist s, msdb.dbo.sysmail_principalprofile pp
        WHERE s.accountsid is not NULL
        AND s.accountsid = pp.principal_sid
        ORDER BY is_default DESC
    END

    -- Lookup a default profile for the Group. If there is one,
    -- then use the group's mail profile.
    IF (@groupSid IS NOT NULL)
    BEGIN
        SET @user_sid = @groupSid
        RETURN 0
    END
    RETURN 1
END
GO



-----------------------------------------------------------
-- procedure sp_send_dbmail
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_send_dbmail', 'P') IS NULL
    DROP PROCEDURE dbo.sp_send_dbmail
GO
-----
PRINT 'Creating sp_send_dbmail'
-----
USE msdb
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
-- sp_send_dbmail : Sends a mail from Yukon outbox.
--
CREATE PROCEDURE [dbo].[sp_send_dbmail]
   @profile_name               sysname    = NULL,        
   @recipients                 VARCHAR(MAX)  = NULL, 
   @copy_recipients            VARCHAR(MAX)  = NULL,
   @blind_copy_recipients      VARCHAR(MAX)  = NULL,
   @subject                    NVARCHAR(255) = NULL,
   @body                       NVARCHAR(MAX) = NULL, 
   @body_format                VARCHAR(20)   = NULL, 
   @importance                 VARCHAR(6)    = 'NORMAL',
   @sensitivity                VARCHAR(12)   = 'NORMAL',
   @file_attachments           NVARCHAR(MAX) = NULL,  
   @query                      NVARCHAR(MAX) = NULL,
   @execute_query_database     sysname       = NULL,  
   @attach_query_result_as_file BIT          = 0,
   @query_attachment_filename  NVARCHAR(260) = NULL,  
   @query_result_header        BIT           = 1,
   @query_result_width         INT           = 256,            
   @query_result_separator     CHAR(1)       = ' ',
   @exclude_query_output       BIT           = 0,
   @append_query_error         BIT           = 0,
   @query_no_truncate          BIT           = 0,
   @query_result_no_padding    BIT           = 0,
   @mailitem_id               INT            = NULL OUTPUT,
   @from_address               VARCHAR(max)  = NULL,
   @reply_to                   VARCHAR(max)  = NULL
  WITH EXECUTE AS 'dbo'
AS
BEGIN
    SET NOCOUNT ON

    -- And make sure ARITHABORT is on. This is the default for yukon DB's
    SET ARITHABORT ON

    --Declare variables used by the procedure internally
    DECLARE @profile_id         INT,
            @temp_table_uid     uniqueidentifier,
            @sendmailxml        VARCHAR(max),
            @CR_str             NVARCHAR(2),
            @localmessage       NVARCHAR(255),
            @QueryResultsExist  INT,
            @AttachmentsExist   INT,
            @RetErrorMsg        NVARCHAR(4000), --Impose a limit on the error message length to avoid memory abuse 
            @rc                 INT,
            @procName           sysname,
            @trancountSave      INT,
            @tranStartedBool    INT,
            @is_sysadmin        BIT,
            @send_request_user  sysname,
            @database_user_id   INT,
            @sid                varbinary(85)

    -- Initialize 
    SELECT  @rc                 = 0,
            @QueryResultsExist  = 0,
            @AttachmentsExist   = 0,
            @temp_table_uid     = NEWID(),
            @procName           = OBJECT_NAME(@@PROCID),
            @tranStartedBool    = 0,
            @trancountSave      = @@TRANCOUNT,
            @sid                = NULL

    EXECUTE AS CALLER
       SELECT @is_sysadmin       = IS_SRVROLEMEMBER('sysadmin'),
              @send_request_user = SUSER_SNAME(),
              @database_user_id  = USER_ID()
    REVERT

    --Check if SSB is enabled in this database
    IF (ISNULL(DATABASEPROPERTYEX(DB_NAME(), N'IsBrokerEnabled'), 0) <> 1)
    BEGIN
       RAISERROR(14650, 16, 1)
       RETURN 1
    END

    --Report error if the mail queue has been stopped. 
    --sysmail_stop_sp/sysmail_start_sp changes the receive status of the SSB queue
    IF NOT EXISTS (SELECT * FROM sys.service_queues WHERE name = N'ExternalMailQueue' AND is_receive_enabled = 1)
    BEGIN
       RAISERROR(14641, 16, 1)
       RETURN 1
    END

    -- Get the relevant profile_id 
    --
    IF (@profile_name IS NULL)
    BEGIN
        -- Use the global or users default if profile name is not supplied
        SELECT TOP (1) @profile_id = pp.profile_id
        FROM msdb.dbo.sysmail_principalprofile as pp
        WHERE (pp.is_default = 1) AND
            (dbo.get_principal_id(pp.principal_sid) = @database_user_id OR pp.principal_sid = 0x00)
        ORDER BY dbo.get_principal_id(pp.principal_sid) DESC

        --Was a profile found
        IF(@profile_id IS NULL)
        BEGIN
            -- Try a profile lookup based on Windows Group membership, if any
            EXEC @rc = msdb.dbo.sp_validate_user @send_request_user, @sid OUTPUT
            IF (@rc = 0)
            BEGIN
                SELECT TOP (1) @profile_id = pp.profile_id
                FROM msdb.dbo.sysmail_principalprofile as pp
                WHERE (pp.is_default = 1) AND
                      (pp.principal_sid = @sid)
                ORDER BY dbo.get_principal_id(pp.principal_sid) DESC
            END

            IF(@profile_id IS NULL)
            BEGIN
                RAISERROR(14636, 16, 1)
                RETURN 1
            END
        END
    END
    ELSE
    BEGIN
        --Get primary account if profile name is supplied
        EXEC @rc = msdb.dbo.sysmail_verify_profile_sp @profile_id = NULL, 
                         @profile_name = @profile_name, 
                         @allow_both_nulls = 0, 
                         @allow_id_name_mismatch = 0,
                         @profileid = @profile_id OUTPUT
        IF (@rc <> 0)
            RETURN @rc

        --Make sure this user has access to the specified profile.
        --sysadmins can send on any profiles
        IF ( @is_sysadmin <> 1)
        BEGIN
            --Not a sysadmin so check users access to profile
            iF NOT EXISTS(SELECT * 
                        FROM msdb.dbo.sysmail_principalprofile 
                        WHERE ((profile_id = @profile_id) AND
                                (dbo.get_principal_id(principal_sid) = @database_user_id OR principal_sid = 0x00)))
            BEGIN
                EXEC msdb.dbo.sp_validate_user @send_request_user, @sid OUTPUT
                IF(@sid IS NULL)
                BEGIN
                    RAISERROR(14607, -1, -1, 'profile')
                    RETURN 1
                END
            END
        END
    END

    --Attach results must be specified
    IF @attach_query_result_as_file IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'attach_query_result_as_file')
       RETURN 2
    END

    --No output must be specified
    IF @exclude_query_output IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'exclude_query_output')
       RETURN 3
    END

    --No header must be specified
    IF @query_result_header IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'query_result_header')
       RETURN 4
    END

    -- Check if query_result_separator is specifed
    IF @query_result_separator IS NULL OR DATALENGTH(@query_result_separator) = 0
    BEGIN
       RAISERROR(14618, 16, 1, 'query_result_separator')
       RETURN 5
    END

    --Echo error must be specified
    IF @append_query_error IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'append_query_error')
       RETURN 6
    END

    --@body_format can be TEXT (default) or HTML
    IF (@body_format IS NULL)
    BEGIN
       SET @body_format = 'TEXT'
    END
    ELSE
    BEGIN
       SET @body_format = UPPER(@body_format)

       IF @body_format NOT IN ('TEXT', 'HTML') 
       BEGIN
          RAISERROR(14626, 16, 1, @body_format)
          RETURN 13
       END
    END

    --Importance must be specified
    IF @importance IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'importance')
       RETURN 15
    END

    SET @importance = UPPER(@importance)

    --Importance must be one of the predefined values
    IF @importance NOT IN ('LOW', 'NORMAL', 'HIGH')
    BEGIN
       RAISERROR(14622, 16, 1, @importance)
       RETURN 16
    END

    --Sensitivity must be specified
    IF @sensitivity IS NULL
    BEGIN
       RAISERROR(14618, 16, 1, 'sensitivity')
       RETURN 17
    END

    SET @sensitivity = UPPER(@sensitivity)

    --Sensitivity must be one of predefined values
    IF @sensitivity NOT IN ('NORMAL', 'PERSONAL', 'PRIVATE', 'CONFIDENTIAL')
    BEGIN
       RAISERROR(14623, 16, 1, @sensitivity)
       RETURN 18
    END

    --Message body cannot be null. Atleast one of message, subject, query,
    --attachments must be specified.
    IF( (@body IS NULL AND @query IS NULL AND @file_attachments IS NULL AND @subject IS NULL)
       OR
    ( (LEN(@body) IS NULL OR LEN(@body) <= 0)  
       AND (LEN(@query) IS NULL  OR  LEN(@query) <= 0)
       AND (LEN(@file_attachments) IS NULL OR LEN(@file_attachments) <= 0)
       AND (LEN(@subject) IS NULL OR LEN(@subject) <= 0)
    )
    )
    BEGIN
       RAISERROR(14624, 16, 1, '@body, @query, @file_attachments, @subject')
       RETURN 19
    END   
    ELSE
       IF @subject IS NULL OR LEN(@subject) <= 0
          SET @subject='SQL Server Message'

    --Recipients cannot be empty. Atleast one of the To, Cc, Bcc must be specified
    IF ( (@recipients IS NULL AND @copy_recipients IS NULL AND 
       @blind_copy_recipients IS NULL
        )     
       OR
        ( (LEN(@recipients) IS NULL OR LEN(@recipients) <= 0)
       AND (LEN(@copy_recipients) IS NULL OR LEN(@copy_recipients) <= 0)
       AND (LEN(@blind_copy_recipients) IS NULL OR LEN(@blind_copy_recipients) <= 0)
        )
    )
    BEGIN
       RAISERROR(14624, 16, 1, '@recipients, @copy_recipients, @blind_copy_recipients')
       RETURN 20
    END

    EXEC @rc = msdb.dbo.sysmail_verify_addressparams_sp @address = @recipients, @parameter_name='@recipients' 
    IF (@rc <> 0)
       RETURN @rc
    EXEC @rc = msdb.dbo.sysmail_verify_addressparams_sp @address = @copy_recipients, @parameter_name='@copy_recipients' 
    IF (@rc <> 0)
       RETURN @rc
    EXEC @rc = msdb.dbo.sysmail_verify_addressparams_sp @address = @blind_copy_recipients, @parameter_name='@blind_copy_recipients' 
    IF (@rc <> 0)
       RETURN @rc
    EXEC @rc = msdb.dbo.sysmail_verify_addressparams_sp @address = @reply_to, @parameter_name='@reply_to' 
    IF (@rc <> 0)
       RETURN @rc

    --If query is not specified, attach results and no header cannot be true.
    IF ( (@query IS NULL OR LEN(@query) <= 0) AND @attach_query_result_as_file = 1)
    BEGIN
       RAISERROR(14625, 16, 1)
       RETURN 21
    END

    --
    -- Execute Query if query is specified
    IF ((@query IS NOT NULL) AND (LEN(@query) > 0))
    BEGIN
        EXECUTE AS CALLER
        EXEC @rc = sp_RunMailQuery 
                @query                     = @query,
                @attach_results            = @attach_query_result_as_file,
                @query_attachment_filename = @query_attachment_filename,
                @no_output                 = @exclude_query_output,
                @query_result_header       = @query_result_header,
                @separator                 = @query_result_separator,
                @echo_error                = @append_query_error,
                @dbuse                     = @execute_query_database,
                @width                     = @query_result_width,
                @temp_table_uid            = @temp_table_uid,
                @query_no_truncate         = @query_no_truncate,
                @query_result_no_padding   = @query_result_no_padding
        
        --Switches the execution context back to the caller after last EXECUTE AS CALLER
        REVERT      

        IF(@rc <> 0 )
        BEGIN
            GOTO ErrorHandler;
        END
 
         -- Always check the transfer tables for data. They may also contain error messages
         -- Only one of the tables receives data in the call to sp_RunMailQuery
         IF(@attach_query_result_as_file = 1)
         BEGIN
             IF EXISTS(SELECT * FROM sysmail_attachments_transfer WHERE uid = @temp_table_uid)
            SET @AttachmentsExist = 1
         END
         ELSE
         BEGIN
             IF EXISTS(SELECT * FROM sysmail_query_transfer WHERE uid = @temp_table_uid AND uid IS NOT NULL)
            SET @QueryResultsExist = 1
         END

         -- Exit if there was an error and caller doesn't want the error appended to the mail
         IF (@rc <> 0 AND @append_query_error = 0)
         BEGIN
            --Error msg with be in either the attachment table or the query table 
            --depending on the setting of @attach_query_result_as_file
            IF(@attach_query_result_as_file = 1)
            BEGIN
               --Copy query results from the attachments table to mail body
               SELECT @RetErrorMsg = CONVERT(NVARCHAR(4000), attachment)
               FROM sysmail_attachments_transfer 
               WHERE uid = @temp_table_uid
            END
            ELSE
            BEGIN
               --Copy query results from the query table to mail body
               SELECT @RetErrorMsg = text_data 
               FROM sysmail_query_transfer 
               WHERE uid = @temp_table_uid
            END

            GOTO ErrorHandler;
         END
         SET @AttachmentsExist = @attach_query_result_as_file
    END
    ELSE
    BEGIN
        --If query is not specified, attach results cannot be true.
        IF (@attach_query_result_as_file = 1)
        BEGIN
           RAISERROR(14625, 16, 1)
           RETURN 21
        END
    END

    --Get the prohibited extensions for attachments from sysmailconfig.
    IF ((@file_attachments IS NOT NULL) AND (LEN(@file_attachments) > 0)) 
    BEGIN
        EXECUTE AS CALLER
        EXEC @rc = sp_GetAttachmentData 
                        @attachments = @file_attachments, 
                        @temp_table_uid = @temp_table_uid,
                        @exclude_query_output = @exclude_query_output
        REVERT
        IF (@rc <> 0)
            GOTO ErrorHandler;
        
        IF EXISTS(SELECT * FROM sysmail_attachments_transfer WHERE uid = @temp_table_uid)
            SET @AttachmentsExist = 1
    END

    -- Start a transaction if not already in one. 
    -- Note: For rest of proc use GOTO ErrorHandler for falures  
    if (@trancountSave = 0) 
       BEGIN TRAN @procName

    SET @tranStartedBool = 1

    -- Store complete mail message for history/status purposes  
    INSERT sysmail_mailitems
    (
       profile_id,   
       recipients,
       copy_recipients,
       blind_copy_recipients,
       subject,
       body, 
       body_format, 
       importance,
       sensitivity,
       file_attachments,  
       attachment_encoding,
       query,
       execute_query_database,
       attach_query_result_as_file,
       query_result_header,
       query_result_width,          
       query_result_separator,
       exclude_query_output,
       append_query_error,
       send_request_user,
       from_address,
       reply_to
    )
    VALUES
    (
       @profile_id,        
       @recipients, 
       @copy_recipients,
       @blind_copy_recipients,
       @subject,
       @body, 
       @body_format, 
       @importance,
       @sensitivity,
       @file_attachments,  
       'MIME',
       @query,
       @execute_query_database,  
       @attach_query_result_as_file,
       @query_result_header,
       @query_result_width,            
       @query_result_separator,
       @exclude_query_output,
       @append_query_error,
       @send_request_user,
       @from_address,
       @reply_to
    )

    SELECT @rc          = @@ERROR,
           @mailitem_id = SCOPE_IDENTITY()

    IF(@rc <> 0)
        GOTO ErrorHandler;

    --Copy query into the message body
    IF(@QueryResultsExist = 1)
    BEGIN
      -- if the body is null initialize it
        UPDATE sysmail_mailitems
        SET body = N''
        WHERE mailitem_id = @mailitem_id
        AND body is null

        --Add CR, a \r followed by \n, which is 0xd and then 0xa
        SET @CR_str = CHAR(13) + CHAR(10)
        UPDATE sysmail_mailitems
        SET body.WRITE(@CR_str, NULL, NULL)
        WHERE mailitem_id = @mailitem_id

   --Copy query results to mail body
        UPDATE sysmail_mailitems
        SET body.WRITE( (SELECT text_data from sysmail_query_transfer WHERE uid = @temp_table_uid), NULL, NULL )
        WHERE mailitem_id = @mailitem_id

    END

    --Copy into the attachments table
    IF(@AttachmentsExist = 1)
    BEGIN
        --Copy temp attachments to sysmail_attachments      
        INSERT INTO sysmail_attachments(mailitem_id, filename, filesize, attachment)
        SELECT @mailitem_id, filename, filesize, attachment
        FROM sysmail_attachments_transfer
        WHERE uid = @temp_table_uid
    END

    -- Create the primary SSB xml maessage
    SET @sendmailxml = '<requests:SendMail xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.microsoft.com/databasemail/requests RequestTypes.xsd" xmlns:requests="http://schemas.microsoft.com/databasemail/requests"><MailItemId>'
                        + CONVERT(NVARCHAR(20), @mailitem_id) + N'</MailItemId></requests:SendMail>'

    -- Send the send request on queue.
    EXEC @rc = sp_SendMailQueues @sendmailxml
    IF @rc <> 0
    BEGIN
       RAISERROR(14627, 16, 1, @rc, 'send mail')
       GOTO ErrorHandler;
    END

    -- Print success message if required
    IF (@exclude_query_output = 0)
    BEGIN
       SET @localmessage = FORMATMESSAGE(14635, @mailitem_id)
       PRINT @localmessage
    END  

    --
    -- See if the transaction needs to be commited
    --
    IF (@trancountSave = 0 and @tranStartedBool = 1)
       COMMIT TRAN @procName

    -- All done OK
    goto ExitProc;

    -----------------
    -- Error Handler
    -----------------
ErrorHandler:
    IF (@tranStartedBool = 1) 
       ROLLBACK TRAN @procName

    ------------------
    -- Exit Procedure
    ------------------
ExitProc:
   
    --Always delete query and attactment transfer records. 
   --Note: Query results can also be returned in the sysmail_attachments_transfer table
    DELETE sysmail_attachments_transfer WHERE uid = @temp_table_uid
    DELETE sysmail_query_transfer WHERE uid = @temp_table_uid

   --Raise an error it the query execution fails
   -- This will only be the case when @append_query_error is set to 0 (false)
   IF( (@RetErrorMsg IS NOT NULL) AND (@exclude_query_output=0) )
   BEGIN
      RAISERROR(14661, -1, -1, @RetErrorMsg)
   END

    RETURN (@rc)
END
GO

-----------------------------------------------------------
-- procedure sp_ExternalMailQueueListener
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_ExternalMailQueueListener', 'P') IS NULL
    DROP PROCEDURE dbo.sp_ExternalMailQueueListener
GO
-----
PRINT 'Creating sp_ExternalMailQueueListener'
-----
USE [msdb]
GO
SET ANSI_NULLS ON
GO
-- Turn on QUOTED IDENTIFIER to compile this stored proc that uses XQuery
SET QUOTED_IDENTIFIER ON

GO
-- Processes messages from the external mail queue
--
CREATE PROCEDURE [dbo].[sp_ExternalMailQueueListener]
AS
BEGIN
    DECLARE 
        @mailitem_id        INT,
        @sent_status        INT,
        @sent_account_id    INT,
        @rc                 INT,
        @processId          INT,
        @sent_date          DATETIME,
        @localmessage       NVARCHAR(max),
        @conv_handle        uniqueidentifier,
       @message_type_name  NVARCHAR(256),
       @xml_message_body   NVARCHAR(max),
        @LogMessage         NVARCHAR(max)

    -- Table to store message information.
    DECLARE @msgs TABLE
    (
        [conversation_handle]   uniqueidentifier,
       [message_type_name]     nvarchar(256),
       [message_body]          varbinary(max)
    )

    --RECEIVE messages from the external queue. 
    --MailItem status messages are sent from the external sql mail process along with other SSB notifications and errors
    ;RECEIVE conversation_handle, message_type_name, message_body FROM InternalMailQueue INTO @msgs
    -- Check if there was some error in reading from queue
    SET @rc = @@ERROR
    IF (@rc <> 0)
    BEGIN
        --Log error and continue. Don't want to block the following messages on the queue
        SET @localmessage = FORMATMESSAGE(@@ERROR)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage

        GOTO ErrorHandler;
    END
   
    -----------------------------------
    --Process sendmail status messages
    SELECT 
        @conv_handle        = conversation_handle,
        @message_type_name  = message_type_name, 
		@xml_message_body   = CAST(message_body AS NVARCHAR(MAX))
    FROM @msgs 
    WHERE [message_type_name] = N'{//www.microsoft.com/databasemail/messages}SendMailStatus'

    IF(@message_type_name IS NOT NULL)
    BEGIN
        --
        --Expecting the xml body to be n the following form:
        --
        --<?xml version="1.0" encoding="utf-16"?>
        --<responses:SendMail xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.microsoft.com/databasemail/responses ResponseTypes.xsd" xmlns:responses="http://schemas.microsoft.com/databasemail/responses">
        --<Information>
        --    <Failure Message="THe error message...."/>
        --</Information>
        --<MailItemId Id="1" />
        --<SentStatus Status="3" />
        --<SentAccountId Id="0" />
        --<SentDate Date="2005-03-30T14:55:13" />
        --<CallingProcess Id="3012" />
        --</responses:SendMail>

        DECLARE @xmlblob xml
        SET  @xmlblob = CONVERT(xml, @xml_message_body)

        SELECT  @mailitem_id = MailResponses.Properties.value('(MailItemId/@Id)[1]', 'int'),
                @sent_status = MailResponses.Properties.value('(SentStatus/@Status)[1]', 'int'),
                @sent_account_id = MailResponses.Properties.value('(SentAccountId/@Id)[1]', 'int'),
                @sent_date = MailResponses.Properties.value('(SentDate/@Date)[1]', 'DateTime'),
                @processId = MailResponses.Properties.value('(CallingProcess/@Id)[1]', 'int'),
                @LogMessage = MailResponses.Properties.value('(Information/Failure/@Message)[1]', 'NVARCHAR(max)')
        FROM @xmlblob.nodes('
        declare namespace responses="http://schemas.microsoft.com/databasemail/responses";
        /responses:SendMail') 
        AS MailResponses(Properties) 

        IF(@mailitem_id IS NULL)
        BEGIN  
            --Log error and continue. Don't want to block the following messages on the queue by rolling back the tran
            SET @localmessage = FORMATMESSAGE(14652, CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
            exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage
        END      
        ELSE
        BEGIN
            -- check sent_status is valid : 0(PendingSend), 1(SendSuccessful), 2(SendFailed), 3(AttemptingSendRetry)
            IF(@sent_status NOT IN (1, 2, 3))
            BEGIN
                SET @localmessage = FORMATMESSAGE(14653, N'SentStatus', CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
                exec msdb.dbo.sysmail_logmailevent_sp @event_type=2, @description=@localmessage

                --Set value to SendFailed
                SET @sent_status = 2
            END

            --Make the @sent_account_id NULL if it is 0. 
            IF(@sent_account_id IS NOT NULL AND @sent_account_id = 0)
                SET @sent_account_id = NULL

            --
            -- Update the mail status if not a retry. Nothing else needs to be done in this case
            UPDATE sysmail_mailitems
            SET sent_status     = CAST (@sent_status as TINYINT),
                sent_account_id = @sent_account_id,
                sent_date       = @sent_date
            WHERE mailitem_id = @mailitem_id
        
            -- Report a failure if no record is found in the sysmail_mailitems table
            IF (@@ROWCOUNT = 0)
            BEGIN
                SET @localmessage = FORMATMESSAGE(14653, N'MailItemId', CONVERT(NVARCHAR(50), @conv_handle), @message_type_name, @xml_message_body)
                exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage
            END

            IF (@LogMessage IS NOT NULL)
            BEGIN
                exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@LogMessage, @process_id=@processId, @mailitem_id=@mailitem_id, @account_id=@sent_account_id
            END
        END
    END

    -------------------------------------------------------
    --Process all other messages by logging to sysmail_log
    SET @conv_handle = NULL;
    
    --Always end the conversion if this message is received
    SELECT @conv_handle = conversation_handle
    FROM @msgs 
    WHERE [message_type_name] = N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog'
    
    IF(@conv_handle IS NOT NULL)
    BEGIN
        END CONVERSATION @conv_handle;
    END

    DECLARE @queuemessage nvarchar(max)
    DECLARE queue_messages_cursor CURSOR LOCAL 
    FOR
        SELECT FORMATMESSAGE(14654, CONVERT(NVARCHAR(50), conversation_handle), message_type_name, message_body)
        FROM @msgs 
        WHERE [message_type_name] 
              NOT IN (N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog',
                      N'{//www.microsoft.com/databasemail/messages}SendMailStatus')
  
    OPEN queue_messages_cursor 
    FETCH NEXT FROM queue_messages_cursor INTO @queuemessage
    WHILE (@@fetch_status = 0)
    BEGIN
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=2, @description=@queuemessage
        FETCH NEXT FROM queue_messages_cursor INTO @queuemessage
    END
    CLOSE queue_messages_cursor 
    DEALLOCATE queue_messages_cursor 

    -- All done OK
    goto ExitProc;

    -----------------
    -- Error Handler
    -----------------
ErrorHandler:

    ------------------
    -- Exit Procedure
    ------------------
ExitProc:
    RETURN (@rc)
END
GO

-- Turn off QUOTED IDENTIFIER after compiling stored proc that uses XQuery
SET QUOTED_IDENTIFIER OFF
GO

----------------------------------------------------------
-- procedure sp_sysmail_activate
-----------------------------------------------------------
IF NOT OBJECT_ID('dbo.sp_sysmail_activate', 'P') IS NULL
    DROP PROCEDURE dbo.sp_sysmail_activate
GO

-----
PRINT 'Creating sp_sysmail_activate'
-----
USE [msdb]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON

GO
-- sp_sysmail_activate : Starts the DatabaseMail process if it isn't already running
--
CREATE PROCEDURE [dbo].[sp_sysmail_activate]
AS
BEGIN
    DECLARE @mailDbName sysname
    DECLARE @mailDbId INT
    DECLARE @mailEngineLifeMin INT
    DECLARE @loggingLevel nvarchar(256)
    DECLARE @loggingLevelInt int   
    DECLARE @parameter_value nvarchar(256)
    DECLARE @localmessage nvarchar(max)
    DECLARE @readFromConfigFile INT
    DECLARE @rc INT

    SET NOCOUNT ON
    EXEC sp_executesql @statement = N'RECEIVE TOP(0) * FROM msdb.dbo.ExternalMailQueue'

    EXEC @rc = msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'DatabaseMailExeMinimumLifeTime', 
                                                        @parameter_value = @parameter_value OUTPUT
    IF(@rc <> 0)
        RETURN (1)

    --ConvertToInt will return the default if @parameter_value is null or config value can't be converted
    --Setting max exe lifetime is 1 week (604800 secs). Can't see a reason for it to ever run longer that this
    SET @mailEngineLifeMin = dbo.ConvertToInt(@parameter_value, 604800, 600) 

    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'ReadFromConfigurationFile', 
                                                  @parameter_value = @parameter_value OUTPUT
    --Try to read the optional read from configuration file:
    SET @readFromConfigFile = dbo.ConvertToInt(@parameter_value, 1, 0) 

    --Try and get the optional logging level for the DatabaseMail process
    EXEC msdb.dbo.sysmail_help_configure_value_sp @parameter_name = N'LoggingLevel', 
                                                  @parameter_value = @loggingLevel OUTPUT

    --Convert logging level into string value for passing into XP
    SET @loggingLevelInt = dbo.ConvertToInt(@loggingLevel, 3, 2) 
    IF @loggingLevelInt = 1
       SET @loggingLevel = 'Normal'
    ELSE IF @loggingLevelInt = 3
       SET @loggingLevel = 'Verbose'
    ELSE -- default
       SET @loggingLevel = 'Extended'

    SET @mailDbName = DB_NAME()
    SET @mailDbId   = DB_ID()

    EXEC @rc = master..xp_sysmail_activate @mailDbId, @mailDbName, @readFromConfigFile,
    @mailEngineLifeMin, @loggingLevel
    IF(@rc <> 0)
    BEGIN
        SET @localmessage = FORMATMESSAGE(14637)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=3, @description=@localmessage
    END
    ELSE
    BEGIN
        SET @localmessage = FORMATMESSAGE(14638)
        exec msdb.dbo.sysmail_logmailevent_sp @event_type=0, @description=@localmessage
    END

    RETURN @rc
END
GO

--------------------------------------------------------------
-- Database Mail roles and permissions
--------------------------------------------------------------

-- Create the DatabaseMailUserRole role
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysusers
            WHERE (name = N'DatabaseMailUserRole')
              AND (issqlrole = 1)))
BEGIN
  -- If there are no members in the role, then drop and re-create it
  IF ((SELECT COUNT(*)
       FROM msdb.dbo.sysusers   su,
            msdb.dbo.sysmembers sm
       WHERE (su.uid = sm.groupuid)
         AND (su.name = N'DatabaseMailUserRole')
         AND (su.issqlrole = 1)) = 0)
  BEGIN
    EXECUTE msdb.dbo.sp_droprole @rolename = N'DatabaseMailUserRole'
    EXECUTE msdb.dbo.sp_addrole @rolename = N'DatabaseMailUserRole'
  END
END
ELSE
  EXECUTE msdb.dbo.sp_addrole @rolename = N'DatabaseMailUserRole'  

go

GRANT EXECUTE   ON [dbo].[sp_send_dbmail]                TO DatabaseMailUserRole
                                                    
GRANT EXECUTE   ON [dbo].[sysmail_help_status_sp]        TO DatabaseMailUserRole
GRANT EXECUTE   ON [dbo].[sysmail_delete_mailitems_sp]   TO DatabaseMailUserRole

GRANT SELECT   ON [dbo].[sysmail_allitems]          TO DatabaseMailUserRole
GRANT SELECT   ON [dbo].[sysmail_sentitems]         TO DatabaseMailUserRole
GRANT SELECT   ON [dbo].[sysmail_unsentitems]       TO DatabaseMailUserRole
GRANT SELECT   ON [dbo].[sysmail_faileditems]       TO DatabaseMailUserRole

     
GRANT  SELECT   ON [dbo].[sysmail_mailattachments]  TO DatabaseMailUserRole
GRANT  SELECT   ON [dbo].[sysmail_event_log]        TO DatabaseMailUserRole

go

/*************************************************************************/
/*                                                                       */
/*  Database Mail SSB objects (Messages, Contracts, Queues, Services)    */
/*                                                                       */
/*************************************************************************/

PRINT ''
PRINT 'Dropping Database Mail MESSAGES, CONTRACTS, QUEUES AND SERVICES...'
PRINT ''

-- Drop service InternalMailService if existing.
IF EXISTS (SELECT * FROM sys.services WHERE name ='InternalMailService')
BEGIN
   PRINT 'Dropping SERVICE InternalMailService'
    DROP SERVICE InternalMailService;
END

-- Drop service ExternalMailService if existing.
IF EXISTS (SELECT * FROM sys.services WHERE name ='ExternalMailService')
BEGIN
   PRINT 'Dropping SERVICE ExternalMailService'
    DROP SERVICE ExternalMailService;
END

-- Drop queue InternalMailQueue if existing.
IF EXISTS (SELECT * FROM sys.objects WHERE name = 'InternalMailQueue' AND type = 'SQ')
BEGIN
   PRINT 'Dropping QUEUE InternalMailQueue'
    DROP QUEUE InternalMailQueue;
END    

-- Drop queue ExternalMailQueue if existing.
IF EXISTS (SELECT * FROM sys.objects WHERE name = 'ExternalMailQueue' AND type = 'SQ')
BEGIN
   PRINT 'Dropping QUEUE ExternalMailQueue'
    DROP QUEUE ExternalMailQueue;
END    

--Drop Notification service for activation of DatabaseMail.exe
IF EXISTS (SELECT * FROM sys.services WHERE name ='SQL/Notifications/SysMailNotification/v1.0')
BEGIN
   PRINT 'Dropping SERVICE [SQL/Notifications/SysMailNotification/v1.0]'
    DROP SERVICE [SQL/Notifications/SysMailNotification/v1.0];
END    

--Drop SysMailNotificationQueue if existing
IF EXISTS (SELECT * FROM sys.objects WHERE name = 'SysMailNotificationQueue' AND type = 'SQ')
BEGIN
   PRINT 'Dropping QUEUE SysMailNotificationQueue'
    DROP QUEUE SysMailNotificationQueue;
END    

-- Drop SendMail v1.0 contract if existing.
IF EXISTS(SELECT * FROM sys.service_contracts 
          WHERE name = '//www.microsoft.com/databasemail/contracts/SendMail/v1.0') 
BEGIN
   PRINT 'Dropping CONTRACT [//www.microsoft.com/databasemail/contracts/SendMail/v1.0]'          
   DROP CONTRACT [//www.microsoft.com/databasemail/contracts/SendMail/v1.0];
END

-- Drop SendMail message type if existing.
IF EXISTS(SELECT * FROM sys.service_message_types 
          WHERE name = '{//www.microsoft.com/databasemail/messages}SendMail')
BEGIN
   PRINT 'Dropping MESSAGE TYPE [{//www.microsoft.com/databasemail/messages}SendMail]'           
   DROP MESSAGE TYPE [{//www.microsoft.com/databasemail/messages}SendMail];
END   

-- Drop SendMailStatus message type if existing.
IF EXISTS(SELECT * FROM sys.service_message_types 
          WHERE name = '{//www.microsoft.com/databasemail/messages}SendMailStatus')
BEGIN
   PRINT 'Dropping MESSAGE TYPE [{//www.microsoft.com/databasemail/messages}SendMailStatus]'           
   DROP MESSAGE TYPE [{//www.microsoft.com/databasemail/messages}SendMailStatus];
END 

GO

-------------------------------------------------------------------
-- Create Database Mail MESSAGES, CONTRACTS, QUEUES AND SERVICES 
-------------------------------------------------------------------

PRINT ''
PRINT 'Creating MESSAGES, CONTRACTS, QUEUES AND SERVICES...'
PRINT ''

-- Create SendMail message type.
PRINT 'Creating MESSAGE TYPE [{//www.microsoft.com/databasemail/messages}SendMail]'

CREATE MESSAGE TYPE 
    [{//www.microsoft.com/databasemail/messages}SendMail] 
    VALIDATION = NONE 

CREATE MESSAGE TYPE 
    [{//www.microsoft.com/databasemail/messages}SendMailStatus]
    VALIDATION = NONE 

-- Create SendMail contract.
PRINT 'Creating CONTRACT [//www.microsoft.com/databasemail/contracts/SendMail/v1.0]'

CREATE CONTRACT [//www.microsoft.com/databasemail/contracts/SendMail/v1.0]
(
    [{//www.microsoft.com/databasemail/messages}SendMail]          SENT BY INITIATOR,
    [{//www.microsoft.com/databasemail/messages}SendMailStatus]    SENT BY TARGET
)

-- Create InternalMailQueue queue.
PRINT 'Creating QUEUE InternalMailQueue'

CREATE QUEUE InternalMailQueue
   WITH ACTIVATION (PROCEDURE_NAME = sp_ExternalMailQueueListener,                  
                    MAX_QUEUE_READERS = 1, 
                    EXECUTE AS SELF);

-- Create ExternalMailQueue queue.
PRINT 'Creating QUEUE ExternalMailQueue'

CREATE QUEUE ExternalMailQueue
   WITH ACTIVATION (PROCEDURE_NAME = sp_sysmail_activate,                  
                    MAX_QUEUE_READERS = 1, 
                    EXECUTE AS SELF);

-- Create InternalMailService service.
PRINT 'Creating SERVICE InternalMailService ON QUEUE InternalMailQueue'

CREATE SERVICE InternalMailService ON QUEUE InternalMailQueue
(
  [//www.microsoft.com/databasemail/contracts/SendMail/v1.0] 
 -- ,[//www.microsoft.com/databasemail/contracts/TestProfile/v1.0]
);

-- Create ExternalMailService service.
PRINT 'Creating SERVICE ExternalMailService ON QUEUE ExternalMailQueue'

CREATE SERVICE ExternalMailService ON QUEUE ExternalMailQueue
(
  [//www.microsoft.com/databasemail/contracts/SendMail/v1.0]
 -- ,[//www.microsoft.com/databasemail/contracts/TestProfile/v1.0]
);

GO

/**************************************************************/
/*                                                            */
/*  M  A  I  N  T  E  N  A  N  C  E    P  L  A  N  S          */
/*                                                            */
/**************************************************************/

/**************************************************************/
/* sysmaintplan_subplans                                      */
/**************************************************************/
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysmaintplan_subplans')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysmaintplan_subplans...'

    -- This table stores the DTS package associated with the maintenance plan
    -- It also stored metadata about the maintenance plan such as its name, description etc
    CREATE TABLE sysmaintplan_subplans
    (
        subplan_id          UNIQUEIDENTIFIER    NOT NULL 
            CONSTRAINT [PK_sysmaintplan_subplan] PRIMARY KEY CLUSTERED,
        subplan_name        sysname             NOT NULL,
        subplan_description NVARCHAR(512)       NULL,
        plan_id             UNIQUEIDENTIFIER    NOT NULL,
        job_id              UNIQUEIDENTIFIER    NOT NULL 
            CONSTRAINT FK_subplan_job_id
            FOREIGN KEY (job_id) REFERENCES sysjobs(job_id),
        msx_job_id          UNIQUEIDENTIFIER DEFAULT NULL NULL
            CONSTRAINT FK_subplan_msx_job_id
            FOREIGN KEY (msx_job_id) REFERENCES sysjobs(job_id),
        schedule_id         INT                 NULL 
            CONSTRAINT FK_subplan_schedule_id 
            FOREIGN KEY (schedule_id) REFERENCES sysschedules(schedule_id),
        msx_plan bit DEFAULT 0 NOT NULL
    )
END
go

/**************************************************************/
/* sysmaintplan_log                                           */
/**************************************************************/
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysmaintplan_log')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysmaintplan_log...'

    -- This table stores the maintenance plan log info 
    CREATE TABLE sysmaintplan_log
    (
        task_detail_id  UNIQUEIDENTIFIER    NOT NULL 
            CONSTRAINT [PK_sysmaintplan_taskdetail_id] PRIMARY KEY CLUSTERED,
        plan_id         UNIQUEIDENTIFIER    NULL,
        subplan_id      UNIQUEIDENTIFIER    NULL 
            CONSTRAINT [FK_sysmaintplan_log_subplan_id] 
            FOREIGN KEY (subplan_id) REFERENCES sysmaintplan_subplans(subplan_id),
        start_time      DATETIME            NULL,
        end_time        DATETIME            NULL,
        succeeded       BIT                 NULL,
        logged_remotely bit not null default (0),
        source_server_name nvarchar (128) NULL,
        plan_name nvarchar (128) NULL,
        subplan_name nvarchar (128) NULL
    )
END
go


/**************************************************************/
/* sysmaintplan_logdetail                                     */
/**************************************************************/
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysmaintplan_logdetail')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysmaintplan_logdetail...'

    -- This table stores the maintenance plan log details 
    CREATE TABLE sysmaintplan_logdetail
    (
        task_detail_id  UNIQUEIDENTIFIER NOT NULL 
            CONSTRAINT [FK_sysmaintplan_log_detail_task_id] 
            FOREIGN KEY (task_detail_id) REFERENCES sysmaintplan_log(task_detail_id)
            ON DELETE CASCADE,
        line1           NVARCHAR(256)   NOT NULL,
        line2           NVARCHAR(256)   NULL,
        line3           NVARCHAR(256)   NULL,
        line4           NVARCHAR(256)   NULL,
        line5           NVARCHAR(256)   NULL,
        server_name     sysname         NOT NULL,
        start_time      DATETIME        NULL,
        end_time        DATETIME        NULL,
        error_number    INT             NULL,
        error_message   NVARCHAR(max)   NULL,
        command         NVARCHAR(max)   NULL,
        succeeded       BIT             NULL
    )
END
go


/**************************************************************/
/*                                                            */
/*  M  A  I  N  T  E  N  A  N  C  E    P  L  A  N  S          */
/*                                                            */
/**************************************************************/


/**************************************************************/
/* sp_maintplan_delete_log                                    */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_delete_log...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_delete_log')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_delete_log
go
CREATE PROCEDURE sp_maintplan_delete_log
    @plan_id        UNIQUEIDENTIFIER    = NULL,
    @subplan_id     UNIQUEIDENTIFIER    = NULL,
    @oldest_time    DATETIME            = NULL
AS
BEGIN
    -- @plan_id and @subplan_id must be both NULL or only one exclusively set
   IF (@plan_id IS NOT NULL) AND (@subplan_id IS NOT NULL)
   BEGIN
      RAISERROR(12980, -1, -1, '@plan_id', '@subplan_id')
      RETURN(1)
   END

   --Scenario 1: User wants to delete all logs
   --Scenario 2: User wants to delete all logs older than X date
   --Scenario 3: User wants to delete all logs for a given plan
   --Scenario 4: User wants to delete all logs for a specific subplan
   --Scenario 5: User wants to delete all logs for a given plan older than X date
   --Scenario 6: User wants to delete all logs for a specific subplan older than X date

   -- Special case 1: Delete all logs
   IF (@plan_id IS NULL) AND (@subplan_id IS NULL) AND (@oldest_time IS NULL)
   BEGIN
      DELETE msdb.dbo.sysmaintplan_logdetail
      DELETE msdb.dbo.sysmaintplan_log
      RETURN (0)
   END

   DELETE msdb.dbo.sysmaintplan_log 
    WHERE ( task_detail_id in 
            (SELECT task_detail_id 
             FROM msdb.dbo.sysmaintplan_log 
             WHERE ((@plan_id IS NULL)     OR (plan_id = @plan_id)) AND 
                   ((@subplan_id IS NULL)  OR (subplan_id = @subplan_id)) AND 
                   ((@oldest_time IS NULL) OR (start_time < @oldest_time))) )

    RETURN (0)
END
GO


/**************************************************************/
/* sp_maintplan_delete_subplan                                */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_delete_subplan...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_delete_subplan')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_delete_subplan
go
CREATE PROCEDURE sp_maintplan_delete_subplan
    @subplan_id       UNIQUEIDENTIFIER,
    @delete_jobs BIT                   = 1
AS
BEGIN

    DECLARE @retval     INT
    DECLARE @job        UNIQUEIDENTIFIER
    DECLARE @jobMsx     UNIQUEIDENTIFIER

    SET NOCOUNT ON
    SET @retval = 0

    -- Raise an error if the @subplan_id doesn't exist
    IF( NOT EXISTS(SELECT * FROM sysmaintplan_subplans WHERE subplan_id = @subplan_id))
    BEGIN
        DECLARE @subplan_id_as_char VARCHAR(36)
        SELECT @subplan_id_as_char = CONVERT(VARCHAR(36), @subplan_id)
        RAISERROR(14262, -1, -1, '@subplan_id', @subplan_id_as_char)
        RETURN(1)
    END


    BEGIN TRAN

    --Is there an Agent Job/Schedule associated with this subplan?
    SELECT @job = job_id, @jobMsx = msx_job_id
    FROM msdb.dbo.sysmaintplan_subplans 
    WHERE subplan_id = @subplan_id

    EXEC @retval = msdb.dbo.sp_maintplan_delete_log @subplan_id = @subplan_id
    IF (@retval <> 0)
    BEGIN
        ROLLBACK TRAN
        RETURN @retval
    END

    -- Delete the subplans table entry first since it has a foreign
    -- key constraint on its job_id existing in sysjobs.
    DELETE msdb.dbo.sysmaintplan_subplans 
    WHERE (subplan_id = @subplan_id)

    IF (@delete_jobs = 1)
    BEGIN
        --delete the local job associated with this subplan
        IF (@job IS NOT NULL)
        BEGIN
            EXEC @retval = msdb.dbo.sp_delete_job @job_id = @job, @delete_unused_schedule = 1
            IF (@retval <> 0)
            BEGIN
                ROLLBACK TRAN
                RETURN @retval
            END
        END

        --delete the multi-server job associated with this subplan.
        IF (@jobMsx IS NOT NULL)
        BEGIN 
            EXEC @retval = msdb.dbo.sp_delete_job @job_id = @jobMsx, @delete_unused_schedule = 1
            IF (@retval <> 0)
            BEGIN
                ROLLBACK TRAN
                RETURN @retval
            END
        END
    END

    COMMIT TRAN
    RETURN (0)
END
go

/**************************************************************/
/* SP_MAINTPLAN_UPDATE_SUBPLAN_TSX                            */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_update_subplan_tsx...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_update_subplan_tsx')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_update_subplan_tsx
go
-- This procedure is called when a maintenance plan subplan record
-- needs to be created or updated to match a multi-server Agent job
-- that has arrived from the master server.
CREATE PROCEDURE sp_maintplan_update_subplan_tsx
    @subplan_id    UNIQUEIDENTIFIER,
    @plan_id       UNIQUEIDENTIFIER,
    @name          sysname,
    @description   NVARCHAR(512),
    @job_id        UNIQUEIDENTIFIER
AS
BEGIN
    -- Find out what schedule, if any, is associated with the job.
    declare @schedule_id int
    select @schedule_id = (SELECT TOP(1) schedule_id
                           FROM msdb.dbo.sysjobschedules
                           WHERE (job_id = @job_id) )

    exec sp_maintplan_update_subplan @subplan_id, @plan_id, @name, @description, @job_id, @schedule_id, @allow_create=1

    -- Be sure to mark this subplan as coming from the master, not locally.
    update sysmaintplan_subplans
    set msx_plan = 1
    where subplan_id = @subplan_id
    
END
go

/**************************************************************/
/* SP_MAINTPLAN_SUBPLANS_BY_JOB                               */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_subplans_by_job...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_subplans_by_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_subplans_by_job
go
-- If the given job_id is associated with a maintenance plan,
-- then matching entries from sysmaintplan_subplans are returned.
CREATE PROCEDURE sp_maintplan_subplans_by_job
    @job_id  UNIQUEIDENTIFIER
AS
BEGIN
    select plans.name as 'plan_name', plans.id as 'plan_id', subplans.subplan_name, subplans.subplan_id
    from sysmaintplan_plans plans, sysmaintplan_subplans subplans
    where  plans.id = subplans.plan_id
    and (job_id = @job_id
         or msx_job_id = @job_id)
    order by subplans.plan_id, subplans.subplan_id
END
go


/**************************************************************/
/* sp_maintplan_open_logentry                                 */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_open_logentry...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_open_logentry')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_open_logentry
go
CREATE PROCEDURE sp_maintplan_open_logentry
    @plan_id       UNIQUEIDENTIFIER,
    @subplan_id       UNIQUEIDENTIFIER,   
    @start_time       DATETIME            = NULL,
    @task_detail_id  UNIQUEIDENTIFIER    = NULL OUTPUT
AS
BEGIN

   --Set defaults
   IF (@start_time IS NULL)
   BEGIN
      SELECT @start_time = GETDATE()
   END

   SELECT @task_detail_id = NEWID()

   --Insert a new record into sysmaintplan_log table
   INSERT INTO msdb.dbo.sysmaintplan_log(task_detail_id, plan_id, subplan_id, start_time)
    VALUES(@task_detail_id, @plan_id, @subplan_id, @start_time)

   RETURN (@@ERROR)
END
GO


/**************************************************************/
/* sp_maintplan_close_logentry                                */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_close_logentry...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_close_logentry')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_close_logentry
go
CREATE PROCEDURE sp_maintplan_close_logentry
    @task_detail_id     UNIQUEIDENTIFIER,
    @end_time          DATETIME            = NULL,
    @succeeded         TINYINT
AS
BEGIN

   --Set defaults
   IF (@end_time IS NULL)
   BEGIN
      SELECT @end_time = GETDATE()
   END

    -- Raise an error if the @task_detail_id doesn't exist
    IF( NOT EXISTS(SELECT * FROM sysmaintplan_log WHERE (task_detail_id = @task_detail_id)))
   BEGIN
        DECLARE @task_detail_id_as_char VARCHAR(36)
        SELECT @task_detail_id_as_char = CONVERT(VARCHAR(36), @task_detail_id)
        RAISERROR(14262, -1, -1, '@task_detail_id', @task_detail_id_as_char)
      RETURN(1)
   END

   UPDATE msdb.dbo.sysmaintplan_log 
    SET end_time = @end_time, succeeded = @succeeded 
    WHERE (task_detail_id = @task_detail_id)

    RETURN (@@ERROR)
END
go



/**************************************************************/
/* sp_maintplan_update_log                                    */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_update_log...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_update_log')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_update_log
go
CREATE PROCEDURE sp_maintplan_update_log
    --Updates the log_details table
    @task_detail_id      UNIQUEIDENTIFIER,       --Required
    @Line1              NVARCHAR(256),       --Required
    @Line2              NVARCHAR(256)   = NULL,
    @Line3              NVARCHAR(256)   = NULL,
    @Line4              NVARCHAR(256)   = NULL,
    @Line5              NVARCHAR(256)   = NULL,
    @server_name      sysname,            --Required
    @succeeded         TINYINT,           --Required
    @start_time           DATETIME,          --Required
    @end_time          DATETIME,          --Required
    @error_number     int=NULL,
    @error_message       NVARCHAR(max)   = NULL,
    @command           NVARCHAR(max)   = NULL
AS
BEGIN

   --Prep strings
   SET NOCOUNT ON
   SELECT @Line1 = LTRIM(RTRIM(@Line1))
   SELECT @Line2 = LTRIM(RTRIM(@Line2))
   SELECT @Line3 = LTRIM(RTRIM(@Line3))
   SELECT @Line4 = LTRIM(RTRIM(@Line4))
   SELECT @Line5 = LTRIM(RTRIM(@Line5))

   INSERT INTO msdb.dbo.sysmaintplan_logdetail(
        task_detail_id, 
        line1,
        line2, 
        line3, 
        line4, 
        line5, 
        server_name, 
        start_time, 
        end_time, 
        error_number, 
        error_message, 
        command, 
        succeeded)
   VALUES(
        @task_detail_id,
        @Line1,
        @Line2,
        @Line3,
        @Line4,
        @Line5,
        @server_name,
        @start_time,
        @end_time,
        @error_number,
        @error_message,
        @command,
        @succeeded)

    RETURN (@@ERROR)
END
GO



/**************************************************************/
/* sp_maintplan_update_subplan                                */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_update_subplan...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_update_subplan')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_update_subplan
go
CREATE PROCEDURE sp_maintplan_update_subplan
    @subplan_id       UNIQUEIDENTIFIER,
    @plan_id       UNIQUEIDENTIFIER    = NULL,
    @name          sysname             = NULL,
    @description  NVARCHAR(512)       = NULL,
    @job_id        UNIQUEIDENTIFIER    = NULL,
    @schedule_id  INT                 = NULL,
    @allow_create   BIT                 = 0,
    @msx_job_id    UNIQUEIDENTIFIER    = NULL
AS
BEGIN

   SET NOCOUNT ON

   SELECT @name = LTRIM(RTRIM(@name))
   SELECT @description = LTRIM(RTRIM(@description))

   --Are we creating a new entry or updating an existing one?

   IF( NOT EXISTS(SELECT * FROM msdb.dbo.sysmaintplan_subplans WHERE subplan_id = @subplan_id) )
   BEGIN
        -- Only allow creation of a record if user permits it
        IF(@allow_create = 0)
        BEGIN
            DECLARE @subplan_id_as_char VARCHAR(36)
            SELECT @subplan_id_as_char = CONVERT(VARCHAR(36), @subplan_id)
            RAISERROR(14262, -1, -1, '@subplan_id', @subplan_id_as_char)
          RETURN(1)
        END

        --Insert it's a new subplan
      IF (@name IS NULL)
      BEGIN
          RAISERROR(12981, -1, -1, '@name')
         RETURN(1) -- Failure
      END

      IF (@plan_id IS NULL)
      BEGIN
          RAISERROR(12981, -1, -1, '@plan_id')
         RETURN(1) -- Failure
      END

      INSERT INTO msdb.dbo.sysmaintplan_subplans(
            subplan_id,
            plan_id,
            subplan_description,
            subplan_name,
            job_id,
            schedule_id,
            msx_job_id)
      VALUES(
            @subplan_id,
            @plan_id,
            @description,
            @name,
            @job_id,
            @schedule_id,
            @msx_job_id)

   END
   ELSE
   BEGIN --Update the table

      DECLARE @s_subplan_name sysname
      DECLARE @s_job_id UNIQUEIDENTIFIER

      SELECT @s_subplan_name         = subplan_name,
            @s_job_id               = job_id
      FROM msdb.dbo.sysmaintplan_subplans
      WHERE (@subplan_id = subplan_id)

      --Determine if user wants to change these variables
      IF (@name IS NOT NULL)          SELECT @s_subplan_name          = @name
      IF (@job_id IS NOT NULL)        SELECT @s_job_id                = @job_id

      --UPDATE the record

      UPDATE msdb.dbo.sysmaintplan_subplans 
        SET subplan_name        = @s_subplan_name,
            subplan_description = @description,
            job_id              = @s_job_id,
            schedule_id         = @schedule_id,
            msx_job_id          = @msx_job_id
      WHERE (subplan_id = @subplan_id)

   END

    RETURN (@@ERROR)
END
GO


/**************************************************************/
/* sp_maintplan_delete_plan                                   */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_delete_plan...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_delete_plan')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_delete_plan
go
CREATE PROCEDURE sp_maintplan_delete_plan
    @plan_id   UNIQUEIDENTIFIER
AS
BEGIN
   SET NOCOUNT ON

   DECLARE @sp_id UNIQUEIDENTIFIER
    DECLARE @retval     INT

    SET @retval = 0

   --Loop through Subplans
   DECLARE sp CURSOR LOCAL FOR 
        SELECT subplan_id 
        FROM msdb.dbo.sysmaintplan_subplans 
        WHERE plan_id = @plan_id FOR READ ONLY

   OPEN sp
   FETCH NEXT FROM sp INTO @sp_id
   WHILE @@FETCH_STATUS = 0
   BEGIN 
     EXECUTE @retval = sp_maintplan_delete_subplan @subplan_id = @sp_id
      IF(@retval <> 0)
        BREAK

     FETCH NEXT FROM sp INTO @sp_id
   END
   CLOSE sp
   DEALLOCATE sp

    RETURN (@retval)
END
go



/**************************************************************/
/* sp_maintplan_start                                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_maintplan_start...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_maintplan_start')
              AND (type = 'P')))
  DROP PROCEDURE sp_maintplan_start
go
CREATE PROCEDURE sp_maintplan_start
    @plan_id        UNIQUEIDENTIFIER    = NULL,
    @subplan_id     UNIQUEIDENTIFIER    = NULL
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @jobid  UNIQUEIDENTIFIER
    DECLARE @retval INT
    SET @retval = 0

    -- A @plan_id or @subplan_id must be supplied
   IF (@plan_id IS NULL) AND (@subplan_id IS NULL)
   BEGIN
      RAISERROR(12982, -1, -1, '@plan_id', '@subplan_id')
      RETURN(1)
   END

    -- either @plan_id or @subplan_id must be exclusively set
   IF (@plan_id IS NOT NULL) AND (@subplan_id IS NOT NULL)
   BEGIN
      RAISERROR(12982, -1, -1, '@plan_id', '@subplan_id')
      RETURN(1)
   END

    IF (@subplan_id IS NOT NULL)
    BEGIN 
        -- subplan_id supplied so simply start the subplan's job

        SELECT @jobid = job_id 
        FROM msdb.dbo.sysmaintplan_subplans 
        WHERE subplan_id = @subplan_id 

        if(@jobid IS NOT NULL)
        BEGIN
            EXEC @retval = msdb.dbo.sp_start_job @job_id = @jobid
        END

    END
    ELSE
    BEGIN
        -- Loop through Subplans and fire off all associated jobs
       DECLARE spj CURSOR LOCAL FOR 
            SELECT job_id
            FROM msdb.dbo.sysmaintplan_subplans 
            WHERE plan_id = @plan_id FOR READ ONLY

       OPEN spj
       FETCH NEXT FROM spj INTO @jobid
       WHILE (@@FETCH_STATUS = 0)
       BEGIN 
           EXEC @retval = msdb.dbo.sp_start_job @job_id = @jobid
            IF(@retval <> 0)
                BREAK

           FETCH NEXT FROM spj INTO @jobid
       END

       CLOSE spj
       DEALLOCATE spj

    END

    RETURN (@retval)
END
GO


/*==================================================================*/
--TODO: The following SYSDBMAINT... tables and SP's will be removed  
/*==================================================================*/


/**************************************************************/
/* SYSDBMAINTPLANS                                            */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysdbmaintplans')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysdbmaintplans...'

  CREATE TABLE sysdbmaintplans
  (
  plan_id                    UNIQUEIDENTIFIER NOT NULL PRIMARY KEY CLUSTERED,
  plan_name                  sysname          NOT NULL,
  date_created               DATETIME         NOT NULL DEFAULT (GETDATE()),
  owner                      sysname          NOT NULL DEFAULT (ISNULL(NT_CLIENT(), SUSER_SNAME())),
  max_history_rows           INT              NOT NULL DEFAULT (0),
  remote_history_server      sysname          NOT NULL DEFAULT (''),
  max_remote_history_rows    INT              NOT NULL DEFAULT (0),
  user_defined_1             INT              NULL,
  user_defined_2             NVARCHAR(100)    NULL,
  user_defined_3             DATETIME         NULL,
  user_defined_4             UNIQUEIDENTIFIER NULL
  )
END
go

-- Add row for "plan 0"
IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysdbmaintplans
                WHERE (plan_id = CONVERT(UNIQUEIDENTIFIER, 0x00))))
  INSERT INTO sysdbmaintplans(plan_id, plan_name, owner) VALUES (0x00, N'All ad-hoc plans', N'sa')
go

/**************************************************************/
/* SYSDBMAINTPLAN_JOBS                                        */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysdbmaintplan_jobs')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysdbmaintplan_jobs...'

  CREATE TABLE sysdbmaintplan_jobs
  (
  plan_id UNIQUEIDENTIFIER NOT NULL UNIQUE CLUSTERED (plan_id, job_id)
                                    FOREIGN KEY REFERENCES msdb.dbo.sysdbmaintplans (plan_id),
  job_id  UNIQUEIDENTIFIER NOT NULL
  )
END
go

/**************************************************************/
/* SYSDBMAINTPLAN_DATABASES                                   */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysdbmaintplan_databases')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysdbmaintplan_databases...'

  CREATE TABLE sysdbmaintplan_databases
  (
  plan_id       UNIQUEIDENTIFIER NOT NULL UNIQUE CLUSTERED (plan_id, database_name)
                                          FOREIGN KEY REFERENCES msdb.dbo.sysdbmaintplans (plan_id),
  database_name sysname          NOT NULL
  )
END
go

/**************************************************************/
/* SYSDBMAINTPLAN_HISTORY                                     */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sysdbmaintplan_history')
                  AND (type = 'U')))
BEGIN
  PRINT ''
  PRINT 'Creating table sysdbmaintplan_history...'

  CREATE TABLE sysdbmaintplan_history
  (
  sequence_id    INT               NOT NULL IDENTITY UNIQUE NONCLUSTERED,
  plan_id        UNIQUEIDENTIFIER  NOT NULL DEFAULT('00000000-0000-0000-0000-000000000000'),
  plan_name      sysname           NOT NULL DEFAULT('All ad-hoc plans'),
  database_name  sysname           NULL,
  server_name    sysname           NOT NULL DEFAULT (CONVERT(sysname, ServerProperty('ServerName'))),
  activity       NVARCHAR(128)     NULL,
  succeeded      BIT               NOT NULL DEFAULT (1),
  end_time       DATETIME          NOT NULL DEFAULT (GETDATE()),
  duration       INT               NULL     DEFAULT (0),
  start_time     AS                DATEADD (ss, -duration, end_time),
  error_number   INT               NOT NULL DEFAULT (0),
  message        NVARCHAR(512)     NULL
  )

  CREATE CLUSTERED INDEX clust ON sysdbmaintplan_history(plan_id)
END
-- ALTER TABLE to correct default constraint 
ELSE
BEGIN
  DECLARE @t TABLE
  (
  constraint_type         NVARCHAR(146)  COLLATE database_default NULL,
  constraint_name         sysname        COLLATE database_default NULL,
  delete_action           NVARCHAR(20)   COLLATE database_default NULL,
  update_action           NVARCHAR(20)   COLLATE database_default NULL,
  status_enabled          NVARCHAR(20)   COLLATE database_default NULL,
  status_for_replication  NVARCHAR(20)   COLLATE database_default NULL,
  constraint_keys         NVARCHAR(2126) COLLATE database_default NULL
  )

  INSERT INTO @t EXEC sp_helpconstraint N'sysdbmaintplan_history', 'nomsg'

  DECLARE @constraint_name sysname
  DECLARE @sql NVARCHAR(4000)

  SELECT @constraint_name = constraint_name 
  FROM   @t 
  WHERE  constraint_type = N'DEFAULT on column server_name' 
  AND    constraint_keys = N'(@@servername)'

  -- default found
  IF (@constraint_name IS NOT NULL)
  BEGIN
    PRINT ''
    PRINT 'Alter sysdbmaintplan_history ...'
    SELECT @sql = N'ALTER TABLE sysdbmaintplan_history DROP CONSTRAINT ' + QUOTENAME(@constraint_name)
    EXEC (@sql)

    ALTER TABLE sysdbmaintplan_history 
      ADD CONSTRAINT servername_default DEFAULT (CONVERT(sysname, ServerProperty('ServerName')))
      FOR server_name
  END
END
go

/**************************************************************/
/* SPs for the maintenance plans                              */
/**************************************************************/
/**************************************************************/
/* sp_clear_dbmaintplan_by_db                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_clear_dbmaintplan_by_db...'
GO
IF (EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sp_clear_dbmaintplan_by_db') AND (type = 'P')))
  DROP PROCEDURE sp_clear_dbmaintplan_by_db
GO
CREATE PROCEDURE sp_clear_dbmaintplan_by_db
  @db_name sysname
AS
BEGIN
  DECLARE planid_cursor CURSOR
  FOR
  select plan_id from msdb.dbo.sysdbmaintplan_databases where database_name=@db_name
  OPEN planid_cursor
  declare @planid uniqueidentifier
  FETCH NEXT FROM planid_cursor INTO @planid
  WHILE (@@FETCH_STATUS <> -1)
  BEGIN
    IF (@@FETCH_STATUS <> -2)
    BEGIN
      delete from msdb.dbo.sysdbmaintplan_databases where plan_id=@planid AND database_name=@db_name
      if (NOT EXISTS(select * from msdb.dbo.sysdbmaintplan_databases where plan_id=@planid))
      BEGIN
        --delete the job
        DECLARE jobid_cursor CURSOR
        FOR
        select job_id from msdb.dbo.sysdbmaintplan_jobs where plan_id=@planid
        OPEN jobid_cursor
        DECLARE @jobid uniqueidentifier
        FETCH NEXT FROM jobid_cursor INTO @jobid
        WHILE (@@FETCH_STATUS <> -1)
        BEGIN
          if (@@FETCH_STATUS <> -2)
          BEGIN
            execute msdb.dbo.sp_delete_job @jobid
          END
          FETCH NEXT FROM jobid_cursor into @jobid
        END
        CLOSE jobid_cursor
        DEALLOCATE jobid_cursor
        --delete the history
        delete from msdb.dbo.sysdbmaintplan_history where plan_id=@planid
        --delete the plan
        delete from msdb.dbo.sysdbmaintplans where plan_id=@planid
      END
    END
    FETCH NEXT FROM planid_cursor INTO @planid
  END
  CLOSE planid_cursor
  DEALLOCATE planid_cursor
END
GO

/**************************************************************/
/* sp_add_maintenance_plan                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_add_maintenance_plan...'
GO
IF (EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = N'sp_add_maintenance_plan') AND (type = 'P')))
  DROP PROCEDURE sp_add_maintenance_plan
GO
CREATE PROCEDURE sp_add_maintenance_plan
  @plan_name varchar(128),
  @plan_id   UNIQUEIDENTIFIER OUTPUT
AS
BEGIN
  IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysdbmaintplans
                WHERE plan_name=@plan_name))
    BEGIN
      SELECT @plan_id=NEWID()
      INSERT INTO msdb.dbo.sysdbmaintplans (plan_id, plan_name) VALUES (@plan_id, @plan_name)
    END
  ELSE
    BEGIN
      RAISERROR(14261,-1,-1,'@plan_name',@plan_name)
      RETURN(1) -- failure
    END
END
GO

/**************************************************************/
/* sp_delete_maintenance_plan                                 */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_maintenance_plan...'
GO
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_maintenance_plan')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_maintenance_plan
GO
CREATE PROCEDURE sp_delete_maintenance_plan
  @plan_id UNIQUEIDENTIFIER
AS
BEGIN
  /*check if the plan_id is valid*/
  IF (NOT EXISTS(SELECT *
                 FROM sysdbmaintplans
                 WHERE plan_id=@plan_id))
  BEGIN
    DECLARE @syserr VARCHAR(100)
    SELECT @syserr=CONVERT(VARCHAR(100),@plan_id)
    RAISERROR(14262,-1,-1,'@plan_id',@syserr)
    RETURN(1)
  END
  /* clean the related records in sysdbmaintplan_database */
  DELETE FROM msdb.dbo.sysdbmaintplan_databases
  WHERE plan_id=@plan_id
  /* clean the related records in sysdbmaintplan_jobs*/
  DELETE FROM msdb.dbo.sysdbmaintplan_jobs
  WHERE plan_id=@plan_id
  /* clean sysdbmaintplans */
  DELETE FROM msdb.dbo.sysdbmaintplans
  WHERE  plan_id= @plan_id
END
GO

/**************************************************************/
/* sp_add_maintenance_plan_db                                 */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_add_maintenance_plan_db...'
GO
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_maintenance_plan_db')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_maintenance_plan_db
GO
CREATE PROCEDURE sp_add_maintenance_plan_db
  @plan_id UNIQUEIDENTIFIER,
  @db_name sysname
AS
BEGIN
  DECLARE @syserr VARCHAR(100)
  /*check if the plan_id is valid */
  IF (NOT EXISTS (SELECT plan_id
              FROM  msdb.dbo.sysdbmaintplans
              WHERE plan_id=@plan_id))
  BEGIN
    SELECT @syserr=CONVERT(VARCHAR(100),@plan_id)
    RAISERROR(14262,-1,-1,'@plan_id',@syserr)
    RETURN(1)
  END
  /*check if the database name is valid */
  IF (NOT EXISTS (SELECT name
              FROM master.dbo.sysdatabases
              WHERE name=@db_name))
   BEGIN
    RAISERROR(14262,-1,-1,'@db_name',@db_name)
    RETURN(1)
  END
  /*check if the (plan_id, database) pair already exists*/
  IF (EXISTS (SELECT *
              FROM sysdbmaintplan_databases
              WHERE plan_id=@plan_id AND database_name=@db_name))
  BEGIN
    SELECT @syserr=CONVERT(VARCHAR(100),@plan_id)+' + '+@db_name
    RAISERROR(14261,-1,-1,'@plan_id+@db_name',@syserr)
    RETURN(1)
  END
  INSERT INTO msdb.dbo.sysdbmaintplan_databases (plan_id,database_name) VALUES (@plan_id, @db_name)
END
GO

/**************************************************************/
/* sp_delete_maintenance_plan_db                              */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_delete_maintenance_plan_db...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_maintenance_plan_db')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_maintenance_plan_db
go
CREATE PROCEDURE sp_delete_maintenance_plan_db
  @plan_id uniqueidentifier,
  @db_name sysname
AS
BEGIN
  /*check if the (plan_id, db_name) exists in the table*/
  IF (NOT EXISTS(SELECT *
                 FROM msdb.dbo.sysdbmaintplan_databases
                 WHERE @plan_id=plan_id AND @db_name=database_name))
  BEGIN
    DECLARE @syserr VARCHAR(300)
    SELECT @syserr=CONVERT(VARCHAR(100),@plan_id)+' + '+@db_name
    RAISERROR(14262,-1,-1,'@plan_id+@db_name',@syserr)
    RETURN(1)
  END
  /*delete the pair*/
  DELETE FROM msdb.dbo.sysdbmaintplan_databases
  WHERE plan_id=@plan_id AND database_name=@db_name
END
GO

/**************************************************************/
/* sp_add_maintenance_plan_job                                */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_add_maintenance_plan_job...'
GO
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_add_maintenance_plan_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_add_maintenance_plan_job
GO
CREATE PROCEDURE sp_add_maintenance_plan_job
  @plan_id UNIQUEIDENTIFIER,
  @job_id  UNIQUEIDENTIFIER
AS
BEGIN
  DECLARE @syserr varchar(100)
  /*check if the @plan_id is valid*/
  IF (NOT EXISTS(SELECT plan_id
                 FROM msdb.dbo.sysdbmaintplans
                 WHERE plan_id=@plan_id))
  BEGIN
    SELECT @syserr=CONVERT(VARCHAR(100),@plan_id)
    RAISERROR(14262,-1,-1,'@plan_id',@syserr)
    RETURN(1)
  END
  /*check if the @job_id is valid*/
  IF (NOT EXISTS(SELECT job_id
                 FROM msdb.dbo.sysjobs
                 WHERE job_id=@job_id))
  BEGIN
    SELECT @syserr=CONVERT(VARCHAR(100),@job_id)
    RAISERROR(14262,-1,-1,'@job_id',@syserr)
    RETURN(1)
  END
  /*check if the job has at least one step calling xp_sqlmaint*/
  DECLARE @maxind INT
  SELECT @maxind=(SELECT MAX(CHARINDEX('xp_sqlmaint', command))
                FROM  msdb.dbo.sysjobsteps
                WHERE @job_id=job_id)
  IF (@maxind<=0)
  BEGIN
    /*print N'Warning: The job is not for maitenance plan.' -- will add the new sysmessage here*/
    SELECT @syserr=CONVERT(VARCHAR(100),@job_id)
    RAISERROR(14199,-1,-1,@syserr)
    RETURN(1)
  END
  INSERT INTO msdb.dbo.sysdbmaintplan_jobs(plan_id,job_id) VALUES (@plan_id, @job_id) --don't have to check duplicate here
END
GO

/**************************************************************/
/* sp_delete_maintenance_plan_job                             */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_delete_maintenance_plan_job...'
GO
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_maintenance_plan_job')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_maintenance_plan_job
GO
CREATE PROCEDURE sp_delete_maintenance_plan_job
  @plan_id uniqueidentifier,
  @job_id  uniqueidentifier
AS
BEGIN
  /*check if the (plan_id, job_id) exists*/
  IF (NOT EXISTS(SELECT *
                 FROM sysdbmaintplan_jobs
                 WHERE @plan_id=plan_id AND @job_id=job_id))
  BEGIN
    DECLARE @syserr VARCHAR(300)
    SELECT @syserr=CONVERT(VARCHAR(100),@plan_id)+' + '+CONVERT(VARCHAR(100),@job_id)
    RAISERROR(14262,-1,-1,'@plan_id+@job_id',@syserr)
    RETURN(1)
  END
  DELETE FROM msdb.dbo.sysdbmaintplan_jobs
  WHERE plan_id=@plan_id AND job_id=@job_id
END
GO

/**************************************************************/
/* sp_help_maintenance_plan                                   */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_help_maintenance_plan...'
GO
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_help_maintenance_plan')
              AND (type = 'P')))
  DROP PROCEDURE sp_help_maintenance_plan
GO
CREATE PROCEDURE sp_help_maintenance_plan
  @plan_id UNIQUEIDENTIFIER = NULL
AS
BEGIN
  IF (@plan_id IS NOT NULL)
    BEGIN
      /*return the information about the plan itself*/
      SELECT *
      FROM msdb.dbo.sysdbmaintplans
      WHERE plan_id=@plan_id
      /*return the information about databases this plan defined on*/
      SELECT database_name
      FROM msdb.dbo.sysdbmaintplan_databases
      WHERE plan_id=@plan_id
      /*return the information about the jobs that relating to the plan*/
      SELECT job_id
      FROM msdb.dbo.sysdbmaintplan_jobs
      WHERE plan_id=@plan_id
    END
  ELSE
    BEGIN
      SELECT *
      FROM msdb.dbo.sysdbmaintplans
    END
END
GO

/**************************************************************/
/**                                                          **/
/**       B A C K U P   H I S T O R Y   S U P P O R T        **/
/**                                                          **/
/**************************************************************/

/**************************************************************/
/* T A B L E S                                                */
/**************************************************************/

/**************************************************************/
/* BACKUPMEDIASET                                             */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'backupmediaset')))
BEGIN
  PRINT ''
  PRINT 'Creating table backupmediaset...'

  CREATE TABLE backupmediaset
  (
  media_set_id       INT IDENTITY     NOT NULL PRIMARY KEY,
  media_uuid         UNIQUEIDENTIFIER NULL,  -- Null if this media set only one media family
  media_family_count TINYINT          NULL,  -- Number of media families in the media set
  name               NVARCHAR(128)    NULL,
  description        NVARCHAR(255)    NULL,
  software_name      NVARCHAR(128)    NULL,
  software_vendor_id INT              NULL,
  MTF_major_version  TINYINT          NULL,
  mirror_count       TINYINT          NULL,   -- number of mirror plexes
  is_password_protected BIT           NULL,
  is_compressed      BIT              NULL,   -- 1 if backup compression was used
  is_encrypted       BIT              NULL    -- 1 if backup encryption was used
  )

  CREATE INDEX backupmediasetuuid ON backupmediaset (media_uuid)
END
ELSE
BEGIN
  IF EXISTS (
    select * from msdb.dbo.syscolumns where name='password_protected' and id =
        (select id from msdb.dbo.sysobjects where name='backupmediaset'))

    ALTER TABLE backupmediaset DROP COLUMN password_protected

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='is_password_protected' and id =
        (select id from msdb.dbo.sysobjects where name='backupmediaset'))

    ALTER TABLE backupmediaset ADD is_password_protected BIT NULL

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='is_compressed' and id =
        (select id from msdb.dbo.sysobjects where name='backupmediaset'))

    ALTER TABLE backupmediaset ADD is_compressed BIT NULL

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='mirror_count' and id =
        (select id from msdb.dbo.sysobjects where name='backupmediaset'))

    ALTER TABLE backupmediaset ADD mirror_count TINYINT NULL

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='is_encrypted' and id =
	  (select id from msdb.dbo.sysobjects where name='backupmediaset'))

    ALTER TABLE backupmediaset ADD is_encrypted BIT NULL

END
go

/**************************************************************/
/* BACKUPMEDIAFAMILY                                          */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'backupmediafamily')))
BEGIN
  PRINT ''
  PRINT 'Creating table backupmediafamily...'

  CREATE TABLE backupmediafamily
  (
  media_set_id           INT              NOT NULL REFERENCES backupmediaset(media_set_id),
  family_sequence_number TINYINT          NOT NULL, -- Raid sequence number
  media_family_id        UNIQUEIDENTIFIER NULL,     -- This will be a uuid in MTF 2.0, allow space
  media_count            INT              NULL,     -- Number of media in the family
  logical_device_name    NVARCHAR(128)    NULL,     -- Name from sysdevices, if any
  physical_device_name   NVARCHAR(260)    NULL,     -- To facilitate restores from online media (disk)
  device_type            TINYINT          NULL,  -- Disk, tape, pipe, ...
  physical_block_size    INT              NULL,
  mirror             TINYINT        DEFAULT 0 NOT NULL
  PRIMARY KEY (media_set_id, family_sequence_number, mirror)
  )

  CREATE INDEX backupmediafamilyuuid ON backupmediafamily (media_family_id)
END
ELSE
BEGIN
  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='mirror' and id =
        (select id from msdb.dbo.sysobjects where name='backupmediafamily'))
  BEGIN
    begin tran

    -- remove any old constraint, not involving mirror
   declare @pkName sysname
   DECLARE @sql NVARCHAR(4000)
   select @pkName=i.name
      from
         sys.indexes i,
         sys.all_objects o
      where
         o.object_id = object_id ('backupmediafamily')
         and o.object_id = i.object_id
         and i.is_primary_key = 1
   IF (@pkName IS NOT NULL)
   begin
      select @sql = N'ALTER TABLE backupmediafamily DROP CONSTRAINT ' + QUOTENAME(@pkName)
      EXEC (@sql)
   end

   ALTER TABLE backupmediafamily ADD mirror TINYINT DEFAULT 0 NOT NULL

   ALTER TABLE backupmediafamily ADD CONSTRAINT backupmediafamily_PK
      PRIMARY KEY (media_set_id, family_sequence_number, mirror)

   commit    
  END
END
go

/**************************************************************/
/* BACKUPSET - One row per backup operation.                  */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'backupset')))
BEGIN
  PRINT ''
  PRINT 'Creating table backupset...'

  CREATE TABLE backupset
  (
  backup_set_id          INT IDENTITY     NOT NULL PRIMARY KEY,
  backup_set_uuid        UNIQUEIDENTIFIER NOT NULL,
  media_set_id           INT              NOT NULL REFERENCES backupmediaset(media_set_id),
  first_family_number    TINYINT          NULL,  -- family number & media number of the media
  first_media_number     SMALLINT         NULL,  -- containing the start of this backup (first SSET)
  last_family_number     TINYINT          NULL,  -- family number & media number of the media
  last_media_number      SMALLINT         NULL,  -- containing the end of this backup (ESET after MBC)
  catalog_family_number  TINYINT          NULL,  -- family number & media number of the media
  catalog_media_number   SMALLINT         NULL,  -- containing the start of the 'directory' data stream

  position               INT              NULL,  -- For FILE=
  expiration_date        DATETIME         NULL,

  -- From SSET...
  software_vendor_id     INT              NULL,  -- Might want table for sw vendors
  name                   NVARCHAR(128)    NULL,
  description            NVARCHAR(255)    NULL,
  user_name              NVARCHAR(128)    NULL,
  software_major_version TINYINT          NULL, 
  software_minor_version TINYINT          NULL, 
  software_build_version SMALLINT         NULL,
  time_zone              SMALLINT         NULL,    
  mtf_minor_version      TINYINT          NULL,

  -- From CONFIG_INFO...
  first_lsn              NUMERIC(25,0)    NULL,
  last_lsn               NUMERIC(25,0)    NULL,
  checkpoint_lsn         NUMERIC(25,0)    NULL,
  database_backup_lsn    NUMERIC(25,0)    NULL,
  database_creation_date DATETIME         NULL,
  backup_start_date      DATETIME         NULL,
  backup_finish_date     DATETIME         NULL,
  type                   CHAR(1)          NULL,
  sort_order             SMALLINT         NULL,
  code_page              SMALLINT         NULL,
  compatibility_level    TINYINT          NULL,
  database_version       INT              NULL,
  backup_size            NUMERIC(20,0)    NULL,
  database_name          NVARCHAR(128)    NULL,
  server_name            NVARCHAR(128)    NULL,
  machine_name           NVARCHAR(128)    NULL,
  flags                  INT              NULL,
  unicode_locale         INT              NULL,
  unicode_compare_style  INT              NULL,
  collation_name         NVARCHAR(128)    NULL,
  is_password_protected  BIT              NULL,
  recovery_model         NVARCHAR(60)     NULL,
  has_bulk_logged_data   BIT              NULL,
  is_snapshot            BIT              NULL,
  is_readonly            BIT              NULL,
  is_single_user         BIT              NULL,
  has_backup_checksums   BIT              NULL,
  is_damaged             BIT              NULL,
  begins_log_chain       BIT              NULL,
  has_incomplete_metadata BIT             NULL,
  is_force_offline       BIT              NULL,
  is_copy_only           BIT              NULL,
  first_recovery_fork_guid UNIQUEIDENTIFIER NULL,
  last_recovery_fork_guid UNIQUEIDENTIFIER NULL,
  fork_point_lsn         NUMERIC(25,0)    NULL,
  database_guid          UNIQUEIDENTIFIER NULL,
  family_guid            UNIQUEIDENTIFIER NULL,
  differential_base_lsn  NUMERIC(25,0)    NULL,
  differential_base_guid UNIQUEIDENTIFIER NULL,
  compressed_backup_size NUMERIC(20,0)    NULL,
  key_algorithm          NVARCHAR(32)     NULL,
  encryptor_thumbprint   VARBINARY(20)    NULL,
  encryptor_type         NVARCHAR(32)     NULL
  )

  CREATE INDEX backupsetuuid ON backupset (backup_set_uuid)
  CREATE INDEX backupsetDate ON backupset (backup_finish_date) -- helps sp_delete_backuphistory
  CREATE INDEX backupsetMediaSetId ON backupset (media_set_id) -- helps sp_delete_backuphistory
  CREATE INDEX backupsetDatabaseName ON backupset (database_name) INCLUDE (backup_set_id, media_set_id) -- helps sp_delete_backuphistory
END
ELSE
BEGIN
  IF EXISTS (
    select * from msdb.dbo.syscolumns where name='password_protected' and id =
        (select id from msdb.dbo.sysobjects where name='backupset'))

    ALTER TABLE backupset DROP COLUMN password_protected

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='flags' and id =
        (select id from msdb.dbo.sysobjects where name='backupset'))

    ALTER TABLE backupset ADD flags INT NULL

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='collation_name' and id =
        (select id from msdb.dbo.sysobjects where name='backupset'))

    ALTER TABLE backupset ADD
      unicode_locale         INT              NULL,
      unicode_compare_style  INT              NULL,
      collation_name         NVARCHAR(128)    NULL

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='is_password_protected' and id =
        (select id from msdb.dbo.sysobjects where name='backupset'))

    ALTER TABLE backupset ADD is_password_protected BIT NULL

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='compressed_backup_size' and id =
        (select id from msdb.dbo.sysobjects where name='backupset'))

    ALTER TABLE backupset ADD compressed_backup_size NUMERIC(20,0) NULL

  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='recovery_model' and id =
        (select id from msdb.dbo.sysobjects where name='backupset'))
   ALTER TABLE backupset ADD
      recovery_model         NVARCHAR(60)     NULL,
      has_bulk_logged_data   BIT              NULL,
      is_snapshot            BIT              NULL,
      is_readonly            BIT              NULL,
      is_single_user         BIT              NULL,
      has_backup_checksums   BIT              NULL,
      is_damaged             BIT              NULL,
      begins_log_chain       BIT              NULL,
      has_incomplete_metadata BIT             NULL,
      is_force_offline       BIT              NULL,
      is_copy_only           BIT              NULL,
      first_recovery_fork_guid UNIQUEIDENTIFIER NULL,
      last_recovery_fork_guid UNIQUEIDENTIFIER NULL,
      fork_point_lsn         NUMERIC(25,0)    NULL,
      database_guid          UNIQUEIDENTIFIER NULL,
      family_guid            UNIQUEIDENTIFIER NULL,
      differential_base_lsn  NUMERIC(25,0)    NULL,
      differential_base_guid UNIQUEIDENTIFIER NULL

  IF NOT EXISTS (
      select * from msdb.dbo.syscolumns where name='key_algorithm' and id =
        (select id from msdb.dbo.sysobjects where name='backupset'))

    ALTER TABLE backupset ADD
        key_algorithm        NVARCHAR(32)  NULL,
        encryptor_thumbprint VARBINARY(20) NULL,
        encryptor_type       NVARCHAR(32)  NULL

  IF NOT EXISTS (
    SELECT *
      FROM msdb.sys.indexes
      WHERE (name = 'backupsetDate'))
    CREATE INDEX backupsetDate ON backupset (backup_finish_date)
  IF NOT EXISTS (
      SELECT *
      FROM msdb.sys.indexes
      WHERE (name = 'backupsetMediaSetId'))
    CREATE INDEX backupsetMediaSetId ON backupset (media_set_id)
 IF NOT EXISTS (
      SELECT *
      FROM msdb.sys.indexes
      WHERE (name = 'backupsetDatabaseName'))
    CREATE INDEX backupsetDatabaseName ON backupset (database_name) INCLUDE (backup_set_id, media_set_id)

END
go

/**************************************************************/
-- BACKUPFILE/FILEGROUP 
-- One row per file/filegroup backed up
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'backupfilegroup')))
BEGIN
  PRINT ''
  PRINT 'Creating table backupfilegroup...'

  CREATE TABLE backupfilegroup
  (
  backup_set_id          INT           NOT NULL REFERENCES backupset(backup_set_id),
  name                   NVARCHAR(128) NOT NULL,
  filegroup_id           INT           NOT NULL,
  filegroup_guid         UNIQUEIDENTIFIER NULL,   
  type                   CHAR(2)       NOT NULL,
  type_desc              NVARCHAR(60)  NOT NULL,
  is_default             BIT           NOT NULL,
  is_readonly            BIT           NOT NULL,
  log_filegroup_guid     UNIQUEIDENTIFIER NULL
  PRIMARY KEY (backup_set_id, filegroup_id)
  )
END
go

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'backupfile')))
BEGIN
  PRINT ''
  PRINT 'Creating table backupfile...'

  CREATE TABLE backupfile
  (
  backup_set_id          INT           NOT NULL REFERENCES backupset(backup_set_id),
  first_family_number    TINYINT       NULL,     -- Family number & media number of he first media
  first_media_number     SMALLINT      NULL,     -- containing this file
  filegroup_name         NVARCHAR(128) NULL,
  page_size              INT           NULL,
  file_number            NUMERIC(10,0) NOT NULL,
  backed_up_page_count   NUMERIC(10,0) NULL,
  file_type              CHAR(1)       NULL,     -- database or log
  source_file_block_size NUMERIC(10,0) NULL,
  file_size              NUMERIC(20,0) NULL,
  logical_name           NVARCHAR(128) NULL,
  physical_drive         NVARCHAR(260) NULL,     -- Drive or partition name
  physical_name          NVARCHAR(260) NULL,     -- Remainder of physical (OS) filename
  state                  TINYINT       NULL,
  state_desc             NVARCHAR(64)  NULL,
  create_lsn             NUMERIC(25,0) NULL,
  drop_lsn               NUMERIC(25,0) NULL,
  file_guid              UNIQUEIDENTIFIER NULL,
  read_only_lsn          NUMERIC(25,0) NULL,
  read_write_lsn         NUMERIC(25,0) NULL,
  differential_base_lsn  NUMERIC(25,0) NULL,
  differential_base_guid UNIQUEIDENTIFIER NULL,
  backup_size            NUMERIC(20,0) NULL,
  filegroup_guid         UNIQUEIDENTIFIER NULL,
  is_readonly            BIT NULL,
  is_present             BIT NULL
  PRIMARY KEY (backup_set_id, file_number)
  )
END
ELSE
BEGIN
  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='state' and id =
        (select id from msdb.dbo.sysobjects where name='backupfile'))
  BEGIN
    -- we want NVARCHAR instead of VARCHAR
   ALTER TABLE backupfile ALTER COLUMN
      physical_drive         NVARCHAR(260) NULL
   ALTER TABLE backupfile ALTER COLUMN
      physical_name          NVARCHAR(260) NULL

    ALTER TABLE backupfile ADD
     state                  TINYINT       NULL,
     state_desc             NVARCHAR(64)  NULL,
     create_lsn             NUMERIC(25,0) NULL,
     drop_lsn               NUMERIC(25,0) NULL,
     file_guid              UNIQUEIDENTIFIER NULL,
     read_only_lsn          NUMERIC(25,0) NULL,
     read_write_lsn         NUMERIC(25,0) NULL,
     differential_base_lsn  NUMERIC(25,0) NULL,
     differential_base_guid UNIQUEIDENTIFIER NULL,
     backup_size            NUMERIC(20,0) NULL,
     filegroup_guid         UNIQUEIDENTIFIER NULL,
     is_readonly            BIT NULL,
     is_present             BIT NULL
  END
END
go

/**************************************************************/
/* RESTOREHISTORY - One row per restore operation.            */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'restorehistory')))
BEGIN
  PRINT ''
  PRINT 'Creating table restorehistory...'

  CREATE TABLE restorehistory
  (
  restore_history_id        INT           NOT NULL IDENTITY PRIMARY KEY,
  restore_date              DATETIME      NULL,
  destination_database_name NVARCHAR(128) NULL,
  user_name                 NVARCHAR(128) NULL,
  backup_set_id             INT           NOT NULL REFERENCES backupset(backup_set_id), -- The backup set restored
  restore_type              CHAR(1)       NULL,      -- Database, file, filegroup, log, verifyonly, ...

  -- Various options...
  replace                   BIT           NULL,      -- Replace(1), Noreplace(0)
  recovery                  BIT           NULL,      -- Recovery(1), Norecovery(0)
  restart                   BIT           NULL,      -- Restart(1), Norestart(0)
  stop_at                   DATETIME      NULL,
  device_count              TINYINT       NULL,      -- Can be less than number of media families
  stop_at_mark_name         NVARCHAR(128) NULL,
  stop_before               BIT           NULL
  )

  CREATE INDEX restorehistorybackupset ON restorehistory (backup_set_id)
END

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.syscolumns
                WHERE (name in ('stop_at_mark_name', 'stop_before'))
                AND (id = (SELECT id
                          FROM msdb.dbo.sysobjects
                          WHERE (name = 'restorehistory')))))
BEGIN
  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='stop_before' and id =
        (select id from msdb.dbo.sysobjects where name='restorehistory'))
  BEGIN
    PRINT ''
    PRINT 'Adding columns to table restorehistory...'

    ALTER TABLE restorehistory
      ADD
      stop_at_mark_name       NVARCHAR(128) NULL,
      stop_before             BIT           NULL
  END
END
go

/**************************************************************/
/* RESTOREFILE - One row per file restored.                   */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'restorefile')))
BEGIN
  PRINT ''
  PRINT 'Creating table restorefile...'

  CREATE TABLE restorefile
  (
  restore_history_id     INT           NOT NULL REFERENCES restorehistory(restore_history_id),
  file_number            NUMERIC(10,0) NULL,      -- Note: requires database to make unique
  destination_phys_drive NVARCHAR(260)  NULL,
  destination_phys_name  NVARCHAR(260)  NULL
  )

  CREATE INDEX restorefileRestoreHistoryId ON restorefile (restore_history_id)
END
ELSE
BEGIN
  ALTER TABLE restorefile ALTER COLUMN
   destination_phys_drive NVARCHAR(260) NULL
  ALTER TABLE restorefile ALTER COLUMN
   destination_phys_name  NVARCHAR(260) NULL
  IF NOT EXISTS (
      SELECT *
      FROM msdb.sys.indexes
      WHERE (name = 'restorefileRestoreHistoryId'))
    CREATE INDEX restorefileRestoreHistoryId ON restorefile (restore_history_id)
END
go

/**************************************************************/
/* RESTOREFILEGROUP - One row per filegroup restored.         */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'restorefilegroup')))
BEGIN
  PRINT ''
  PRINT 'Creating table restorefilegroup...'

  CREATE TABLE restorefilegroup
  (
  restore_history_id INT           NOT NULL REFERENCES restorehistory(restore_history_id),
  filegroup_name     NVARCHAR(128) NULL
  )

  CREATE INDEX restorefilegroupRestoreHistoryId ON restorefilegroup (restore_history_id)
END 
ELSE
BEGIN
  IF NOT EXISTS (
      SELECT *
      FROM msdb.sys.indexes
      WHERE (name = 'restorefilegroupRestoreHistoryId'))
    CREATE INDEX restorefilegroupRestoreHistoryId ON restorefilegroup (restore_history_id)
END
go

/**************************************************************/
/* LOGMARKHISTORY - One row per log mark generated            */
/**************************************************************/

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'logmarkhistory')))
BEGIN
  PRINT ''
  PRINT 'Creating table logmarkhistory...'

  CREATE TABLE logmarkhistory
  (
  database_name     NVARCHAR(128)   NOT NULL,
  mark_name         NVARCHAR(128)   NOT NULL,
  description       NVARCHAR(255)   NULL,
  user_name         NVARCHAR(128)   NOT NULL,
  lsn               NUMERIC(25,0)   NOT NULL,
  mark_time         DATETIME        NOT NULL
  )

  CREATE INDEX logmarkhistory1 ON logmarkhistory (database_name, mark_name)

  CREATE INDEX logmarkhistory2 ON logmarkhistory (database_name, lsn)
END
go

IF (EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'trig_backupset_delete')
                  AND (OBJECTPROPERTY(id, 'IsTrigger') != 0)))
BEGIN
  DROP TRIGGER trig_backupset_delete
END
go

CREATE TRIGGER trig_backupset_delete ON msdb.dbo.backupset FOR DELETE AS
BEGIN
  DELETE FROM msdb.dbo.logmarkhistory from deleted
  WHERE (msdb.dbo.logmarkhistory.database_name = deleted.database_name)
    AND (msdb.dbo.logmarkhistory.lsn >= deleted.first_lsn)
    AND (msdb.dbo.logmarkhistory.lsn < deleted.last_lsn)
END
go

/**************************************************************/
/* suspect_pages */
/**************************************************************/

IF (EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'badpagehistory')))
   DROP TABLE badpagehistory
go               
IF (EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'suspect_page_table')))
   DROP TABLE suspect_page_table
go               

IF (NOT EXISTS (SELECT *
                FROM msdb.dbo.sysobjects
                WHERE (name = 'suspect_pages')))
BEGIN
  PRINT ''
  PRINT 'Creating table suspect_pages...'

  CREATE TABLE suspect_pages
  (
  database_id           INT          NOT NULL,
  file_id               INT          NOT NULL,
  page_id               bigint       NOT NULL, -- we only use unsigned 32bits
  event_type            INT          NOT NULL,
  error_count           INT          NOT NULL,
  last_update_date      DATETIME     NOT NULL DEFAULT GETDATE()
  )
END
go


/**************************************************************/
/**                                                          **/
/**           O B J E C T    P E R M I S S I O N S           **/
/**                                                          **/
/**************************************************************/

GRANT SELECT ON backupfile        TO PUBLIC
GRANT SELECT ON backupmediafamily TO PUBLIC
GRANT SELECT ON backupmediaset    TO PUBLIC
GRANT SELECT ON backupset         TO PUBLIC
GRANT SELECT ON restorehistory    TO PUBLIC
GRANT SELECT ON restorefile       TO PUBLIC
GRANT SELECT ON restorefilegroup  TO PUBLIC
GRANT SELECT ON logmarkhistory    TO PUBLIC
GRANT SELECT ON suspect_pages     TO PUBLIC

go

/**************************************************************/
/*                                                            */
/* B A C K U P  H I S T O R Y                                 */
/*                                                            */
/**************************************************************/
/**************************************************************/
/* sp_delete_database_backuphistory                           */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_database_backuphistory...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_database_backuphistory')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_database_backuphistory
go
CREATE   PROCEDURE sp_delete_database_backuphistory
  @database_name sysname
AS
BEGIN
  SET NOCOUNT ON

  DECLARE @backup_set_id TABLE      (backup_set_id INT)
  DECLARE @media_set_id TABLE       (media_set_id INT)
  DECLARE @restore_history_id TABLE (restore_history_id INT)

  INSERT INTO @backup_set_id (backup_set_id)
  SELECT DISTINCT backup_set_id
  FROM msdb.dbo.backupset
  WHERE database_name = @database_name

  INSERT INTO @media_set_id (media_set_id)
  SELECT DISTINCT media_set_id
  FROM msdb.dbo.backupset
  WHERE database_name = @database_name

  INSERT INTO @restore_history_id (restore_history_id)
  SELECT DISTINCT restore_history_id
  FROM msdb.dbo.restorehistory
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)

  BEGIN TRANSACTION

  DELETE FROM msdb.dbo.backupfile
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.backupfilegroup
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.restorefile
  WHERE restore_history_id IN (SELECT restore_history_id
                               FROM @restore_history_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.restorefilegroup
  WHERE restore_history_id IN (SELECT restore_history_id
                               FROM @restore_history_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.restorehistory
  WHERE restore_history_id IN (SELECT restore_history_id
                               FROM @restore_history_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.backupset
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE msdb.dbo.backupmediafamily
  FROM msdb.dbo.backupmediafamily bmf
  WHERE bmf.media_set_id IN (SELECT media_set_id
                             FROM @media_set_id)
    AND ((SELECT COUNT(*)
          FROM msdb.dbo.backupset
          WHERE media_set_id = bmf.media_set_id) = 0)
  IF (@@error > 0)
    GOTO Quit

  DELETE msdb.dbo.backupmediaset
  FROM msdb.dbo.backupmediaset bms
  WHERE bms.media_set_id IN (SELECT media_set_id
                             FROM @media_set_id)
    AND ((SELECT COUNT(*)
          FROM msdb.dbo.backupset
          WHERE media_set_id = bms.media_set_id) = 0)
  IF (@@error > 0)
    GOTO Quit

  COMMIT TRANSACTION
  RETURN

Quit:
  ROLLBACK TRANSACTION
END
go



/**************************************************************/
/* SP_DELETE_BACKUPHISTORY                                    */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_backuphistory...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_backuphistory')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_backuphistory
go
CREATE   PROCEDURE sp_delete_backuphistory
  @oldest_date datetime
AS
BEGIN
  SET NOCOUNT ON

  DECLARE @backup_set_id TABLE      (backup_set_id INT)
  DECLARE @media_set_id TABLE       (media_set_id INT)
  DECLARE @restore_history_id TABLE (restore_history_id INT)

  INSERT INTO @backup_set_id (backup_set_id)
  SELECT DISTINCT backup_set_id
  FROM msdb.dbo.backupset
  WHERE backup_finish_date < @oldest_date

  INSERT INTO @media_set_id (media_set_id)
  SELECT DISTINCT media_set_id
  FROM msdb.dbo.backupset
  WHERE backup_finish_date < @oldest_date

  INSERT INTO @restore_history_id (restore_history_id)
  SELECT DISTINCT restore_history_id
  FROM msdb.dbo.restorehistory
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)

  BEGIN TRANSACTION

  DELETE FROM msdb.dbo.backupfile
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.backupfilegroup
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.restorefile
  WHERE restore_history_id IN (SELECT restore_history_id
                               FROM @restore_history_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.restorefilegroup
  WHERE restore_history_id IN (SELECT restore_history_id
                               FROM @restore_history_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.restorehistory
  WHERE restore_history_id IN (SELECT restore_history_id
                               FROM @restore_history_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE FROM msdb.dbo.backupset
  WHERE backup_set_id IN (SELECT backup_set_id
                          FROM @backup_set_id)
  IF (@@error > 0)
    GOTO Quit

  DELETE msdb.dbo.backupmediafamily
  FROM msdb.dbo.backupmediafamily bmf
  WHERE bmf.media_set_id IN (SELECT media_set_id
                             FROM @media_set_id)
    AND ((SELECT COUNT(*)
          FROM msdb.dbo.backupset
          WHERE media_set_id = bmf.media_set_id) = 0)
  IF (@@error > 0)
    GOTO Quit

  DELETE msdb.dbo.backupmediaset
  FROM msdb.dbo.backupmediaset bms
  WHERE bms.media_set_id IN (SELECT media_set_id
                             FROM @media_set_id)
    AND ((SELECT COUNT(*)
          FROM msdb.dbo.backupset
          WHERE media_set_id = bms.media_set_id) = 0)
  IF (@@error > 0)
    GOTO Quit

  COMMIT TRANSACTION
  RETURN

Quit:
  ROLLBACK TRANSACTION

END
go
/**********************************************************************/
/* TABLE : log_shipping_primaries                                     */
/* Populated on the monitor server                                    */
/*                                                                    */
/**********************************************************************/

IF (NOT EXISTS (SELECT *
            FROM INFORMATION_SCHEMA.TABLES
            WHERE (TABLE_NAME = N'log_shipping_primaries')))
BEGIN
 PRINT ''
 PRINT 'Creating table log_shipping_primaries...'
 CREATE TABLE log_shipping_primaries
 (
  primary_id                   INT IDENTITY     NOT NULL PRIMARY KEY,
  primary_server_name          sysname          NOT NULL,
  primary_database_name        sysname          NOT NULL,
  maintenance_plan_id          UNIQUEIDENTIFIER NULL,
  backup_threshold             INT              NOT NULL,
  threshold_alert              INT              NOT NULL,
  threshold_alert_enabled      BIT              NOT NULL, /* 1 = enabled, 0 = disabled */
  last_backup_filename         NVARCHAR(500)    NULL,
  last_updated                 DATETIME         NULL,
  planned_outage_start_time    INT              NOT NULL,
  planned_outage_end_time      INT              NOT NULL,
  planned_outage_weekday_mask  INT              NOT NULL,
  source_directory             NVARCHAR(500)    NULL
 )
END
ELSE 
BEGIN
  IF (NOT EXISTS (SELECT * 
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE (TABLE_NAME = N'log_shipping_primaries')
      AND (COLUMN_NAME = N'source_directory')))

  BEGIN
    PRINT ''
    PRINT 'Adding columns to table log_shipping_primaries...'

    ALTER TABLE log_shipping_primaries
     ADD source_directory NVARCHAR(500) NULL

  END
END 
go

/**********************************************************************/
/* TABLE : log_shipping_secondaries                                   */
/* Populated on the monitor server                                    */
/*                                                                    */
/**********************************************************************/

IF (NOT EXISTS (SELECT *
            FROM INFORMATION_SCHEMA.TABLES
            WHERE (TABLE_NAME = N'log_shipping_secondaries')))
BEGIN
 PRINT ''
 PRINT 'Creating table log_shipping_secondaries...'
 CREATE TABLE log_shipping_secondaries
 (
  primary_id                   INT                FOREIGN KEY REFERENCES log_shipping_primaries (primary_id),
  secondary_server_name        sysname,
  secondary_database_name      sysname,
  last_copied_filename         NVARCHAR(500),
  last_loaded_filename         NVARCHAR(500),
  last_copied_last_updated     DATETIME,
  last_loaded_last_updated     DATETIME,
  secondary_plan_id            UNIQUEIDENTIFIER,
  copy_enabled                 BIT,
  load_enabled                 BIT,              /* 1 = load enabled, 0 = load disabled */
  out_of_sync_threshold        INT,
  threshold_alert              INT,
  threshold_alert_enabled      BIT,              /*1 = enabled, 0 = disabled */
  planned_outage_start_time    INT,
  planned_outage_end_time      INT,
  planned_outage_weekday_mask  INT,
  allow_role_change            BIT DEFAULT (0)
 )
END
ELSE 
BEGIN
  IF (NOT EXISTS (SELECT * 
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE (TABLE_NAME = N'log_shipping_secondaries')
      AND (COLUMN_NAME = N'allow_role_change')))

  BEGIN
    PRINT ''
    PRINT 'Adding columns to table log_shipping_secondaries...'

    ALTER TABLE log_shipping_secondaries
     ADD allow_role_change BIT DEFAULT (0)

  END
END 
go

/**************************************************************/
/* sp_add_log_shipping_monitor_jobs                           */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_add_log_shipping_monitor_jobs...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_add_log_shipping_monitor_jobs' AND type = N'P')  )
  drop procedure sp_add_log_shipping_monitor_jobs
go
CREATE PROCEDURE sp_add_log_shipping_monitor_jobs AS 
BEGIN
  SET NOCOUNT ON
  BEGIN TRANSACTION
  DECLARE @rv INT
  DECLARE @backup_job_name sysname
  SET @backup_job_name = N'Log Shipping Alert Job - Backup'
  IF (NOT EXISTS (SELECT * FROM msdb.dbo.sysjobs WHERE name = @backup_job_name))
  BEGIN
    EXECUTE @rv = msdb.dbo.sp_add_job @job_name = N'Log Shipping Alert Job - Backup'

    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error 

    EXECUTE @rv = msdb.dbo.sp_add_jobstep 
      @job_name = N'Log Shipping Alert Job - Backup', 
      @step_id = 1, 
      @step_name = N'Log Shipping Alert - Backup', 
      @command = N'EXECUTE msdb.dbo.sp_log_shipping_monitor_backup',
      @on_fail_action = 2, 
      @flags = 4, 
      @subsystem = N'TSQL', 
      @on_success_step_id = 0, 
      @on_success_action = 1, 
      @on_fail_step_id = 0
    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error 

   EXECUTE @rv = msdb.dbo.sp_add_jobschedule 
      @job_name = @backup_job_name, 
      @freq_type = 4, 
      @freq_interval = 1, 
      @freq_subday_type = 0x4, 
      @freq_subday_interval = 1, -- run every minute
      @freq_relative_interval = 0, 
      @name = @backup_job_name
    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error

   EXECUTE @rv = msdb.dbo.sp_add_jobserver @job_name = @backup_job_name, @server_name = NULL
    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error
  END

  DECLARE @restore_job_name sysname
  SET @restore_job_name = 'Log Shipping Alert Job - Restore'
  IF (NOT EXISTS (SELECT * FROM msdb.dbo.sysjobs WHERE name = @restore_job_name))
  BEGIN
    EXECUTE @rv = msdb.dbo.sp_add_job @job_name = @restore_job_name

    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error 

    EXECUTE @rv = msdb.dbo.sp_add_jobstep 
      @job_name = @restore_job_name, 
      @step_id = 1, 
      @step_name = @restore_job_name, 
      @command = N'EXECUTE msdb.dbo.sp_log_shipping_monitor_restore',
      @on_fail_action = 2, 
      @flags = 4, 
      @subsystem = N'TSQL', 
      @on_success_step_id = 0, 
      @on_success_action = 1, 
      @on_fail_step_id = 0
    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error 

    EXECUTE @rv = msdb.dbo.sp_add_jobschedule 
      @job_name = @restore_job_name, 
      @freq_type = 4, 
      @freq_interval = 1, 
      @freq_subday_type = 0x4, 
      @freq_subday_interval = 1, -- run every minute
      @freq_relative_interval = 0, 
      @name = @restore_job_name
    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error

    EXECUTE @rv = msdb.dbo.sp_add_jobserver @job_name = @restore_job_name, @server_name = NULL
    IF (@@error <> 0 OR @rv <> 0) GOTO rollback_quit -- error
  END
  COMMIT TRANSACTION
  RETURN

rollback_quit:
  ROLLBACK TRANSACTION
END
go

/**************************************************************/
/* sp_add_log_shipping_primary                                */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_add_log_shipping_primary...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_add_log_shipping_primary' AND type = N'P'))
  drop procedure sp_add_log_shipping_primary
go
CREATE PROCEDURE sp_add_log_shipping_primary
  @primary_server_name         sysname,
  @primary_database_name       sysname,
  @maintenance_plan_id         UNIQUEIDENTIFIER = NULL,
  @backup_threshold            INT              = 60,
  @threshold_alert             INT              = 14420,
  @threshold_alert_enabled     BIT              = 1,
  @planned_outage_start_time   INT              = 0,
  @planned_outage_end_time     INT              = 0,
  @planned_outage_weekday_mask INT              = 0,
  @primary_id              INT = NULL OUTPUT       
AS
BEGIN
  SET NOCOUNT ON
  IF EXISTS (SELECT * FROM msdb.dbo.log_shipping_primaries WHERE primary_server_name = @primary_server_name AND primary_database_name = @primary_database_name)
  BEGIN  
    DECLARE @pair_name NVARCHAR 
   SELECT @pair_name = @primary_server_name + N'.' + @primary_database_name
   RAISERROR (14261,16,1, N'primary_server_name.primary_database_name', @pair_name)
    RETURN (1) -- error
  END
  INSERT INTO msdb.dbo.log_shipping_primaries (
    primary_server_name,
    primary_database_name,
    maintenance_plan_id,
    backup_threshold,
    threshold_alert,
    threshold_alert_enabled,
    last_backup_filename,
    last_updated,
    planned_outage_start_time,
    planned_outage_end_time,
    planned_outage_weekday_mask,
    source_directory)  
  VALUES (@primary_server_name,  
    @primary_database_name, 
    @maintenance_plan_id, 
    @backup_threshold,
    @threshold_alert,
    @threshold_alert_enabled,
    N'first_file_000000000000.trn',
    GETDATE (),
    @planned_outage_start_time,
    @planned_outage_end_time,
    @planned_outage_weekday_mask,
    NULL)

  SELECT @primary_id = @@IDENTITY

  EXECUTE msdb.dbo.sp_add_log_shipping_monitor_jobs
END
go

/**************************************************************/
/* sp_add_log_shipping_secondary                              */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_add_log_shipping_secondary...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_add_log_shipping_secondary' AND type = N'P'))
  drop procedure sp_add_log_shipping_secondary
go
CREATE PROCEDURE sp_add_log_shipping_secondary
  @primary_id                  INT,
  @secondary_server_name       sysname,
  @secondary_database_name     sysname,
  @secondary_plan_id           UNIQUEIDENTIFIER,
  @copy_enabled                BIT              = 1,
  @load_enabled                BIT              = 1,
  @out_of_sync_threshold       INT              = 60,
  @threshold_alert             INT              = 14421,
  @threshold_alert_enabled     BIT              = 1,
  @planned_outage_start_time   INT              = 0,
  @planned_outage_end_time     INT              = 0,
  @planned_outage_weekday_mask INT              = 0,
  @allow_role_change           BIT              = 0 
AS
BEGIN
  SET NOCOUNT ON
  IF NOT EXISTS (SELECT * FROM msdb.dbo.log_shipping_primaries where primary_id = @primary_id)
  BEGIN
    RAISERROR (14262, 16, 1, N'primary_id', N'msdb.dbo.log_shipping_primaries')
    RETURN(1)
  END

  INSERT INTO msdb.dbo.log_shipping_secondaries (
    primary_id,
    secondary_server_name,
    secondary_database_name,
    last_copied_filename,
    last_loaded_filename,
    last_copied_last_updated,
    last_loaded_last_updated,
    secondary_plan_id,
    copy_enabled,
    load_enabled,
    out_of_sync_threshold,
    threshold_alert,
    threshold_alert_enabled,
    planned_outage_start_time,
    planned_outage_end_time,
    planned_outage_weekday_mask,
    allow_role_change)
   VALUES (@primary_id,
    @secondary_server_name,
    @secondary_database_name,
    N'first_file_000000000000.trn',
    N'first_file_000000000000.trn',
    GETDATE (),
    GETDATE (),
    @secondary_plan_id,
    @copy_enabled,
    @load_enabled,
    @out_of_sync_threshold,
    @threshold_alert,
    @threshold_alert_enabled,
    @planned_outage_start_time,
    @planned_outage_end_time,
    @planned_outage_weekday_mask,
    @allow_role_change)
END
go

/**************************************************************/
/* sp_delete_log_shipping_monitor_jobs                        */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_delete_log_shipping_monitor_jobs...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_delete_log_shipping_monitor_jobs' AND type = N'P')  )
  drop procedure sp_delete_log_shipping_monitor_jobs
go
CREATE PROCEDURE sp_delete_log_shipping_monitor_jobs AS
BEGIN
  DECLARE @backup_job_name sysname
  SET NOCOUNT ON
  SET @backup_job_name = N'Log Shipping Alert Job - Backup'
  IF (EXISTS (SELECT * FROM msdb.dbo.sysjobs WHERE name = @backup_job_name))
    EXECUTE msdb.dbo.sp_delete_job @job_name = N'Log Shipping Alert Job - Backup'

  DECLARE @restore_job_name sysname
  SET @restore_job_name = 'Log Shipping Alert Job - Restore'
  IF (EXISTS (SELECT * FROM msdb.dbo.sysjobs WHERE name = @restore_job_name))
    EXECUTE msdb.dbo.sp_delete_job @job_name = N'Log Shipping Alert Job - Restore'
END
go

/**************************************************************/
/* sp_delete_log_shipping_primary                             */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_delete_log_shipping_primary...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_delete_log_shipping_primary' AND type = N'P')  )
  drop procedure sp_delete_log_shipping_primary
go
CREATE PROCEDURE sp_delete_log_shipping_primary 
  @primary_server_name sysname,
  @primary_database_name sysname,
  @delete_secondaries BIT = 0
AS BEGIN
  DECLARE @primary_id INT

  SET NOCOUNT ON

  SELECT @primary_id = primary_id 
    FROM msdb.dbo.log_shipping_primaries 
    WHERE primary_server_name = @primary_server_name AND primary_database_name = @primary_database_name
  IF (@primary_id IS NULL)
    RETURN (0)

  BEGIN TRANSACTION
  IF (EXISTS (SELECT * FROM msdb.dbo.log_shipping_secondaries WHERE primary_id = @primary_id))
  BEGIN
    IF (@delete_secondaries = 0)
    BEGIN
      RAISERROR (14429,-1,-1)
      goto rollback_quit
    END
    DELETE FROM msdb.dbo.log_shipping_secondaries WHERE primary_id = @primary_id
    IF (@@ERROR <> 0)
      GOTO rollback_quit
  END
  DELETE FROM msdb.dbo.log_shipping_primaries WHERE primary_id = @primary_id
  IF (@@ERROR <> 0)
    GOTO rollback_quit

  COMMIT TRANSACTION
  DECLARE @i INT
  SELECT @i = COUNT(*) FROM msdb.dbo.log_shipping_primaries
  IF (@i=0)
    EXECUTE msdb.dbo.sp_delete_log_shipping_monitor_jobs
  RETURN (0)

rollback_quit:
  ROLLBACK TRANSACTION
  RETURN(1) -- error
END
go

/**************************************************************/
/* sp_delete_log_shipping_secondary                           */
/**************************************************************/
PRINT ''
PRINT 'Creating sp_delete_log_shipping_secondary...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_delete_log_shipping_secondary' AND type = N'P')  )
  drop procedure sp_delete_log_shipping_secondary
go
CREATE PROCEDURE sp_delete_log_shipping_secondary 
  @secondary_server_name   sysname,
  @secondary_database_name sysname
AS BEGIN
  SET NOCOUNT ON
  DELETE FROM msdb.dbo.log_shipping_secondaries WHERE 
    secondary_server_name   = @secondary_server_name AND
    secondary_database_name = @secondary_database_name
END
go

/**************************************************************/
/* sp_log_shipping_in_sync                                    */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_log_shipping_in_sync...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_log_shipping_in_sync' AND type = N'P')  )
  drop procedure sp_log_shipping_in_sync
go
CREATE PROCEDURE sp_log_shipping_in_sync
  @last_updated        DATETIME,
  @compare_with        DATETIME,
  @threshold           INT,
  @outage_start_time   INT,
  @outage_end_time     INT,
  @outage_weekday_mask INT,
  @enabled             BIT = 1,
  @delta               INT = NULL OUTPUT
AS BEGIN
  SET NOCOUNT ON
  DECLARE @cur_time INT

  SELECT @delta = DATEDIFF (mi, @last_updated, @compare_with)
  -- in sync
  IF (@delta <= @threshold)
    RETURN (0) -- in sync

  IF (@enabled = 0) 
    RETURN(0) -- in sync

  IF (@outage_weekday_mask & DATEPART(dw, GETDATE ()) > 0) -- potentially in outage window
  BEGIN
    SELECT @cur_time = DATEPART (hh, GETDATE()) * 10000 +
                       DATEPART (mi, GETDATE()) * 100 + 
                       DATEPART (ss, GETDATE())
     -- outage doesn't span midnight
    IF (@outage_start_time < @outage_end_time)
    BEGIN
      IF (@cur_time >= @outage_start_time AND @cur_time < @outage_end_time)
        RETURN(1) -- in outage
    END
     -- outage does span midnight
   ELSE IF (@outage_start_time > @outage_end_time)
   BEGIN
     IF (@cur_time >= @outage_start_time OR @cur_time < @outage_end_time)
       RETURN(1) -- in outage
   END
  END
  RETURN(-1 ) -- not in outage, not in sync
END
go

/**************************************************************/
/* sp_log_shipping_get_date_from_file                         */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_log_shipping_get_date_from_file...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_log_shipping_get_date_from_file' AND type = N'P')  )
  drop procedure sp_log_shipping_get_date_from_file
go
CREATE PROCEDURE sp_log_shipping_get_date_from_file 
  @db_name sysname,
  @filename NVARCHAR (500),
  @file_date DATETIME OUTPUT
AS
BEGIN
  SET NOCOUNT ON

  DECLARE @tempname NVARCHAR (500)
  IF (LEN (@filename) - (LEN(@db_name) + LEN ('_tlog_')) <= 0)
    RETURN(1) -- filename string isn't long enough
  SELECT @tempname = RIGHT (@filename, LEN (@filename) - (LEN(@db_name) + LEN ('_tlog_')))
  IF (CHARINDEX ('.',@tempname,0) > 0)
    SELECT @tempname = LEFT (@tempname, CHARINDEX ('.',@tempname,0) - 1)
  IF (LEN (@tempname) <>  8 AND LEN (@tempname) <> 12)
    RETURN (1) -- error must be yyyymmddhhmm or yyyymmdd
  IF (ISNUMERIC (@tempname) = 0 OR CHARINDEX ('.',@tempname,0) <> 0 OR CONVERT (FLOAT,SUBSTRING (@tempname, 1,8)) < 1 )
    RETURN (1) -- must be numeric, can't contain any '.' etc
  SELECT @file_date = CONVERT (DATETIME,SUBSTRING (@tempname, 1,8),112)
  IF (LEN (@tempname) = 12)
  BEGIN
    SELECT @file_date = DATEADD (hh, CONVERT (INT, SUBSTRING (@tempname,9,2)),@file_date)
    SELECT @file_date = DATEADD (mi, CONVERT (INT, SUBSTRING (@tempname,11,2)),@file_date)
  END
  RETURN (0) -- success
END
go

/**************************************************************/
/* sp_get_log_shipping_monitor_info                           */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_get_log_shipping_monitor_info...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_get_log_shipping_monitor_info' AND type = N'P')  )
  drop procedure sp_get_log_shipping_monitor_info
go
CREATE PROCEDURE sp_get_log_shipping_monitor_info
  @primary_server_name     sysname = N'%',
  @primary_database_name   sysname = N'%',
  @secondary_server_name   sysname = N'%',
  @secondary_database_name sysname = N'%'
AS BEGIN
  SET NOCOUNT ON
  DECLARE @lsp TABLE (
    primary_server_name            sysname       COLLATE database_default NOT NULL,
    primary_database_name          sysname       COLLATE database_default NOT NULL,
    secondary_server_name          sysname       COLLATE database_default NOT NULL,
    secondary_database_name        sysname       COLLATE database_default NOT NULL,
    backup_threshold               INT           NOT NULL,
    backup_threshold_alert         INT           NOT NULL,
    backup_threshold_alert_enabled BIT           NOT NULL,
    last_backup_filename           NVARCHAR(500) COLLATE database_default NOT NULL,
    last_backup_last_updated       DATETIME      NOT NULL,
    backup_outage_start_time       INT           NOT NULL,
    backup_outage_end_time         INT           NOT NULL,
    backup_outage_weekday_mask     INT           NOT NULL,
    backup_in_sync                 INT           NULL, -- 0 = in sync, -1 = out of sync, 1 = in outage window
    backup_delta                   INT           NULL,
    last_copied_filename           NVARCHAR(500) COLLATE database_default NOT NULL,
    last_copied_last_updated       DATETIME      NOT NULL,
    last_loaded_filename           NVARCHAR(500) COLLATE database_default NOT NULL,
    last_loaded_last_updated       DATETIME      NOT NULL,
    copy_delta                     INT           NULL,
    copy_enabled                   BIT           NOT NULL,
    load_enabled                   BIT           NOT NULL,
    out_of_sync_threshold          INT           NOT NULL,
    load_threshold_alert           INT           NOT NULL,
    load_threshold_alert_enabled   BIT           NOT NULL,
    load_outage_start_time         INT           NOT NULL,
    load_outage_end_time           INT           NOT NULL,
    load_outage_weekday_mask       INT           NOT NULL,
    load_in_sync                   INT           NULL, -- 0 = in sync, -1 = out of sync, 1 = in outage window
    load_delta                     INT           NULL,
    maintenance_plan_id             UNIQUEIDENTIFIER NULL,
    secondary_plan_id              UNIQUEIDENTIFIER NOT NULL)

  INSERT INTO @lsp

 SELECT
    primary_server_name,
    primary_database_name,
    secondary_server_name,
    secondary_database_name,
    backup_threshold,
    p.threshold_alert,
    p.threshold_alert_enabled,
    last_backup_filename,
    p.last_updated,
    p.planned_outage_start_time,
    p.planned_outage_end_time,
    p.planned_outage_weekday_mask,
    NULL,
    NULL,
    last_copied_filename,
    last_copied_last_updated,
    last_loaded_filename,
    last_loaded_last_updated,
    NULL,
    copy_enabled,
    load_enabled,
    out_of_sync_threshold,
    s.threshold_alert,
    s.threshold_alert_enabled,
    s.planned_outage_start_time,
    s.planned_outage_weekday_mask,
    s.planned_outage_end_time,
    NULL,
    NULL,
    maintenance_plan_id,
    secondary_plan_id
  FROM msdb.dbo.log_shipping_primaries p, msdb.dbo.log_shipping_secondaries s
  WHERE 
    p.primary_id = s.primary_id AND
    primary_server_name LIKE @primary_server_name AND
    primary_database_name LIKE @primary_database_name AND
    secondary_server_name LIKE @secondary_server_name AND
    secondary_database_name LIKE @secondary_database_name

  DECLARE @load_in_sync                   INT
  DECLARE @backup_in_sync                 INT
  DECLARE @_primary_server_name           sysname 
  DECLARE @_primary_database_name         sysname 
  DECLARE @_secondary_server_name         sysname
  DECLARE @_secondary_database_name       sysname
  DECLARE @last_loaded_last_updated       DATETIME
  DECLARE @last_loaded_filename           NVARCHAR (500)
  DECLARE @last_copied_filename           NVARCHAR (500)
  DECLARE @last_backup_last_updated       DATETIME
  DECLARE @last_backup_filename           NVARCHAR (500)
  DECLARE @backup_outage_start_time       INT
  DECLARE @backup_outage_end_time         INT
  DECLARE @backup_outage_weekday_mask     INT
  DECLARE @backup_threshold               INT
  DECLARE @backup_threshold_alert_enabled BIT
  DECLARE @load_outage_start_time         INT
  DECLARE @load_outage_end_time           INT
  DECLARE @load_outage_weekday_mask       INT
  DECLARE @load_threshold                 INT
  DECLARE @load_threshold_alert_enabled   BIT
  DECLARE @backupdt                       DATETIME
  DECLARE @restoredt                      DATETIME
  DECLARE @copydt                         DATETIME
  DECLARE @rv                             INT
  DECLARE @dt                             DATETIME
  DECLARE @copy_delta                     INT
  DECLARE @load_delta                     INT
  DECLARE @backup_delta                   INT
  DECLARE @last_copied_last_updated       DATETIME

  SELECT @dt = GETDATE ()

  DECLARE sync_update CURSOR FOR
    SELECT 
      primary_server_name, 
      primary_database_name, 
      secondary_server_name, 
      secondary_database_name,
      last_backup_filename,
      last_backup_last_updated,
      last_loaded_filename,
      last_loaded_last_updated,
      backup_outage_start_time,
      backup_outage_end_time,
      backup_outage_weekday_mask,
      backup_threshold,
      backup_threshold_alert_enabled,
      load_outage_start_time,
      load_outage_end_time,
      out_of_sync_threshold,
      load_outage_weekday_mask,
      load_threshold_alert_enabled,
      last_copied_filename,
      last_copied_last_updated
    FROM @lsp
    FOR READ ONLY

  OPEN sync_update

loop:
  FETCH NEXT FROM sync_update INTO
    @_primary_server_name, 
    @_primary_database_name, 
    @_secondary_server_name, 
    @_secondary_database_name,
    @last_backup_filename,
    @last_backup_last_updated,
    @last_loaded_filename,
    @last_loaded_last_updated,
    @backup_outage_start_time,
    @backup_outage_end_time,
    @backup_outage_weekday_mask,
    @backup_threshold,
    @backup_threshold_alert_enabled,
    @load_outage_start_time,
    @load_outage_end_time,
    @load_threshold,
    @load_outage_weekday_mask,
    @load_threshold_alert_enabled,
    @last_copied_filename,
    @last_copied_last_updated

  IF @@fetch_status <> 0
    GOTO _loop

  EXECUTE @rv = sp_log_shipping_get_date_from_file @_primary_database_name, @last_backup_filename, @backupdt OUTPUT
  IF (@rv <> 0)
    SELECT @backupdt = @last_backup_last_updated
  EXECUTE @rv = sp_log_shipping_get_date_from_file @_primary_database_name, @last_loaded_filename, @restoredt OUTPUT
  IF  (@rv <> 0)
    SELECT @restoredt = @last_loaded_last_updated
  EXECUTE @rv = sp_log_shipping_get_date_from_file @_primary_database_name, @last_copied_filename, @copydt OUTPUT
  IF  (@rv <> 0)
    SELECT @copydt = @last_copied_last_updated
  
  EXECUTE @load_in_sync = msdb.dbo.sp_log_shipping_in_sync
    @restoredt,
    @backupdt,
    @load_threshold,
    @load_outage_start_time,
    @load_outage_end_time,
    @load_outage_weekday_mask,
    @load_threshold_alert_enabled,
    @load_delta OUTPUT

  EXECUTE @backup_in_sync = msdb.dbo.sp_log_shipping_in_sync
    @last_backup_last_updated,
    @dt,
    @backup_threshold,
    @backup_outage_start_time,
    @backup_outage_end_time,
    @backup_outage_weekday_mask,
    @backup_threshold_alert_enabled,
    @backup_delta OUTPUT

  EXECUTE msdb.dbo.sp_log_shipping_in_sync
    @copydt,
    @backupdt,
    1,0,0,0,0,
    @copy_delta OUTPUT

  UPDATE @lsp 
  SET backup_in_sync = @backup_in_sync, load_in_sync  = @load_in_sync, 
    copy_delta = @copy_delta, load_delta = @load_delta, backup_delta = @backup_delta
  WHERE primary_server_name = @_primary_server_name AND
    secondary_server_name = @_secondary_server_name AND
    primary_database_name = @_primary_database_name AND
    secondary_database_name = @_secondary_database_name 
  GOTO loop
_loop:
  CLOSE sync_update
  DEALLOCATE sync_update
  SELECT * FROM @lsp
END
go

/**************************************************************/
/* sp_update_log_shipping_monitor_info                        */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_update_log_shipping_monitor_info...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_update_log_shipping_monitor_info' AND type = N'P')  )
  DROP PROCEDURE sp_update_log_shipping_monitor_info
go
CREATE PROCEDURE sp_update_log_shipping_monitor_info
  @primary_server_name                 sysname,
  @primary_database_name               sysname,
  @secondary_server_name               sysname,
  @secondary_database_name             sysname,
  @backup_threshold                    INT = NULL,
  @backup_threshold_alert              INT = NULL,
  @backup_threshold_alert_enabled      BIT = NULL,
  @backup_outage_start_time            INT = NULL,
  @backup_outage_end_time              INT = NULL,
  @backup_outage_weekday_mask          INT = NULL,
  @copy_enabled                        BIT = NULL,
  @load_enabled                        BIT = NULL,
  @out_of_sync_threshold               INT = NULL,
  @out_of_sync_threshold_alert         INT = NULL,
  @out_of_sync_threshold_alert_enabled BIT = NULL,
  @out_of_sync_outage_start_time       INT = NULL,
  @out_of_sync_outage_end_time         INT = NULL,
  @out_of_sync_outage_weekday_mask     INT = NULL
AS BEGIN
  SET NOCOUNT ON
  DECLARE @_backup_threshold                    INT
  DECLARE @_backup_threshold_alert              INT
  DECLARE @_backup_threshold_alert_enabled      BIT
  DECLARE @_backup_outage_start_time            INT
  DECLARE @_backup_outage_end_time              INT
  DECLARE @_backup_outage_weekday_mask          INT
  DECLARE @_copy_enabled                        BIT
  DECLARE @_load_enabled                        BIT
  DECLARE @_out_of_sync_threshold               INT
  DECLARE @_out_of_sync_threshold_alert         INT
  DECLARE @_out_of_sync_threshold_alert_enabled BIT
  DECLARE @_out_of_sync_outage_start_time       INT
  DECLARE @_out_of_sync_outage_end_time         INT
  DECLARE @_out_of_sync_outage_weekday_mask     INT

  -- check that the primary exists
  IF (NOT EXISTS (SELECT * FROM msdb.dbo.log_shipping_primaries WHERE primary_server_name = @primary_server_name AND primary_database_name = @primary_database_name))
  BEGIN
    DECLARE @pp sysname
    SELECT @pp = @primary_server_name + N'.' + @primary_database_name
    RAISERROR (14262, 16, 1, N'primary_server_name.primary_database_name', @pp)
    RETURN (1) -- error
  END

  -- check that the secondary exists
  IF (NOT EXISTS (SELECT * FROM msdb.dbo.log_shipping_secondaries WHERE secondary_server_name = @secondary_server_name AND secondary_database_name = @secondary_database_name))
  BEGIN
    DECLARE @sp sysname
    SELECT @sp = @secondary_server_name + N'.' + @secondary_database_name
    RAISERROR (14262, 16, 1, N'secondary_server_name.secondary_database_name', @sp)
    RETURN (1) -- error
  END

  -- load the original variables

 SELECT
    @_backup_threshold                    = backup_threshold,
    @_backup_threshold_alert              = p.threshold_alert,
    @_backup_threshold_alert_enabled      = p.threshold_alert_enabled,
    @_backup_outage_start_time            = p.planned_outage_start_time,
    @_backup_outage_end_time              = p.planned_outage_end_time,
    @_backup_outage_weekday_mask          = p.planned_outage_weekday_mask,
    @_copy_enabled                        = copy_enabled,
    @_load_enabled                        = load_enabled,
    @_out_of_sync_threshold               = out_of_sync_threshold,
    @_out_of_sync_threshold_alert         = s.threshold_alert,
    @_out_of_sync_threshold_alert_enabled = s.threshold_alert_enabled,
    @_out_of_sync_outage_start_time       = s.planned_outage_start_time,
    @_out_of_sync_outage_weekday_mask     = s.planned_outage_weekday_mask,
    @_out_of_sync_outage_end_time         = s.planned_outage_end_time
  FROM msdb.dbo.log_shipping_primaries p, msdb.dbo.log_shipping_secondaries s
  WHERE 
    p.primary_id            = s.primary_id           AND
    primary_server_name     = @primary_server_name   AND
    primary_database_name   = @primary_database_name AND
    secondary_server_name   = @secondary_server_name AND
    secondary_database_name = @secondary_database_name

  SELECT @_backup_threshold                    = ISNULL (@backup_threshold,                    @_backup_threshold)
  SELECT @_backup_threshold_alert              = ISNULL (@backup_threshold_alert,              @_backup_threshold_alert)
  SELECT @_backup_threshold_alert_enabled      = ISNULL (@backup_threshold_alert_enabled,      @_backup_threshold_alert_enabled)
  SELECT @_backup_outage_start_time            = ISNULL (@backup_outage_start_time,            @_backup_outage_start_time)
  SELECT @_backup_outage_end_time              = ISNULL (@backup_outage_end_time,              @_backup_outage_end_time)
  SELECT @_backup_outage_weekday_mask          = ISNULL (@backup_outage_weekday_mask,          @_backup_outage_weekday_mask)
  SELECT @_copy_enabled                        = ISNULL (@copy_enabled,                        @_copy_enabled)
  SELECT @_load_enabled                        = ISNULL (@load_enabled,                        @_load_enabled)
  SELECT @_out_of_sync_threshold               = ISNULL (@out_of_sync_threshold,               @_out_of_sync_threshold)
  SELECT @_out_of_sync_threshold_alert         = ISNULL (@out_of_sync_threshold_alert,         @_out_of_sync_threshold_alert)
  SELECT @_out_of_sync_threshold_alert_enabled = ISNULL (@out_of_sync_threshold_alert_enabled, @_out_of_sync_threshold_alert_enabled)
  SELECT @_out_of_sync_outage_start_time       = ISNULL (@out_of_sync_outage_start_time,       @_out_of_sync_outage_start_time)
  SELECT @_out_of_sync_outage_end_time         = ISNULL (@out_of_sync_outage_end_time,         @_out_of_sync_outage_end_time)
  SELECT @_out_of_sync_outage_weekday_mask     = ISNULL (@out_of_sync_outage_weekday_mask,     @_out_of_sync_outage_weekday_mask)

  -- updates
  UPDATE msdb.dbo.log_shipping_primaries SET
    backup_threshold            = @_backup_threshold,
    threshold_alert             = @_backup_threshold_alert,
    threshold_alert_enabled     = @_backup_threshold_alert_enabled,
    planned_outage_start_time   = @_backup_outage_start_time,
    planned_outage_end_time     = @_backup_outage_end_time,
    planned_outage_weekday_mask = @_backup_outage_weekday_mask
  WHERE primary_server_name = @primary_server_name AND primary_database_name = @primary_database_name

  UPDATE msdb.dbo.log_shipping_secondaries SET
    copy_enabled                = @_copy_enabled,
    load_enabled                = @_load_enabled,
    out_of_sync_threshold       = @_out_of_sync_threshold,
    threshold_alert             = @_out_of_sync_threshold_alert,
    threshold_alert_enabled     = @_out_of_sync_threshold_alert_enabled,
    planned_outage_start_time   = @_out_of_sync_outage_start_time,
    planned_outage_end_time     = @_out_of_sync_outage_weekday_mask,
    planned_outage_weekday_mask = @_out_of_sync_outage_end_time
  WHERE secondary_server_name = @secondary_server_name AND secondary_database_name = @secondary_database_name
RETURN(0)
END
go

/**************************************************************/
/* sp_delete_log_shipping_monitor_info                        */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_delete_log_shipping_monitor_info...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_delete_log_shipping_monitor_info' AND type = N'P')  )
  DROP PROCEDURE sp_delete_log_shipping_monitor_info
go
CREATE PROCEDURE sp_delete_log_shipping_monitor_info
  @primary_server_name                 sysname,
  @primary_database_name               sysname,
  @secondary_server_name               sysname,
  @secondary_database_name             sysname
AS BEGIN
  -- check that the primary exists
  IF (NOT EXISTS (SELECT * FROM msdb.dbo.log_shipping_primaries WHERE primary_server_name = @primary_server_name AND primary_database_name = @primary_database_name))
  BEGIN
    DECLARE @pp sysname
    SELECT @pp = @primary_server_name + N'.' + @primary_database_name
    RAISERROR (14262, 16, 1, N'primary_server_name.primary_database_name', @pp)
    RETURN (1) -- error
  END

  -- check that the secondary exists
  IF (NOT EXISTS (SELECT * FROM msdb.dbo.log_shipping_secondaries WHERE secondary_server_name = @secondary_server_name AND secondary_database_name = @secondary_database_name))
  BEGIN
    DECLARE @sp sysname
    SELECT @sp = @secondary_server_name + N'.' + @secondary_database_name
    RAISERROR (14262, 16, 1, N'secondary_server_name.secondary_database_name', @sp)
    RETURN (1) -- error
  END

  BEGIN TRANSACTION

  -- delete the secondary
  DELETE FROM msdb.dbo.log_shipping_secondaries WHERE secondary_server_name = @secondary_server_name AND secondary_database_name = @secondary_database_name
  IF (@@error <> 0)
    goto rollback_quit

  -- if there are no more secondaries for this primary then delete it
  IF (NOT EXISTS (SELECT * FROM msdb.dbo.log_shipping_primaries p, msdb.dbo.log_shipping_secondaries s WHERE p.primary_id = s.primary_id AND primary_server_name = @primary_server_name AND primary_database_name = @primary_database_name))
  BEGIN
    DELETE FROM msdb.dbo.log_shipping_primaries WHERE primary_server_name = @primary_server_name AND primary_database_name = @primary_database_name
    IF (@@error <> 0)
      goto rollback_quit
  END
 COMMIT TRANSACTION
 RETURN (0)

rollback_quit:
  ROLLBACK TRANSACTION
  RETURN(1) -- Failure
END
go 

/**************************************************************/
/* sp_remove_log_shipping_monitor_account                     */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_remove_log_shipping_monitor_account...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_remove_log_shipping_monitor_account' AND type = N'P')  )
  DROP PROCEDURE sp_remove_log_shipping_monitor_account
go

CREATE PROCEDURE sp_remove_log_shipping_monitor_account
AS
BEGIN
  SET NOCOUNT ON
  EXECUTE sp_dropuser N'log_shipping_monitor_probe'
  EXECUTE sp_droplogin N'log_shipping_monitor_probe'
END
go

/**************************************************************/
/* sp_log_shipping_monitor_backup                             */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_log_shipping_monitor_backup...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_log_shipping_monitor_backup' AND type = N'P')  )
  drop procedure sp_log_shipping_monitor_backup
go

CREATE PROCEDURE sp_log_shipping_monitor_backup AS
BEGIN
  DECLARE @primary_id                  sysname
  DECLARE @primary_server_name         sysname 
  DECLARE @primary_database_name       sysname 
  DECLARE @maintenance_plan_id         UNIQUEIDENTIFIER
  DECLARE @backup_threshold            INT
  DECLARE @threshold_alert             INT 
  DECLARE @threshold_alert_enabled     BIT 
  DECLARE @last_backup_filename        sysname 
  DECLARE @last_updated                DATETIME
  DECLARE @planned_outage_start_time   INT
  DECLARE @planned_outage_end_time     INT 
  DECLARE @planned_outage_weekday_mask INT
  DECLARE @sync_status                 INT
  DECLARE @backup_delta                INT
  DECLARE @delta_string                NVARCHAR (10)
  DECLARE @dt                             DATETIME

  SELECT @dt = GETDATE ()

  SET NOCOUNT ON

  DECLARE bmlsp_cur CURSOR FOR
    SELECT primary_id, 
           primary_server_name, 
           primary_database_name, 
         maintenance_plan_id, 
           backup_threshold, 
           threshold_alert, 
           threshold_alert_enabled, 
           last_backup_filename, 
           last_updated,
           planned_outage_start_time, 
           planned_outage_end_time, 
           planned_outage_weekday_mask 
    FROM msdb.dbo.log_shipping_primaries
    FOR READ ONLY

  OPEN bmlsp_cur
loop:
  FETCH NEXT FROM bmlsp_cur 
  INTO @primary_id, 
       @primary_server_name, 
      @primary_database_name, 
      @maintenance_plan_id,
       @backup_threshold, 
      @threshold_alert, 
      @threshold_alert_enabled, 
      @last_backup_filename, 
      @last_updated, 
      @planned_outage_start_time,
       @planned_outage_end_time, 
      @planned_outage_weekday_mask

  IF @@FETCH_STATUS <> 0 -- nothing more to fetch, finish the loop
    GOTO _loop

  EXECUTE @sync_status = sp_log_shipping_in_sync
    @last_updated,
    @dt,
     @backup_threshold,
   @planned_outage_start_time,
   @planned_outage_end_time,
    @planned_outage_weekday_mask,
   @threshold_alert_enabled,
   @backup_delta OUTPUT

   IF (@sync_status < 0)
   BEGIN
     SELECT @delta_string = CONVERT (NVARCHAR(10), @backup_delta)
     RAISERROR (@threshold_alert, 16, 1, @primary_server_name, @primary_database_name, @delta_string)
   END

  GOTO loop
_loop:
  CLOSE bmlsp_cur
  DEALLOCATE bmlsp_cur
END
go

/**************************************************************/
/* sp_log_shipping_monitor_restore                            */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_log_shipping_monitor_restore...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_log_shipping_monitor_restore' AND type = N'P')  )
  drop procedure sp_log_shipping_monitor_restore
go
CREATE PROCEDURE sp_log_shipping_monitor_restore AS
BEGIN
  SET NOCOUNT ON
  DECLARE @primary_id                  INT
  DECLARE @secondary_server_name       sysname
  DECLARE @secondary_database_name     sysname
  DECLARE @secondary_plan_id           UNIQUEIDENTIFIER
  DECLARE @out_of_sync_threshold       INT 
  DECLARE @threshold_alert             INT 
  DECLARE @threshold_alert_enabled     BIT 
  DECLARE @last_loaded_filename        NVARCHAR (500)
  DECLARE @last_backup_filename        NVARCHAR (500) 
  DECLARE @primary_database_name       sysname
  DECLARE @last_loaded_last_updated    DATETIME
  DECLARE @last_backup_last_updated    DATETIME
  DECLARE @planned_outage_start_time   INT 
  DECLARE @planned_outage_end_time     INT 
  DECLARE @planned_outage_weekday_mask INT
  DECLARE @sync_status                 INT
  DECLARE @sync_delta                  INT
  DECLARE @delta_string                NVARCHAR(10)

  SET NOCOUNT ON
  DECLARE @backupdt  DATETIME
  DECLARE @restoredt DATETIME
  DECLARE @rv        INT
  DECLARE rmlsp_cur CURSOR FOR
    SELECT s.primary_id, 
      s.secondary_server_name, 
      s.secondary_database_name, 
      s.secondary_plan_id, 
      s.out_of_sync_threshold, 
      s.threshold_alert, 
      s.threshold_alert_enabled, 
      s.last_loaded_filename, 
      s.last_loaded_last_updated,
      p.last_backup_filename,
      p.last_updated,
      p.primary_database_name,
      s.planned_outage_start_time, 
      s.planned_outage_end_time, 
      s.planned_outage_weekday_mask 
    FROM msdb.dbo.log_shipping_secondaries s 
    INNER JOIN msdb.dbo.log_shipping_primaries p 
    ON s.primary_id = p.primary_id
    FOR READ ONLY

  OPEN rmlsp_cur
loop:
  FETCH NEXT FROM rmlsp_cur 
  INTO @primary_id, 
      @secondary_server_name, 
         @secondary_database_name, 
         @secondary_plan_id, 
       @out_of_sync_threshold, 
         @threshold_alert, 
         @threshold_alert_enabled, 
         @last_loaded_filename, 
         @last_loaded_last_updated,
       @last_backup_filename,
       @last_backup_last_updated,
       @primary_database_name,
       @planned_outage_start_time, 
         @planned_outage_end_time, 
         @planned_outage_weekday_mask 

  IF @@FETCH_STATUS <> 0 -- nothing more to fetch, finish the loop
    GOTO _loop

  EXECUTE @rv = sp_log_shipping_get_date_from_file @primary_database_name, @last_backup_filename, @backupdt OUTPUT
  IF (@rv <> 0)
    SELECT @backupdt = @last_backup_last_updated
  
  EXECUTE @rv = sp_log_shipping_get_date_from_file @primary_database_name, @last_loaded_filename, @restoredt OUTPUT
  IF  (@rv <> 0)
    SELECT @restoredt = @last_loaded_last_updated

  EXECUTE @sync_status = sp_log_shipping_in_sync
    @restoredt,
    @backupdt,
     @out_of_sync_threshold,
     @planned_outage_start_time,
     @planned_outage_end_time,
    @planned_outage_weekday_mask,
    @threshold_alert_enabled,
    @sync_delta OUTPUT

   IF (@sync_status < 0)
   BEGIN
     SELECT @delta_string = CONVERT (NVARCHAR(10), @sync_delta)
     RAISERROR (@threshold_alert, 16, 1, @secondary_server_name, @secondary_database_name, @delta_string)
   END

  GOTO loop
_loop:
  CLOSE rmlsp_cur
  DEALLOCATE rmlsp_cur
END
go

/**************************************************************/
/* sp_change_monitor_role                                     */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_change_monitor_role...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_change_monitor_role' AND type = N'P')  )
  DROP PROCEDURE sp_change_monitor_role
go
CREATE PROCEDURE sp_change_monitor_role
  @primary_server     sysname,
  @secondary_server   sysname,
  @database           sysname,
  @new_source         NVARCHAR (128)
AS BEGIN
  SET NOCOUNT ON

  BEGIN TRANSACTION

  -- drop the secondary
  DELETE FROM msdb.dbo.log_shipping_secondaries 
    WHERE secondary_server_name = @secondary_server AND secondary_database_name = @database

  IF (@@ROWCOUNT <> 1)
  BEGIN
      ROLLBACK TRANSACTION
      RAISERROR (14442,-1,-1)
      return(1)
  END

  -- let everyone know that we are the new primary
  UPDATE msdb.dbo.log_shipping_primaries 
    SET primary_server_name = @secondary_server, primary_database_name = @database, source_directory = @new_source
    WHERE primary_server_name = @primary_server AND primary_database_name = @database

  IF (@@ROWCOUNT <> 1)
  BEGIN
      ROLLBACK TRANSACTION
      RAISERROR (14442,-1,-1)
      return(1)
  END
  COMMIT TRANSACTION

END
go

/**************************************************************/
/* sp_create_log_shipping_monitor_account                     */
/**************************************************************/
PRINT ''
PRINT 'Creating procedure sp_create_log_shipping_monitor_account...'
go
IF (EXISTS (SELECT * from msdb.dbo.sysobjects WHERE name = N'sp_create_log_shipping_monitor_account' AND type = N'P')  )
  drop procedure sp_create_log_shipping_monitor_account
go
CREATE PROCEDURE sp_create_log_shipping_monitor_account @password sysname
AS
BEGIN
  DECLARE @rv INT
  SET NOCOUNT ON
-- raise an error if the password already exists
  if exists(select * from master.dbo.syslogins where loginname = N'log_shipping_monitor_probe')
  begin
    raiserror(15025,-1,-1,N'log_shipping_monitor_probe')
    RETURN (1) -- error
  end

  IF (@password = N'')
  BEGIN
    EXECUTE @rv = sp_addlogin N'log_shipping_monitor_probe', @defdb = N'msdb'
    IF @@error <>0 or @rv <> 0
      RETURN (1) -- error
  END
  ELSE
  BEGIN
    EXECUTE @rv = sp_addlogin N'log_shipping_monitor_probe', @password, N'msdb'
    IF @@error <>0 or @rv <> 0
      RETURN (1) -- error
  END

  EXECUTE @rv = sp_grantdbaccess N'log_shipping_monitor_probe', N'log_shipping_monitor_probe'
  IF @@error <>0 or @rv <> 0
    RETURN (1) -- error

  GRANT UPDATE ON log_shipping_primaries   TO log_shipping_monitor_probe
  GRANT UPDATE ON log_shipping_secondaries TO log_shipping_monitor_probe
  GRANT SELECT ON log_shipping_primaries   TO log_shipping_monitor_probe
  GRANT SELECT ON log_shipping_secondaries TO log_shipping_monitor_probe

  RETURN (0)
END
go

/**************************************************************/
/* INTEGRATION SERVICES SECTION                               */
/**************************************************************/

USE msdb
GO

/***************************************************************/
/* Create SSIS roles										   */
/***************************************************************/
if not exists (select * from dbo.sysusers where [name] = N'db_ssisadmin' and [issqlrole] = 1)
BEGIN
EXEC sp_addrole N'db_ssisadmin' 
END
GO

if not exists (select * from dbo.sysusers where [name] = N'db_ssisltduser' and [issqlrole] = 1)
BEGIN
EXEC sp_addrole N'db_ssisltduser'
END
GO

if not exists (select * from dbo.sysusers where [name] = N'db_ssisoperator' and [issqlrole] = 1)
BEGIN
EXEC sp_addrole N'db_ssisoperator'
END
GO


if not exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sysssispackages]'))
BEGIN
CREATE TABLE [dbo].[sysssispackages] (
   [name] [sysname] NOT NULL ,
   [id] [uniqueidentifier] NOT NULL ,
   [description] [nvarchar] (1024) NULL ,
   [createdate] [datetime] NOT NULL ,
   [folderid] [uniqueidentifier] NOT NULL ,
   [ownersid] [varbinary] (85) NOT NULL ,
   [packagedata] [image] NOT NULL ,
   [packageformat] [int] NOT NULL,
   [packagetype] [int] NOT NULL CONSTRAINT [DF__sysssispackages] DEFAULT (0),
   [vermajor] [int] NOT NULL,
   [verminor] [int] NOT NULL,
   [verbuild] [int] NOT NULL,
   [vercomments] [nvarchar] (1024) NULL,
   [verid] [uniqueidentifier] NOT NULL,
   [isencrypted] [bit] NOT NULL CONSTRAINT [DF__sysssispackages_2] DEFAULT (0),
   [readrolesid] [varbinary] (85) NULL,
   [writerolesid] [varbinary] (85) NULL,
   CONSTRAINT [pk_sysssispackages] PRIMARY KEY NONCLUSTERED 
   (
      [folderid],
      [name]
   ) ON [PRIMARY] ,
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
END
else
BEGIN
IF NOT EXISTS (
  select * from msdb.dbo.syscolumns where name='isencrypted' and id =
      (select id from msdb.dbo.sysobjects where name='sysssispackages'))
BEGIN
  ALTER TABLE [dbo].[sysssispackages] ADD [isencrypted] [bit] NOT NULL CONSTRAINT [DF__sysssispackages_2] DEFAULT (0)
  ALTER TABLE [dbo].[sysssispackages] ADD [readrolesid] [varbinary] (85) NULL
  ALTER TABLE [dbo].[sysssispackages] ADD [writerolesid] [varbinary] (85) NULL
END
ELSE
BEGIN
  IF NOT EXISTS (
    select * from msdb.dbo.syscolumns where name='readrolesid' and id =
        (select id from msdb.dbo.sysobjects where name='sysssispackages'))
  BEGIN
    ALTER TABLE [dbo].[sysssispackages] DROP COLUMN [readrole]
    ALTER TABLE [dbo].[sysssispackages] DROP COLUMN [writerole]
    ALTER TABLE [dbo].[sysssispackages] ADD [readrolesid] [varbinary] (85) NULL
    ALTER TABLE [dbo].[sysssispackages] ADD [writerolesid] [varbinary] (85) NULL
  END
END
END
GO

/**************************************************************/
/* sysmaintplan_plans                                      */
/**************************************************************/
PRINT ''
PRINT 'Creating view sysmaintplan_plans...'
go
IF (NOT OBJECT_ID(N'dbo.sysmaintplan_plans', 'V') IS NULL)
  DROP VIEW sysmaintplan_plans
go
CREATE VIEW sysmaintplan_plans
AS
   SELECT
   s.name AS [name],
   s.id AS [id],
   s.description AS [description],
   s.createdate AS [create_date],
   suser_sname(s.ownersid) AS [owner],
   s.vermajor AS [version_major],
   s.verminor AS [version_minor],
   s.verbuild AS [version_build],
   s.vercomments AS [version_comments],
   ISNULL((select TOP 1 msx_plan from sysmaintplan_subplans where plan_id = s.id), 0) AS [from_msx],
   CASE WHEN (NOT EXISTS (select TOP 1 msx_job_id 
                          from sysmaintplan_subplans subplans, sysjobservers jobservers
                          where plan_id = s.id 
                          and msx_job_id is not null
                          and subplans.msx_job_id = jobservers.job_id
                          and server_id != 0)) 
        then 0 
        else 1 END AS [has_targets]
   FROM
   msdb.dbo.sysssispackages AS s
   WHERE
   (s.folderid = '08aa12d5-8f98-4dab-a4fc-980b150a5dc8' and s.packagetype = 6)
go

if not exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sysssispackagefolders]'))
BEGIN
CREATE TABLE [dbo].[sysssispackagefolders] (
   [folderid] [uniqueidentifier] NOT NULL ,
   [parentfolderid] [uniqueidentifier] NULL ,
   [foldername] [sysname] NOT NULL ,
   CONSTRAINT [PK_sysssispackagefolders] PRIMARY KEY NONCLUSTERED 
   (
      [folderid]
   )  ON [PRIMARY],
   CONSTRAINT [U_sysssispackagefoldersuniquepath] UNIQUE NONCLUSTERED 
   (
      [parentfolderid], 
      [foldername]
   ) ON [PRIMARY]
) ON [PRIMARY]
END
GO

-- WARNING! IMPORTANT! If you change sysssislog table schema,
-- be sure to update \dts\src\dtr\runtime\logproviders.cpp !!!

if not exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sysssislog]'))
BEGIN
CREATE TABLE [dbo].[sysssislog] (
   [id] [int] NOT NULL IDENTITY PRIMARY KEY,
   [event] [sysname] NOT NULL,
   [computer] [nvarchar] (128) NOT NULL,
   [operator] [nvarchar] (128) NOT NULL,
   [source] [nvarchar] (1024) NOT NULL,
   [sourceid] [uniqueidentifier] NOT NULL,
   [executionid] [uniqueidentifier] NOT NULL,
   [starttime] [datetime] NOT NULL,
   [endtime] [datetime] NOT NULL,
   [datacode] [int] NOT NULL,
   [databytes] [image] NULL,
   [message] [nvarchar] (2048) NOT NULL,
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
END
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_addlogentry]'))
    drop procedure [dbo].[sp_ssis_addlogentry]   

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_addlogentry]
  @event sysname,
  @computer nvarchar(128),
  @operator nvarchar(128),
  @source nvarchar(1024),
  @sourceid uniqueidentifier,
  @executionid uniqueidentifier,
  @starttime datetime,
  @endtime datetime,
  @datacode int,
  @databytes image,
  @message nvarchar(2048)
AS
  INSERT INTO sysssislog (
      event,
      computer,
      operator,
      source,
      sourceid,
      executionid,
      starttime,
      endtime,
      datacode,
      databytes,
      message )
  VALUES (
      @event,
      @computer,
      @operator,
      @source,
      @sourceid,
      @executionid,
      @starttime,
      @endtime,
      @datacode,
      @databytes,
      @message )
  RETURN 0
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_listpackages]'))
    drop procedure [dbo].[sp_ssis_listpackages]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_listpackages]
  @folderid uniqueidentifier
AS
  SELECT
      name,
      id,
      description,
      createdate,
      folderid,
      datalength(packagedata),
      vermajor,
      verminor,
      verbuild,
      vercomments,
      verid
  FROM
      sysssispackages
  WHERE
      [folderid] = @folderid
  ORDER BY
      name
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_listfolders]'))
    drop procedure [dbo].[sp_ssis_listfolders]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_listfolders]
  @parentfolderid uniqueidentifier = NULL
AS
  SELECT
   folderid,
   parentfolderid,
   foldername
  FROM
      sysssispackagefolders
  WHERE
      [parentfolderid] = @parentfolderid OR 
      (@parentfolderid IS NULL AND [parentfolderid] IS NULL)
  ORDER BY 
      foldername
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_deletepackage]'))
    drop procedure [dbo].[sp_ssis_deletepackage]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_deletepackage]
  @name sysname,
  @folderid uniqueidentifier
AS
  DECLARE @sid varbinary(85)
  DECLARE @writerolesid varbinary(85)
  DECLARE @writerole nvarchar(128)
  SELECT
      @sid = [ownersid],
      @writerolesid = [writerolesid]
  FROM
      sysssispackages
  WHERE
      [name] = @name AND
      [folderid] = @folderid
  IF @sid IS NOT NULL
  BEGIN
      --// The row exists, check security
      IF @writerolesid IS NOT NULL
      BEGIN
          SELECT @writerole = [name] FROM sys.database_principals WHERE [type] = ''R'' AND [sid] = @writerolesid
          IF @writerole IS NULL SET @writerole = ''db_ssisadmin''
      END
      IF @writerole IS NULL
      BEGIN
          IF (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
          BEGIN
              IF (@sid<>SUSER_SID()) OR (IS_MEMBER(''db_ssisltduser'')<>1)
              BEGIN
                  RAISERROR (14307, -1, -1, @name)
                  RETURN 1  -- Failure
              END
          END
      END
      ELSE
      BEGIN
          -- If writerrole is set for this package, 
          -- Allow sysadmins and the members of writer role to delete this package
          IF (IS_MEMBER(@writerole)<>1)  AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
          BEGIN
              IF (@sid<>SUSER_SID()) OR (IS_MEMBER(''db_ssisltduser'')<>1)
              BEGIN
                  RAISERROR (14307, -1, -1, @name)
                  RETURN 1  -- Failure
              END
          END
      END
  END
  DELETE FROM sysssispackages
  WHERE
      [name] = @name AND
      [folderid] = @folderid
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_deletefolder]'))
    drop procedure [dbo].[sp_ssis_deletefolder]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_deletefolder]
  @folderid uniqueidentifier
AS
   DECLARE @name  sysname
   DECLARE @count int

   IF @folderid = ''00000000-0000-0000-0000-000000000000''
   BEGIN
      RAISERROR (14307, -1, -1, ''00000000-0000-0000-0000-000000000000'')
      RETURN 1  -- Failure
   END

   SELECT
       @name = [foldername]
   FROM
       sysssispackagefolders
   WHERE
       [folderid] = @folderid
   IF @name IS NOT NULL
   BEGIN
       --// The row exists, check security
       IF (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
       BEGIN
           IF (IS_MEMBER(''db_ssisltduser'')<>1)
           BEGIN
               RAISERROR (14307, -1, -1, @name)
               RETURN 1  -- Failure
           END
       END
   END

   -- Get the number of packages in this folder
   SELECT
      @count = count(*)
   FROM
      sysssispackages
   WHERE
      [folderid] = @folderid

   -- Are there any packages in this folder
   IF @count > 0
   BEGIN
      -- Yes, do not delete
      RAISERROR (14593, -1, -1, @name)
      RETURN 1  -- Failure
   END

   -- Get the number of folders in this folder
   SELECT
      @count = count(*)
   FROM
      sysssispackagefolders
   WHERE
      [parentfolderid] = @folderid

   -- Are there any folders in this folder
   IF @count > 0
   BEGIN
      -- Yes, do not delete
      RAISERROR (14593, -1, -1, @name)
      RETURN 1  -- Failure
   END

   DELETE FROM sysssispackagefolders
   WHERE
       [folderid] = @folderid
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_getpackage]'))
    drop procedure [dbo].[sp_ssis_getpackage]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_getpackage]
  @name sysname,
  @folderid uniqueidentifier
AS
  DECLARE @sid varbinary(85)
  DECLARE @isencrypted bit
  DECLARE @readrolesid varbinary(85)
  DECLARE @readrole nvarchar(128)
  --// Check security, if the row exists
  SELECT @sid = [ownersid], @readrolesid = [readrolesid] FROM sysssispackages WHERE [name] = @name AND [folderid] = @folderid
  IF @sid IS NOT NULL
  BEGIN
      IF @readrolesid IS NOT NULL
      BEGIN
          SELECT @readrole = [name] FROM sys.database_principals WHERE [type] = ''R'' AND [sid] = @readrolesid
          IF @readrole IS NULL SET @readrole = ''db_ssisadmin''
      END
      IF @readrole IS NOT NULL
      BEGIN
          IF (IS_MEMBER(@readrole)<>1) AND (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
          BEGIN
              IF (IS_MEMBER(''db_ssisltduser'')<>1) OR (@sid<>SUSER_SID())
              BEGIN
                  RAISERROR (14307, -1, -1, @name)
                  RETURN 1  -- Failure
              END
          END
      END
      ELSE
      BEGIN
          IF (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1) AND (IS_MEMBER(''db_ssisoperator'')<>1)
          BEGIN
              IF (IS_MEMBER(''db_ssisltduser'')<>1) OR (@sid<>SUSER_SID())
              BEGIN
                  RAISERROR (14586, -1, -1, @name)
                  RETURN 1  -- Failure
              END
          END
      END
  END

  SELECT
      packagedata
  FROM
      sysssispackages
  WHERE
      [name] = @name AND
      [folderid] = @folderid
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_getfolder]'))
    drop procedure [dbo].[sp_ssis_getfolder]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_getfolder]
  @name sysname,
  @parentfolderid uniqueidentifier
AS
  SELECT
   folder.folderid,
   folder.foldername,
   folder.parentfolderid,
   parent.foldername
  FROM
      sysssispackagefolders folder 
  LEFT OUTER JOIN 
      sysssispackagefolders parent
  ON
      folder.parentfolderid = parent.folderid
  WHERE
      folder.foldername = @name AND
      (folder.parentfolderid = @parentfolderid OR 
      (@parentfolderid IS NULL AND folder.parentfolderid IS NULL))
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_putpackage]'))
    drop procedure [dbo].[sp_ssis_putpackage]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_putpackage]
  @name sysname,
  @id uniqueidentifier,
  @description nvarchar(1024),
  @createdate datetime,
  @folderid uniqueidentifier,
  @packagedata image,
  @packageformat int,
  @packagetype int,
  @vermajor int,
  @verminor int,
  @verbuild int,
  @vercomments nvarchar(1024),
  @verid uniqueidentifier
AS
  SET NOCOUNT ON
  DECLARE @sid varbinary(85)
  DECLARE @writerolesid varbinary(85)
  DECLARE @writerole nvarchar(128)
  --// Determine if we should INSERT or UPDATE
  SELECT @sid = [ownersid], @writerolesid = [writerolesid] FROM sysssispackages WHERE [name] = @name AND [folderid] = @folderid
  IF @sid IS NOT NULL
  BEGIN
      --// The row exists, check security
      IF @writerolesid IS NOT NULL
      BEGIN
          SELECT @writerole = [name] FROM sys.database_principals WHERE [type] = ''R'' AND [sid] = @writerolesid
          IF @writerole IS NULL SET @writerole = ''db_ssisadmin''
      END
      IF @writerole IS NULL
      BEGIN
          IF (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
          BEGIN
              IF (@sid<>SUSER_SID()) OR (IS_MEMBER(''db_ssisltduser'')<>1)
              BEGIN
                  RAISERROR (14307, -1, -1, @name)
                  RETURN 1  -- Failure
              END
          END
      END
      ELSE
      BEGIN
          IF (IS_MEMBER(@writerole)<>1) AND (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
          BEGIN
              IF (@sid<>SUSER_SID()) OR (IS_MEMBER(''db_ssisltduser'')<>1)
              BEGIN
                  RAISERROR (14307, -1, -1, @name)
                  RETURN 1  -- Failure
              END
          END
      END
      --// Security check passed, UPDATE now
      UPDATE sysssispackages
      SET
          id = @id,
          description = @description,
          createdate = @createdate,
          packagedata = @packagedata,
          packageformat = @packageformat,
          packagetype = @packagetype,
          vermajor = @vermajor,
          verminor = @verminor,
          verbuild = @verbuild,
          vercomments = @vercomments,
          verid = @verid
      WHERE
          name = @name AND folderid = @folderid
  END
  ELSE
  BEGIN
      --// The row does not exist, check security
      IF (IS_MEMBER(''db_ssisltduser'')<>1) AND (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
      BEGIN
          RAISERROR (14307, -1, -1, @name)
          RETURN 1  -- Failure
      END
      --// Security check passed, INSERT now
      INSERT INTO sysssispackages (
          name,
          id,
          description,
          createdate,
          folderid,
          ownersid,
          packagedata,
          packageformat,
          packagetype,
          vermajor,
          verminor,
          verbuild,
          vercomments,
          verid )
      VALUES (
          @name,
          @id,
          @description,
          @createdate,
          @folderid,
          SUSER_SID(),
          @packagedata,
          @packageformat,
          @packagetype,
          @vermajor,
          @verminor,
          @verbuild,
          @vercomments,
          @verid )
  END
  RETURN 0    -- SUCCESS
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_checkexists]'))
    drop procedure [dbo].[sp_ssis_checkexists]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_checkexists]
  @name sysname,
  @folderid uniqueidentifier
AS
  SET NOCOUNT ON
  SELECT TOP 1 1 FROM sysssispackages WHERE [name] = @name AND [folderid] = @folderid
  RETURN 0    -- SUCCESS
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_addfolder]'))
    drop procedure [dbo].[sp_ssis_addfolder]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_addfolder]
  @parentfolderid uniqueidentifier,
  @name sysname,
  @folderid uniqueidentifier = NULL
AS
   --Check security
   IF (IS_MEMBER(''db_ssisltduser'')<>1) AND (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
   BEGIN
       RAISERROR (14591, -1, -1, @name)
       RETURN 1  -- Failure
   END

   --// Security check passed, INSERT now
   INSERT INTO sysssispackagefolders (folderid, parentfolderid, foldername)
   VALUES (ISNULL(@folderid, NEWID()), @parentfolderid, @name)
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_renamefolder]'))
    drop procedure [dbo].[sp_ssis_renamefolder]

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_renamefolder]
  @folderid uniqueidentifier,
  @name sysname
AS
   --Check security
   IF (IS_MEMBER(''db_ssisltduser'')<>1) AND (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
   BEGIN
       RAISERROR (14591, -1, -1, @name)
       RETURN 1  -- Failure
   END

   --// Security check passed, INSERT now
   UPDATE sysssispackagefolders
   SET [foldername] = @name
   WHERE [folderid] = @folderid
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_setpackageroles]'))
DROP PROCEDURE [dbo].[sp_ssis_setpackageroles]
GO

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_setpackageroles]
  @name sysname,
  @folderid uniqueidentifier,
  @readrole nvarchar (128),
  @writerole nvarchar (128)
AS
  SET NOCOUNT ON
  DECLARE @sid varbinary(85)
  --// Determine if we should INSERT or UPDATE
  SELECT @sid = ownersid FROM sysssispackages WHERE name = @name AND folderid = @folderid
  IF @sid IS NOT NULL
  BEGIN
      --// The row exists, check security
      IF (IS_MEMBER(''db_ssisadmin'')<>1) AND (IS_SRVROLEMEMBER(''sysadmin'')<>1)
      BEGIN
          IF (@sid<>SUSER_SID())
          BEGIN
              RAISERROR (14307, -1, -1, @name)
              RETURN 1  -- Failure
          END
      END
      --// Security check passed, UPDATE now
      DECLARE @readrolesid varbinary(85)
      DECLARE @writerolesid varbinary(85)
      SELECT @readrolesid = [sid] FROM sys.database_principals WHERE [type] = ''R'' AND [name] = @readrole
      SELECT @writerolesid = [sid] FROM sys.database_principals WHERE [type] = ''R'' AND [name] = @writerole
      IF @readrolesid IS NULL AND @readrole IS NOT NULL
      BEGIN
          RAISERROR (15014, -1, -1, @readrole)
          RETURN 1
      END
      IF @writerolesid IS NULL AND @writerole IS NOT NULL
      BEGIN
          RAISERROR (15014, -1, -1, @writerole)
          RETURN 1
      END
      UPDATE sysssispackages
      SET
          [readrolesid] = @readrolesid,
          [writerolesid] = @writerolesid
      WHERE
          name = @name AND folderid = @folderid
  END
  ELSE
  BEGIN
      RAISERROR (14307, -1, -1, @name)
      RETURN 1  -- Failure
  END
  RETURN 0    -- SUCCESS
'
GO

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sp_ssis_getpackageroles]'))
DROP PROCEDURE [dbo].[sp_ssis_getpackageroles]
GO

execute sp_executesql 
 N'CREATE PROCEDURE [dbo].[sp_ssis_getpackageroles]
  @name sysname,
  @folderid uniqueidentifier
AS
  DECLARE @readrolesid varbinary(85)
  DECLARE @writerolesid varbinary(85)
  DECLARE @readrole nvarchar(128)
  DECLARE @writerole nvarchar(128)
  SELECT
      @readrolesid = [readrolesid],
      @writerolesid = [writerolesid]
  FROM
      sysssispackages
  WHERE
      [name] = @name AND
      [folderid] = @folderid
  SELECT @readrole = [name] FROM sys.database_principals WHERE [type] = ''R'' AND [sid] = @readrolesid
  SELECT @writerole = [name] FROM sys.database_principals WHERE [type] = ''R'' AND [sid] = @writerolesid
  SELECT @readrole AS readrole, @writerole AS writerole
'
GO


GRANT  EXECUTE  ON [dbo].[sp_ssis_addlogentry]     TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_addlogentry]     TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_deletepackage]   TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_deletepackage]   TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_deletepackage]   TO [db_ssisoperator]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getpackage]      TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getpackage]      TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getpackage]      TO [db_ssisoperator]
GRANT  EXECUTE  ON [dbo].[sp_ssis_listpackages]    TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_listpackages]    TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_listpackages]    TO [db_ssisoperator]
GRANT  EXECUTE  ON [dbo].[sp_ssis_putpackage]      TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_putpackage]      TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_putpackage]      TO [db_ssisoperator]
GRANT  EXECUTE  ON [dbo].[sp_ssis_checkexists]     TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_checkexists]     TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_checkexists]     TO [db_ssisoperator]
GRANT  EXECUTE  ON [dbo].[sp_ssis_listfolders]     TO [db_ssisadmin]  
GRANT  EXECUTE  ON [dbo].[sp_ssis_listfolders]     TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_listfolders]     TO [db_ssisoperator] 
GRANT  EXECUTE  ON [dbo].[sp_ssis_deletefolder]    TO [db_ssisadmin]  
GRANT  EXECUTE  ON [dbo].[sp_ssis_deletefolder]    TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_addfolder]       TO [db_ssisadmin]  
GRANT  EXECUTE  ON [dbo].[sp_ssis_addfolder]       TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_renamefolder]    TO [db_ssisadmin]  
GRANT  EXECUTE  ON [dbo].[sp_ssis_renamefolder]    TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getfolder]       TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getfolder]       TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getfolder]       TO [db_ssisoperator]
GRANT  EXECUTE  ON [dbo].[sp_ssis_setpackageroles] TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_setpackageroles] TO [db_ssisltduser]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getpackageroles] TO [db_ssisadmin]
GRANT  EXECUTE  ON [dbo].[sp_ssis_getpackageroles] TO [db_ssisltduser]
GO

GRANT  ALL      ON [dbo].[sysssislog]            TO [db_ssisadmin]
GRANT  INSERT   ON [dbo].[sysssislog]            TO [db_ssisltduser]
GRANT  SELECT   ON [dbo].[sysssislog]            TO [db_ssisltduser]
GRANT  INSERT   ON [dbo].[sysssislog]            TO [db_ssisoperator]
GRANT  SELECT   ON [dbo].[sysssislog]            TO [db_ssisoperator]
GO

-- Maintenance Plans
 -- Allow SQLAgent on target servers to gather information about
 -- maintenance plans from the master.
GRANT EXECUTE ON sp_maintplan_subplans_by_job  TO SQLAgentUserRole
GRANT EXECUTE ON sp_maintplan_subplans_by_job  TO TargetServersRole


/**************************************************************/
/*                                                            */
/*  D  A  T  A     C  O  L  L  E  C  T  O  R                  */
/*                                                            */
/**************************************************************/
USE msdb
GO

---------------------------------------------------------------
-- Data Collector: Security: Database Principals
---------------------------------------------------------------

PRINT ''
PRINT 'Create dc_operator role...'
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'dc_operator' AND type = 'R'))
BEGIN
    CREATE ROLE [dc_operator]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'dc_operator' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [dc_operator]
        CREATE ROLE [dc_operator]
    END
END
GO

EXECUTE sp_addrolemember @rolename = 'db_ssisltduser' , 
                   @membername = 'dc_operator' 
GO

EXECUTE sp_addrolemember @rolename = 'db_ssisoperator' , 
                   @membername = 'dc_operator' 
GO

EXECUTE sp_addrolemember @rolename = 'SQLAgentUserRole' , 
                   @membername = 'dc_operator' 
GO

PRINT ''
PRINT 'Create dc_admin role...'
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'dc_admin' AND type = 'R'))
BEGIN
    CREATE ROLE [dc_admin]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'dc_admin' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [dc_admin]
        CREATE ROLE [dc_admin]
    END
END
GO

EXECUTE sp_addrolemember @rolename = 'dc_operator' , 
                   @membername = 'dc_admin' 
GO

PRINT ''
PRINT 'Create dc_proxy role...'
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'dc_proxy' AND type = 'R'))
BEGIN
    CREATE ROLE [dc_proxy]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'dc_proxy' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [dc_proxy]
        CREATE ROLE [dc_proxy]
    END
END
GO

EXECUTE sp_addrolemember @rolename = 'db_ssisltduser' , 
                   @membername = 'dc_proxy' 
GO

EXECUTE sp_addrolemember @rolename = 'db_ssisoperator' , 
                   @membername = 'dc_proxy' 
GO

PRINT ''
PRINT 'Create loginless user that has ownership of data collector agent-related securables...'
IF (NOT EXISTS(SELECT * FROM sys.database_principals WHERE NAME = 'MS_DataCollectorInternalUser'))
BEGIN
    CREATE USER [MS_DataCollectorInternalUser] WITHOUT LOGIN
END
GO

EXECUTE sp_addrolemember @rolename = 'db_ssisoperator' , 
                   @membername = 'MS_DataCollectorInternalUser' 
GO

EXECUTE sp_addrolemember @rolename = 'SQLAgentUserRole' , 
                   @membername = 'MS_DataCollectorInternalUser' 
GO

EXECUTE sp_addrolemember @rolename = 'dc_admin' , 
                   @membername = 'MS_DataCollectorInternalUser' 
GO

GRANT IMPERSONATE ON USER::[MS_DataCollectorInternalUser] TO [dc_admin];
GO

---------------------------------------------------------------
-- Configuration store
---------------------------------------------------------------
IF (OBJECT_ID(N'[dbo].[syscollector_config_store_internal]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [dbo].[syscollector_config_store_internal]...'
    CREATE TABLE [dbo].[syscollector_config_store_internal] (
        parameter_name                nvarchar(128) NOT NULL,
        parameter_value                sql_variant NULL,
        CONSTRAINT [PK_syscollector_config_store_internal_paremeter_name] PRIMARY KEY CLUSTERED (parameter_name ASC)
        )
END
GO

IF (NOT OBJECT_ID(N'[dbo].[syscollector_config_store]', 'V') IS NULL)
BEGIN
    PRINT 'Dropping view [dbo].[syscollector_config_store]...'
    DROP VIEW [dbo].[syscollector_config_store]
END
GO

PRINT 'Creating view [dbo].[syscollector_config_store]...'
GO
CREATE VIEW [dbo].[syscollector_config_store]
AS
    SELECT
        s.parameter_name,
        s.parameter_value
    FROM 
        [dbo].[syscollector_config_store_internal] s
GO

-- populate config store with known name and value pairs
IF (NOT EXISTS(SELECT * FROM [dbo].[syscollector_config_store_internal]
                WHERE parameter_name = N'MDWInstance'))
BEGIN
INSERT INTO [dbo].[syscollector_config_store_internal] (
    parameter_name, 
    parameter_value
)
VALUES
(
    N'MDWInstance',
    NULL
)
END

IF (NOT EXISTS(SELECT * FROM [dbo].[syscollector_config_store_internal]
                WHERE parameter_name = N'MDWDatabase'))
BEGIN
INSERT INTO [dbo].[syscollector_config_store_internal] (
    parameter_name, 
    parameter_value
)
VALUES
(
    N'MDWDatabase',
    NULL
)
END

IF (NOT EXISTS(SELECT * FROM [dbo].[syscollector_config_store_internal]
                WHERE parameter_name = 'CollectorEnabled'))
BEGIN
INSERT INTO [dbo].[syscollector_config_store_internal] (
    parameter_name, 
    parameter_value
)
VALUES
(
    'CollectorEnabled',
    0
)
END

IF (NOT EXISTS(SELECT * FROM [dbo].[syscollector_config_store_internal]
                WHERE parameter_name = 'CacheWindow'))
BEGIN
INSERT INTO [dbo].[syscollector_config_store_internal] (
    parameter_name, 
    parameter_value
)
VALUES
(
    'CacheWindow',
    1  --By default, the collector will keep 1 window's worth of uploads
)
END

IF (NOT EXISTS(SELECT * FROM [dbo].[syscollector_config_store_internal]
                WHERE parameter_name = 'CacheDirectory'))
BEGIN
INSERT INTO [dbo].[syscollector_config_store_internal] (
    parameter_name, 
    parameter_value
)
VALUES
(
    'CacheDirectory',
    NULL
)
END

GO

---------------------------------------------------------------
-- Access collector level properties
---------------------------------------------------------------

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_verify_collector_state]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_verify_collector_state]...'
    DROP PROCEDURE [dbo].[sp_syscollector_verify_collector_state]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_verify_collector_state]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_verify_collector_state]
    @desired_state          int
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    DECLARE @collector_enabled      INT
    SET @collector_enabled = CONVERT(int, (SELECT parameter_value FROM dbo.syscollector_config_store_internal
                            WHERE parameter_name = 'CollectorEnabled'))

    IF (@collector_enabled IS NULL)
    BEGIN
        RAISERROR(14691, -1, -1)
        RETURN(1)
    END

    IF (@collector_enabled = 0) AND (@desired_state = 1)
    BEGIN
        RAISERROR(14681, -1, -1)
        RETURN(1)
    END

    IF (@collector_enabled = 1) AND (@desired_state = 0)
    BEGIN
        RAISERROR(14690, -1, -1)
        RETURN(1)
    END

    RETURN(0)
END
GO

IF (NOT OBJECT_ID('dbo.sp_syscollector_set_warehouse_instance_name', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_set_warehouse_instance_name]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_set_warehouse_instance_name]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_set_warehouse_instance_name]
    @instance_name                    sysname = NULL
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN(1) -- Failure
    END

    -- Check if the collector is disabled
    DECLARE @retVal int
    EXEC @retVal = [dbo].[sp_syscollector_verify_collector_state] @desired_state = 0
    IF (@retVal <> 0)
        RETURN (1)

    UPDATE [msdb].[dbo].[syscollector_config_store_internal]
    SET parameter_value = @instance_name
    WHERE parameter_name = N'MDWInstance'

    RETURN (0)
END
GO

IF (NOT OBJECT_ID('dbo.sp_syscollector_set_warehouse_database_name', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_set_warehouse_database_name]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_set_warehouse_database_name]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_set_warehouse_database_name]
    @database_name                    sysname = NULL
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN(1) -- Failure
    END

    -- Check if the collector is disabled
    DECLARE @retVal int
    EXEC @retVal = [dbo].[sp_syscollector_verify_collector_state] @desired_state = 0
    IF (@retVal <> 0)
        RETURN (1)

    UPDATE [msdb].[dbo].[syscollector_config_store_internal]
    SET parameter_value = @database_name
    WHERE parameter_name = N'MDWDatabase'

    RETURN (0)
END
GO

IF (NOT OBJECT_ID('dbo.sp_syscollector_set_cache_directory', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_set_cache_directory]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_set_cache_directory]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_set_cache_directory]
    @cache_directory                    nvarchar(255) = NULL
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN(1) -- Failure
    END

    SET @cache_directory = NULLIF(LTRIM(RTRIM(@cache_directory)), N'')

    -- Check if the collector is disabled
    DECLARE @retVal int
    EXEC @retVal = [dbo].[sp_syscollector_verify_collector_state] @desired_state = 0
    IF (@retVal <> 0)
        RETURN (1)

    UPDATE [msdb].[dbo].[syscollector_config_store_internal]
    SET parameter_value = @cache_directory
    WHERE parameter_name = N'CacheDirectory'

    RETURN (0)
END
GO

IF (NOT OBJECT_ID('dbo.sp_syscollector_set_cache_window', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_set_cache_window]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_set_cache_window]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_set_cache_window]
    @cache_window                    int = 1
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN(1) -- Failure
    END

    -- Check if the collector is disabled
    DECLARE @retVal int
    EXEC @retVal = [dbo].[sp_syscollector_verify_collector_state] @desired_state = 0
    IF (@retVal <> 0)
        RETURN (1)

    IF (@cache_window < -1)
    BEGIN
        RAISERROR(14687, -1, -1, @cache_window)
        RETURN(1)
    END

    UPDATE [msdb].[dbo].[syscollector_config_store_internal]
    SET parameter_value = @cache_window
    WHERE parameter_name = N'CacheWindow'

    RETURN (0)
END
GO

IF (NOT OBJECT_ID('dbo.sp_syscollector_get_warehouse_connection_string', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_get_warehouse_connection_string]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_get_warehouse_connection_string]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_get_warehouse_connection_string]
    @connection_string              nvarchar(512) = NULL OUTPUT
AS
BEGIN
    DECLARE @instance_name sysname
    DECLARE @database_name sysname
    DECLARE @user_name sysname
    DECLARE @password sysname
    DECLARE @product_version  nvarchar(128) -- SERVERPROPERTY('ProductVersion') returns value of type nvarchar(128)
    DECLARE @provider_name  nvarchar(128)

    SELECT @instance_name = CONVERT(sysname,parameter_value)
    FROM [msdb].[dbo].[syscollector_config_store_internal]
    WHERE parameter_name = N'MDWInstance'

    IF (@instance_name IS NULL)
    BEGIN
        RAISERROR(14686, -1, -1)
        RETURN (1)
    END
    
    -- '"' is the delimiter for the sql client connection string
    SET @instance_name = QUOTENAME(@instance_name, '"')

    SELECT @database_name = CONVERT(sysname,parameter_value)
    FROM [msdb].[dbo].[syscollector_config_store_internal]
    WHERE parameter_name = N'MDWDatabase'

    IF (@database_name IS NULL)
    BEGIN
        RAISERROR(14686, -1, -1)
        RETURN (1)
    END

    SET @database_name = QUOTENAME(@database_name, '"')
    
    SET @product_version = CONVERT(nvarchar(128), SERVERPROPERTY('ProductVersion'))
	
    -- SQLNCLI11 remains as SQLNCLI11 for SQL12 
    SET @provider_name = 'SQLNCLI11'
    SET @connection_string = N'Data Source=' + @instance_name + N';Application Name="Data Collector - MDW";Initial Catalog=' + @database_name
    SET @connection_string = @connection_string + N';Use Encryption for Data=true;Trust Server Certificate=true;Provider=' + @provider_name 
    SET @connection_string = @connection_string + N';Integrated Security=SSPI;Connect Timeout=60;';

    RETURN (0)
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_snapshot_dm_exec_requests_internal]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_snapshot_dm_exec_requests_internal]...'
    DROP PROCEDURE [dbo].[sp_syscollector_snapshot_dm_exec_requests_internal]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_snapshot_dm_exec_requests_internal]...'
GO

CREATE PROC [dbo].[sp_syscollector_snapshot_dm_exec_requests_internal]
  @include_system_databases bit = 1
AS
BEGIN
    SET NOCOUNT ON

    -- Get the collection time as UTC time
    DECLARE @collection_time datetime
    SET @collection_time = GETDATE()

    SELECT
    CONVERT(INT, ROW_NUMBER() OVER (ORDER BY sess.session_id, ISNULL (req.request_id, -1), ISNULL (tasks.exec_context_id, -1)) ) AS row_id,
    -- IDs and Blocking IDs
    sess.session_id, 
    ISNULL (req.request_id, -1) AS request_id, 
    ISNULL (tasks.exec_context_id, -1) AS exec_context_id, 
    ISNULL (req.blocking_session_id, 0) AS blocking_session_id,
    CONVERT (BIT, CASE 
                    WHEN EXISTS (SELECT TOP 1 session_id FROM sys.dm_exec_requests bl WHERE bl.blocking_session_id = req.session_id) THEN 1
                    ELSE 0
                  END) AS is_blocking,
    ISNULL (waits.blocking_exec_context_id, 0) AS blocking_exec_context_id, 
    tasks.scheduler_id, 
    DB_NAME(req.database_id) as database_name, 
    req.[user_id], 

    -- State information
    LEFT (tasks.task_state, 10) AS task_state, 
    LEFT (req.status, 15) AS request_status, 
    LEFT (sess.status, 15) AS session_status,
    req.executing_managed_code, 

    -- Session information
    sess.login_time, 
    sess.is_user_process, 
    LEFT (ISNULL (sess.[host_name], ''), 20) AS [host_name], 
    LEFT (ISNULL (sess.[program_name], ''), 50) AS [program_name], 
    LEFT (ISNULL (sess.login_name, ''), 30) AS login_name, 

    -- Waits information
    LEFT (ISNULL (req.wait_type, ''), 45) AS wait_type, 
    LEFT (ISNULL (req.last_wait_type, ''), 45) AS last_wait_type, 
    ISNULL (waits.wait_duration_ms, 0) AS wait_duration_ms, 
    LEFT (ISNULL (req.wait_resource, ''), 50) AS wait_resource, 
    LEFT (ISNULL (waits.resource_description, ''), 140) AS resource_description,

    -- Transaction information
    req.transaction_id, 
    ISNULL(req.open_transaction_count, 0) AS open_transaction_count,
    COALESCE(req.transaction_isolation_level, sess.transaction_isolation_level) AS transaction_isolation_level,

    -- Request stats
    req.cpu_time AS request_cpu_time, 
    req.logical_reads AS request_logical_reads, 
    req.reads AS request_reads, 
    req.writes AS request_writes, 
    req.total_elapsed_time AS request_total_elapsed_time, 
    req.start_time AS request_start_time, 

    -- Session stats
    sess.memory_usage, 
    sess.cpu_time AS session_cpu_time, 
    sess.reads AS session_reads, 
    sess.writes AS session_writes, 
    sess.logical_reads AS session_logical_reads, 
    sess.total_scheduled_time AS session_total_scheduled_time, 
    sess.total_elapsed_time AS session_total_elapsed_time, 
    sess.last_request_start_time, 
    sess.last_request_end_time, 
    req.open_resultset_count AS open_resultsets, 
    sess.row_count AS session_row_count, 
    sess.prev_error, 
    tasks.pending_io_count, 

    -- Text/Plan handles
    ISNULL (req.command, 'AWAITING COMMAND') AS command,  
    req.plan_handle, 
    req.sql_handle, 
    req.statement_start_offset, 
    req.statement_end_offset,
    @collection_time AS collection_time
    FROM sys.dm_exec_sessions sess 
    LEFT OUTER MERGE JOIN sys.dm_exec_requests req  ON sess.session_id = req.session_id
    LEFT OUTER MERGE JOIN sys.dm_os_tasks tasks ON tasks.session_id = sess.session_id AND tasks.request_id = req.request_id AND tasks.task_address = req.task_address
    LEFT OUTER MERGE JOIN sys.dm_os_waiting_tasks waits ON waits.session_id = sess.session_id AND waits.waiting_task_address = req.task_address
    WHERE 
        sess.session_id <> @@SPID
        AND
        (
            (req.session_id IS NOT NULL AND (sess.is_user_process = 1 OR req.status COLLATE Latin1_General_BIN NOT IN ('background', 'sleeping')))    -- active request
                OR 
            (sess.session_id IN (SELECT DISTINCT blocking_session_id FROM sys.dm_exec_requests WHERE blocking_session_id != 0))                    -- not active, but head blocker
        )
        AND (@include_system_databases = 1 OR (req.database_id > 4 AND req.database_id < 32767))
    OPTION (FORCE ORDER)
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_snapshot_dm_exec_query_stats_internal]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_snapshot_dm_exec_query_stats_internal]...'
    DROP PROCEDURE [dbo].[sp_syscollector_snapshot_dm_exec_query_stats_internal]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_snapshot_dm_exec_query_stats_internal]...'
GO

CREATE PROC [dbo].[sp_syscollector_snapshot_dm_exec_query_stats_internal]
  @include_system_databases bit = 1
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @p1 datetime
    SET @p1 = GETDATE()

    SELECT 
        [sql_handle],
        statement_start_offset,
        statement_end_offset,
        -- Use ISNULL here and in other columns to handle in-progress queries that are not yet in sys.dm_exec_query_stats.  
        -- These values only come from sys.dm_exec_query_stats. If the plan does not show up in sys.dm_exec_query_stats 
        -- (first execution of a still-in-progress query, visible in sys.dm_exec_requests), these values will be NULL. 
        MAX (plan_generation_num) AS plan_generation_num,
        plan_handle,
        MIN (creation_time) AS creation_time, 
        MAX (last_execution_time) AS last_execution_time,
        SUM (execution_count) AS execution_count,
        SUM (total_worker_time) AS total_worker_time,
        MIN (min_worker_time) AS min_worker_time,           -- NULLable
        MAX (max_worker_time) AS max_worker_time,
        SUM (total_physical_reads) AS total_physical_reads,
        MIN (min_physical_reads) AS min_physical_reads,     -- NULLable
        MAX (max_physical_reads) AS max_physical_reads,
        SUM (total_logical_writes) AS total_logical_writes,
        MIN (min_logical_writes) AS min_logical_writes,     -- NULLable
        MAX (max_logical_writes) AS max_logical_writes,
        SUM (total_logical_reads) AS total_logical_reads,
        MIN (min_logical_reads) AS min_logical_reads,       -- NULLable
        MAX (max_logical_reads) AS max_logical_reads,
        SUM (total_clr_time) AS total_clr_time,
        MIN (min_clr_time) AS min_clr_time,                 -- NULLable
        MAX (max_clr_time) AS max_clr_time,
        SUM (total_elapsed_time) AS total_elapsed_time,
        MIN (min_elapsed_time) AS min_elapsed_time,         -- NULLable
        MAX (max_elapsed_time) AS max_elapsed_time,
        @p1 AS collection_time
    FROM
    (
        SELECT  
            [sql_handle],
            statement_start_offset,
            statement_end_offset,
            plan_generation_num,
            plan_handle,
            creation_time,
            last_execution_time,
            execution_count,
            total_worker_time,
            min_worker_time,
            max_worker_time,
            total_physical_reads,
            min_physical_reads,
            max_physical_reads,
            total_logical_writes,
            min_logical_writes,
            max_logical_writes,
            total_logical_reads,
            min_logical_reads,
            max_logical_reads,
            total_clr_time,
            min_clr_time,
            max_clr_time,
            total_elapsed_time,
            min_elapsed_time,
            max_elapsed_time 
        FROM sys.dm_exec_query_stats AS q
        -- Temporary workaround for VSTS #91422.  This should be removed if/when sys.dm_exec_query_stats reflects in-progress queries. 
        UNION ALL 
        SELECT 
            r.[sql_handle],
            r.statement_start_offset,
            r.statement_end_offset,
            ISNULL (qs.plan_generation_num, 0) AS plan_generation_num,
            r.plan_handle,
            ISNULL (qs.creation_time, r.start_time) AS creation_time,
            r.start_time AS last_execution_time,
            1 AS execution_count,
            -- dm_exec_requests shows CPU time as ms, while dm_exec_query_stats 
            -- uses microseconds.  Convert ms to us. 
            r.cpu_time * CAST(1000 as bigint) AS total_worker_time,
            qs.min_worker_time,     -- min should not be influenced by in-progress queries
            r.cpu_time * CAST(1000 as bigint) AS max_worker_time,
            r.reads AS total_physical_reads,
            qs.min_physical_reads,  -- min should not be influenced by in-progress queries
            r.reads AS max_physical_reads,
            r.writes AS total_logical_writes,
            qs.min_logical_writes,  -- min should not be influenced by in-progress queries
            r.writes AS max_logical_writes,
            r.logical_reads AS total_logical_reads,
            qs.min_logical_reads,   -- min should not be influenced by in-progress queries
            r.logical_reads AS max_logical_reads,
            qs.total_clr_time,      -- CLR time is not available in dm_exec_requests
            qs.min_clr_time,        -- CLR time is not available in dm_exec_requests
            qs.max_clr_time,        -- CLR time is not available in dm_exec_requests
            -- dm_exec_requests shows elapsed time as ms, while dm_exec_query_stats 
            -- uses microseconds.  Convert ms to us. 
            r.total_elapsed_time * CAST(1000 as bigint) AS total_elapsed_time,
            qs.min_elapsed_time,    -- min should not be influenced by in-progress queries
            r.total_elapsed_time * CAST(1000 as bigint) AS max_elapsed_time
        FROM sys.dm_exec_requests AS r 
        LEFT OUTER JOIN sys.dm_exec_query_stats AS qs ON r.plan_handle = qs.plan_handle AND r.statement_start_offset = qs.statement_start_offset 
            AND r.statement_end_offset = qs.statement_end_offset 
        WHERE r.sql_handle IS NOT NULL 
    ) AS query_stats 
    OUTER APPLY sys.dm_exec_sql_text (sql_handle) AS sql
    WHERE (@include_system_databases = 1 OR ([sql].dbid > 4 AND [sql].dbid < 32767))
    GROUP BY [sql_handle], plan_handle, statement_start_offset, statement_end_offset 
    ORDER BY [sql_handle], plan_handle, statement_start_offset, statement_end_offset
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_get_query_activity_collection_item_params]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_get_query_activity_collection_item_params]...'
    DROP PROCEDURE [dbo].[sp_syscollector_get_query_activity_collection_item_params]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_get_query_activity_collection_item_params]...'
GO

CREATE PROC [dbo].[sp_syscollector_get_query_activity_collection_item_params]
  @collection_item_id         int,
  @include_system_databases bit = 1 OUTPUT
AS
BEGIN
    -- Validate if collection item is valid
    DECLARE @retVal int
    DECLARE @name   sysname
    EXEC @retVal = dbo.sp_syscollector_verify_collection_item @collection_item_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
    BEGIN
        RETURN (1)
    END

    -- Validate if collector type is "Query Activity"
    IF NOT EXISTS(SELECT collector_type_uid FROM dbo.syscollector_collection_items
                 WHERE collector_type_uid = '14AF3C12-38E6-4155-BD29-F33E7966BA23'
                 AND collection_item_id = @collection_item_id)
    BEGIN
       -- TODO - Fix Error code
        RAISERROR(14262, -1, -1, '@collection_item_id', 'Collection item type is not Query Activity collector')
        RETURN (1)
    END


    -- Get collection set param value from collection item config param
    DECLARE @paramxml XML
    SELECT @paramxml = parameters
    FROM dbo.syscollector_collection_items
    WHERE collection_item_id = @collection_item_id
    
    SELECT  
    @include_system_databases = CollectionItem.Properties.value('(Databases/@IncludeSystemDatabases)[1]', 'bit')
    FROM @paramxml.nodes('
    declare namespace ns="DataCollectorType";
    /ns:QueryActivityCollector') 
    AS CollectionItem(Properties) 

    RETURN (0)
END

GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_snapshot_dm_exec_requests]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_snapshot_dm_exec_requests]...'
    DROP PROCEDURE [dbo].[sp_syscollector_snapshot_dm_exec_requests]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_snapshot_dm_exec_requests]...'
GO

CREATE PROC [dbo].[sp_syscollector_snapshot_dm_exec_requests]
  @collection_item_id         int
AS
BEGIN
    DECLARE @include_system_databases bit = 1

    DECLARE @returnValue int = 1
    EXEC @returnValue = [dbo].[sp_syscollector_get_query_activity_collection_item_params] @collection_item_id, @include_system_databases OUTPUT

    IF(@returnValue <> 1 )
    BEGIN
        EXEC [dbo].[sp_syscollector_snapshot_dm_exec_requests_internal]  @include_system_databases
    END
END

GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_snapshot_dm_exec_query_stats]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_snapshot_dm_exec_query_stats]...'
    DROP PROCEDURE [dbo].[sp_syscollector_snapshot_dm_exec_query_stats]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_snapshot_dm_exec_query_stats]...'
GO

CREATE PROC [dbo].[sp_syscollector_snapshot_dm_exec_query_stats]
  @collection_item_id         int
AS
BEGIN
    DECLARE @include_system_databases bit = 1

    DECLARE @returnValue int = 1
    EXEC @returnValue = [dbo].[sp_syscollector_get_query_activity_collection_item_params] @collection_item_id, @include_system_databases OUTPUT

    IF(@returnValue <> 1 )
    BEGIN
        EXEC [dbo].[sp_syscollector_snapshot_dm_exec_query_stats_internal]  @include_system_databases
    END
END

GO

-- 
-- Return the highest version of the Management Data Warehouse that this
-- Collector is no compatible with. Anything higher than this version
-- is fine.
--
-- If you change this number, make sure to change the corresponding versions
-- in HighestIncompatibleMDWVersion.cs
--
IF (NOT OBJECT_ID('dbo.fn_syscollector_highest_incompatible_mdw_version', 'FN') IS NULL)
BEGIN
    DROP FUNCTION [dbo].[fn_syscollector_highest_incompatible_mdw_version]
END
GO

PRINT ''
PRINT 'Creating function [dbo].[fn_syscollector_highest_incompatible_mdw_version]...'
GO
CREATE FUNCTION [dbo].[fn_syscollector_highest_incompatible_mdw_version]()
RETURNS nvarchar(50)
BEGIN
    RETURN '10.00.1300.13'  -- CTP6
END
GO
---------------------------------------------------------------
-- Collection set
---------------------------------------------------------------

IF (OBJECT_ID(N'[dbo].[syscollector_collection_sets_internal]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [dbo].[syscollector_collection_sets_internal]...'
    CREATE TABLE [dbo].[syscollector_collection_sets_internal] (
        collection_set_id            int IDENTITY NOT NULL,
        collection_set_uid            uniqueidentifier NOT NULL,      
        schedule_uid                uniqueidentifier NULL,          -- schedule to run collection or upload
        name                        sysname NOT NULL,               -- name of the collection set, must be unique
        name_id                        int NULL,                        -- sysmessage id of the name of the set (for localizing system collection set)
        target                        nvarchar(max) NULL,             -- future use
        is_running                    bit default 0 NOT NULL,         -- is the collection set active
        proxy_id                    int NULL,                       -- proxy to use to run the collection set
        is_system                    bit NOT NULL,                   -- indicates MS-shipped collection set
        collection_job_id            uniqueidentifier NULL,          -- id of the collection job
        upload_job_id                uniqueidentifier NULL,          -- id of the upload job
        collection_mode                smallint default 0 NOT NULL,    -- 0 - cached, 1 - non-cached
        logging_level                smallint default 2 NOT NULL,    -- 0 - errors only, 1 - errors & warnings, 2 - detailed
        description                    nvarchar(4000) NULL,            -- description of the set
        description_id                int NULL,                        -- sysmessage id of the description of the set (for localizing system collection set)
        days_until_expiration       smallint NOT NULL,              -- how long to keep the data from this collection set
        dump_on_any_error           bit default 0 NOT NULL,         -- configure SQL dumper to dump on any SSIS errors
        dump_on_codes               nvarchar(max) NULL,             -- configure SQL dumper to dump when we hit one of the specified SSIS errors 
        CONSTRAINT [PK_syscollector_collection_sets_internal] PRIMARY KEY CLUSTERED (collection_set_id ASC),
        CONSTRAINT [UQ_syscollector_collection_sets_internal_name] UNIQUE (name)
        )
    ALTER TABLE syscollector_collection_sets_internal
        ADD CONSTRAINT [FK_syscollector_collection_sets_internal_sysproxies] FOREIGN KEY(proxy_id)
        REFERENCES sysproxies (proxy_id)
    ALTER TABLE syscollector_collection_sets_internal
        ADD CONSTRAINT [FK_syscollector_collection_sets_collection_sysjobs] FOREIGN KEY(collection_job_id)
        REFERENCES sysjobs (job_id)
    ALTER TABLE syscollector_collection_sets_internal
        ADD CONSTRAINT [FK_syscollector_collection_sets_upload_sysjobs] FOREIGN KEY(upload_job_id)
        REFERENCES sysjobs(job_id)
END ELSE BEGIN
    IF (NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name = N'name_id' AND id = OBJECT_ID(N'[dbo].[syscollector_collection_sets_internal]')))
    BEGIN
        PRINT 'Altering table [dbo].[syscollector_collection_sets_internal]...'
        ALTER TABLE [dbo].[syscollector_collection_sets_internal] ADD name_id int NULL
    END
    
    IF (NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name = N'description_id' AND id = OBJECT_ID(N'[dbo].[syscollector_collection_sets_internal]')))
    BEGIN
        PRINT 'Altering table [dbo].[syscollector_collection_sets_internal]...'
        ALTER TABLE [dbo].[syscollector_collection_sets_internal] ADD description_id int NULL
    END
        IF (NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name = N'dump_on_any_error' AND id = OBJECT_ID(N'[dbo].[syscollector_collection_sets_internal]')))
    BEGIN
        PRINT 'Altering table [dbo].[syscollector_collection_sets_internal]...'
        ALTER TABLE [dbo].[syscollector_collection_sets_internal] ADD dump_on_any_error bit default 0
    END
    
    IF (NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name = N'dump_on_codes' AND id = OBJECT_ID(N'[dbo].[syscollector_collection_sets_internal]')))
    BEGIN
        PRINT 'Altering table [dbo].[syscollector_collection_sets_internal]...'
        ALTER TABLE [dbo].[syscollector_collection_sets_internal] ADD dump_on_codes nvarchar(max) NULL
    END    
END
GO

IF (NOT OBJECT_ID(N'[dbo].[syscollector_collection_sets]', 'V') IS NULL)
BEGIN
    PRINT 'Dropping view [dbo].[syscollector_collection_sets]...'
    DROP VIEW [dbo].[syscollector_collection_sets]
END
GO

PRINT 'Creating view [dbo].[syscollector_collection_sets]...'
GO
CREATE VIEW [dbo].[syscollector_collection_sets]
AS
    SELECT 
        s.collection_set_id,
        s.collection_set_uid,
        CASE 
            WHEN s.name_id IS NULL THEN s.name 
            ELSE FORMATMESSAGE(s.name_id)
        END AS name,        
        s.target,
        s.is_system,
        s.is_running,
        s.collection_mode,
        s.proxy_id,
        s.schedule_uid,
        s.collection_job_id,
        s.upload_job_id,
        s.logging_level,
        s.days_until_expiration,
        CASE 
            WHEN s.description_id IS NULL THEN s.description
            ELSE FORMATMESSAGE(s.description_id)
        END AS description,
        s.dump_on_any_error,
        s.dump_on_codes
    FROM 
        [dbo].[syscollector_collection_sets_internal] AS s
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_verify_collection_set]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_verify_collection_set]...'
    DROP PROCEDURE [dbo].[sp_syscollector_verify_collection_set]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_verify_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_verify_collection_set]
    @collection_set_id        int = NULL OUTPUT,
    @name                    sysname = NULL OUTPUT
AS
BEGIN
    IF (@name IS NOT NULL)
    BEGIN
        -- Remove any leading/trailing spaces from parameters
        SET @name =            NULLIF(LTRIM(RTRIM(@name)), N'')
    END

    IF (@collection_set_id IS NULL AND @name IS NULL)
    BEGIN
        RAISERROR(14624, -1, -1, '@collection_set_id, @name')
        RETURN(1)
    END

    IF (@collection_set_id IS NOT NULL AND @name IS NOT NULL)
    BEGIN
        IF (NOT EXISTS(SELECT *
                        FROM dbo.syscollector_collection_sets
                        WHERE collection_set_id = @collection_set_id
                        AND name = @name))
        BEGIN
            DECLARE @errMsg NVARCHAR(196)
            SELECT @errMsg = CONVERT(NVARCHAR(36), @collection_set_id) + ', ' + @name
            RAISERROR(14262, -1, -1, '@collection_set_id, @name', @errMsg)
            RETURN(1)
        END
    END
    -- Check id
    ELSE IF (@collection_set_id IS NOT NULL)
    BEGIN
        SELECT @name = name
        FROM dbo.syscollector_collection_sets
        WHERE (collection_set_id = @collection_set_id)
    
        -- the view would take care of all the permissions issues.
        IF (@name IS NULL) 
        BEGIN
            DECLARE @collection_set_id_as_char VARCHAR(36)
            SELECT @collection_set_id_as_char = CONVERT(VARCHAR(36), @collection_set_id)
            RAISERROR(14262, -1, -1, '@collection_set_id', @collection_set_id_as_char)
            RETURN(1) -- Failure
        END
    END
    -- Check name
    ELSE IF (@name IS NOT NULL)
    BEGIN
        -- get the corresponding collection_set_id (if the collection set exists)
        SELECT @collection_set_id = collection_set_id
        FROM dbo.syscollector_collection_sets
        WHERE (name = @name)

        -- the view would take care of all the permissions issues.
        IF (@collection_set_id IS NULL)
        BEGIN
            RAISERROR(14262, -1, -1, '@name', @name)
            RETURN(1) -- Failure
        END
    END
    RETURN (0)
END
GO

-- Create one schedule that starts when SQL Agent starts so that all continuous
-- collection jobs can attach to it, the schedule has to be accessible to the internal dc user that owns agent objects
-- EXECUTE AS USER = 'MS_DataCollectorInternalUser';

IF (NOT EXISTS (SELECT * FROM sysschedules_localserver_view WHERE name = N'RunAsSQLAgentServiceStartSchedule'))
BEGIN
    EXEC dbo.sp_add_schedule
        @schedule_name = N'RunAsSQLAgentServiceStartSchedule',
        @freq_type = 0x40,            -- FREQTYPE_AUTOSTART
        @freq_interval = 1
END

-- REVERT;
GO


IF (NOT OBJECT_ID('[dbo].[sp_syscollector_create_jobs]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_create_jobs]...'
    DROP PROCEDURE [dbo].[sp_syscollector_create_jobs]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_create_jobs]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_create_jobs]
    @collection_set_id        int,
    @collection_set_uid        uniqueidentifier,
    @collection_set_name    sysname,
    @proxy_id                int = NULL,
    @schedule_id            int = NULL,
    @collection_mode        smallint,
    @collection_job_id        uniqueidentifier OUTPUT,
    @upload_job_id            uniqueidentifier OUTPUT
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_syscollector_create_jobs
    ELSE
        BEGIN TRANSACTION

    BEGIN TRY

    -- job step names and commands shared between collection modes
    DECLARE @collection_set_id_as_char nvarchar(36)

    DECLARE @collection_step_command nvarchar(512)
    DECLARE @upload_step_command nvarchar(512)
    DECLARE @autostop_step_command nvarchar(512)
    DECLARE @purge_step_command nvarchar(1024)

    DECLARE @collection_step_name sysname
    DECLARE @upload_step_name sysname
    DECLARE @autostop_step_name sysname
    DECLARE @purge_step_name sysname

    DECLARE @job_name sysname
    DECLARE @job_id uniqueidentifier        
    DECLARE @description nvarchar(512)

    IF(@collection_set_id IS NOT NULL)
    BEGIN
        SET @collection_set_id_as_char = CONVERT(NVARCHAR(36), @collection_set_id)
        SET @collection_step_command = 
            N'dcexec -c -s ' + @collection_set_id_as_char + N' -i "$(ESCAPE_DQUOTE(MACH))\$(ESCAPE_DQUOTE(INST))"' + 
            N' -m ' + CONVERT(NVARCHAR(36), @collection_mode);
        SET @upload_step_command = 
            N'dcexec -u -s ' + @collection_set_id_as_char + N' -i "$(ESCAPE_DQUOTE(MACH))\$(ESCAPE_DQUOTE(INST))"';
        SET @autostop_step_command =
            N'exec dbo.sp_syscollector_stop_collection_set @collection_set_id=' + @collection_set_id_as_char 
            + N', @stop_collection_job = 0';  -- do not stop the collection job, otherwise you will abort yourself!
        SET @purge_step_command = 
            N'
            EXEC [dbo].[sp_syscollector_purge_collection_logs]
            '
    END

    -- verify that the proxy_id exists
    IF (@proxy_id IS NOT NULL)
    BEGIN
        DECLARE @proxy_name sysname
        DECLARE @retVal int
        -- this will throw an error of proxy_id does not exist
        EXEC @retVal = msdb.dbo.sp_verify_proxy_identifiers '@proxy_name', '@proxy_id', @proxy_name OUTPUT, @proxy_id OUTPUT
        IF (@retVal <> 0)
            RETURN (0)
    END

    -- add jobs, job steps and attach schedule separately for different modes
    IF (@collection_mode = 1)    -- non-cached mode
    BEGIN
        -- create 1 job and 2 steps, first for collection & upload, second for log purging
        SET @job_name = N'collection_set_' + @collection_set_id_as_char + '_noncached_collect_and_upload'
        SET @collection_step_name = @job_name + '_collect'
        SET @upload_step_name = @job_name + '_upload'
        SET @purge_step_name = @job_name + '_purge_logs'
        SET @description = N'Data Collector job for collection set ' + QUOTENAME(@collection_set_name)

        -- add agent job and job server
        EXEC dbo.sp_add_job 
            @job_name        = @job_name,
            @category_id    = 8, -- N'Data Collector'
            @enabled        = 0,
            @description    = @description,
            @job_id            = @job_id OUTPUT
        
        EXEC dbo.sp_add_jobserver
            @job_id            = @job_id,
            @server_name    = N'(local)'

        -- add both collect and upload job steps to the same job
        EXEC dbo.sp_add_jobstep
            @job_id                = @job_id,
            @step_name            = @collection_step_name,
            @subsystem            = 'CMDEXEC',
            @command            = @collection_step_command,
            @on_success_action    =  3,        -- go to the next job step (purge the log)
            @on_fail_action        =  2,        -- quit with failure
            @proxy_id            = @proxy_id,
            @flags              = 16        -- Write log to table (append to existing history)

        EXEC dbo.sp_add_jobstep
            @job_id                = @job_id,
            @step_name            = @purge_step_name,
            @subsystem            = 'TSQL',
            @database_name        = 'msdb',
            @command            = @purge_step_command,
            @on_success_action    =  3,        -- go to the next job step (upload)
            @on_fail_action        =  3,        -- go to the next job step (upload)
            @proxy_id            = NULL,
            @flags                = 16        -- write log to table (append to existing history)

        EXEC dbo.sp_add_jobstep
            @job_id                = @job_id,
            @step_name            = @upload_step_name,
            @subsystem            = 'CMDEXEC',
            @command            = @upload_step_command,
            @on_success_action    =  1,        -- quit with success
            @on_fail_action        =  2,        -- quit with failure
            @proxy_id            = @proxy_id,
            @flags              = 16        -- Write log to table (append to existing history)

        IF @schedule_id IS NOT NULL
        BEGIN
            -- attach the schedule
            EXEC dbo.sp_attach_schedule
                @job_id            = @job_id,
                @schedule_id    = @schedule_id
        END

        SET @upload_job_id = @job_id
        SET @collection_job_id = @job_id
    END

    IF (@collection_mode = 0) -- cached mode
    BEGIN
        -- create 2 jobs for collect and upload
        -- add to collect job an extra step that autostops collection called in case collect job fails
        DECLARE @upload_job_name        sysname
        DECLARE @collection_job_name    sysname
        SET @upload_job_name = N'collection_set_' + @collection_set_id_as_char + '_upload'
        SET @collection_job_name = N'collection_set_' + @collection_set_id_as_char + '_collection'

        SET @collection_step_name = @collection_job_name + '_collect'
        SET @autostop_step_name = @collection_job_name + '_autostop'
        SET @upload_step_name = @upload_job_name + '_upload'
        SET @purge_step_name = @upload_job_name + '_purge_logs'

        -- modify the collection step to pass in the stop event name passed in by agent
        SET @collection_step_command = @collection_step_command + N' -e $' + N'(ESCAPE_NONE(' + N'STOPEVENT))'

        -- add agent job and job server
        EXEC dbo.sp_add_job 
            @job_name        = @upload_job_name,
            @category_id    = 8, -- N'Data Collector'
            @enabled        = 0,
            @job_id            = @upload_job_id OUTPUT
        
        EXEC dbo.sp_add_jobserver
            @job_id            = @upload_job_id,
            @server_name    = N'(local)'

        EXEC dbo.sp_add_job 
            @job_name        = @collection_job_name,
            @category_id    = 8, -- N'Data Collector'
            @enabled        = 0,
            @job_id            = @collection_job_id OUTPUT

        EXEC dbo.sp_add_jobserver
            @job_id            = @collection_job_id,
            @server_name    = N'(local)'

        -- add upload job step to upload job and collection job
        -- step to collection job separately
        EXEC dbo.sp_add_jobstep
            @job_id                = @upload_job_id,
            @step_name            = @purge_step_name,
            @subsystem            = 'TSQL',
            @database_name        = 'msdb',
            @command            = @purge_step_command,
            @on_success_action    =  3,        -- go to next job step (upload)
            @on_fail_action        =  3,        -- go to next job step (upload)
            @proxy_id            = NULL,
            @flags                = 16        -- write log to table (append to existing history)

        EXEC dbo.sp_add_jobstep
            @job_id                = @upload_job_id,
            @step_name            = @upload_step_name,
            @subsystem            = 'CMDEXEC',
            @command            = @upload_step_command,
            @on_success_action    =  1,        -- quit with success
            @on_fail_action        =  2,        -- quit with failure
            @proxy_id            = @proxy_id

        EXEC dbo.sp_add_jobstep
            @job_id             = @collection_job_id,
            @step_name          = @collection_step_name,
            @subsystem          = 'CMDEXEC',
            @command            = @collection_step_command,
            @on_success_action  =  1,        -- quit with success
            @on_fail_action     =  3,        -- go to next job step (auto-stop)
                                             -- The retry logic will be applied to new collection sets only
            @retry_attempts     =  3,        -- in case a job step failed retry for 3 times
            @retry_interval     =  5,        -- 5 minutes wait between retry attempts
            @proxy_id           = @proxy_id,
            @flags              = 80 -- 16 (write log to table (append to existing history) 
                                     -- + 64 (create a stop event and pass it to the command line)

        EXEC dbo.sp_add_jobstep
            @job_id                = @collection_job_id,
            @step_name            = @autostop_step_name,
            @subsystem            = 'TSQL',
            @database_name        = 'msdb',
            @command            = @autostop_step_command,
            @on_success_action    =  2,        -- quit with failure
            @on_fail_action        =  2,        -- quit with failure
            @proxy_id            = NULL,
            @flags                = 16        -- write log to table (append to existing history)

        -- attach the input schedule to the upload job
        EXEC dbo.sp_attach_schedule
            @job_id            = @upload_job_id,
            @schedule_id    = @schedule_id

        -- attach the RunAsSQLAgentServiceStartSchedule to the collection job
        EXEC dbo.sp_attach_schedule
            @job_id            = @collection_job_id,
            @schedule_name    = N'RunAsSQLAgentServiceStartSchedule'
    END

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)

    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_syscollector_create_jobs

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_create_collection_set]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_create_collection_set]...'
    DROP PROCEDURE [dbo].[sp_syscollector_create_collection_set]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_create_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_create_collection_set]
    @name                        sysname,
    @target                        nvarchar(128) = NULL,
    @collection_mode            smallint = 0,    -- 0: cached, 1: non-cached
    @days_until_expiration      smallint = 730, -- two years
    @proxy_id                   int = NULL,     -- mutual exclusive; must specify either proxy_id or proxy_name to identify the proxy
    @proxy_name                    sysname = NULL,
    @schedule_uid               uniqueidentifier = NULL, 
    @schedule_name              sysname = NULL, -- mutual exclusive; must specify either schedule_uid or schedule_name to identify the schedule
    @logging_level                smallint = 1,
    @description                nvarchar(4000) = NULL,
    @collection_set_id            int OUTPUT,
    @collection_set_uid            uniqueidentifier = NULL OUTPUT
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_create_collection_set
    ELSE
        BEGIN TRANSACTION

    BEGIN TRY

    -- Security check (role membership)
    EXECUTE AS CALLER;
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN (1)
    END
    REVERT;

    -- Remove any leading/trailing spaces from parameters
    SET @name                    = NULLIF(LTRIM(RTRIM(@name)), N'')
    SET @proxy_name                = NULLIF(LTRIM(RTRIM(@proxy_name)), N'')
    SET @schedule_name            = NULLIF(LTRIM(RTRIM(@schedule_name)), N'')
    SET @target                    = NULLIF(LTRIM(RTRIM(@target)), N'')
    SET @description            = LTRIM(RTRIM(@description))

    IF (@name IS NULL)
    BEGIN
        RAISERROR(21263, -1, -1, '@name')
        RETURN (1)
    END

    -- can't have both name and uid for the schedule
    IF (@schedule_uid IS NOT NULL) AND (@schedule_name IS NOT NULL)
    BEGIN
        RAISERROR(14373, -1, -1, '@schedule_uid', '@schedule_name')
        RETURN (1)
    END

    -- Execute the check for the schedule as caller to ensure only schedules owned by caller can be attached
    EXECUTE AS CALLER;

    DECLARE @schedule_id int
    IF (@schedule_uid IS NOT NULL)
    BEGIN
        SElECT @schedule_id = schedule_id FROM sysschedules_localserver_view WHERE @schedule_uid = schedule_uid
    
        IF (@schedule_id IS NULL)
        BEGIN
            DECLARE @schedule_uid_as_char VARCHAR(36)
            SELECT @schedule_uid_as_char = CONVERT(VARCHAR(36), @schedule_uid)
            REVERT;
            RAISERROR(14262, -1, -1, N'@schedule_uid', @schedule_uid_as_char)
            RETURN (1)
        END
    END
    ELSE IF (@schedule_name IS NOT NULL)
    BEGIN
        SELECT @schedule_id = schedule_id, @schedule_uid = schedule_uid FROM sysschedules_localserver_view WHERE name = @schedule_name 
    
        IF (@schedule_id IS NULL)
        BEGIN
            REVERT;
            RAISERROR(14262, -1, -1, N'@schedule_name', @schedule_name)
            RETURN (1)
        END
    END

    REVERT;

    -- if collection_mode is cached, make sure schedule_id is not null
    IF    (@collection_mode = 0 AND @schedule_id IS NULL)
    BEGIN
        RAISERROR(14683, -1, -1)    
        RETURN (1)
    END    

    IF (@proxy_id IS NOT NULL) OR (@proxy_name IS NOT NULL) 
    BEGIN
        -- check if the proxy exists
        EXEC sp_verify_proxy_identifiers '@proxy_name',
                                         '@proxy_id',
                                         @proxy_name OUTPUT,
                                         @proxy_id   OUTPUT

        -- check if proxy_id is granted to dc_admin
        IF (@proxy_id NOT IN (SELECT proxy_id 
                              FROM sysproxylogin 
                              WHERE sid = USER_SID(USER_ID('dc_admin'))
                              )
            )
        BEGIN
            RAISERROR(14719, -1, -1, N'dc_admin')
            RETURN (1)
        END
    END

    IF (@collection_mode < 0 OR @collection_mode > 1)
    BEGIN
        RAISERROR(14266, -1, -1, '@collection_mode', '0, 1')
        RETURN (1)
    END

    IF (@logging_level < 0 OR @logging_level > 2)
    BEGIN
        RAISERROR(14266, -1, -1, '@logging_level', '0, 1, or 2')
        RETURN (1)
    END

    IF (@collection_set_uid IS NULL)
    BEGIN
        SET @collection_set_uid = NEWID()
    END

    IF (@days_until_expiration < 0)
    BEGIN
        RAISERROR(14266, -1, -1, '@days_until_expiration', '>= 0')
        RETURN (1)
    END

    INSERT INTO [dbo].[syscollector_collection_sets_internal]
    (
        collection_set_uid,
        schedule_uid,
        name,
        target,
        is_running,
        proxy_id,
        is_system,
        upload_job_id,
        collection_job_id,
        collection_mode,
        logging_level,
        days_until_expiration,
        description
    )
    VALUES
    (
        @collection_set_uid,
        @schedule_uid,
        @name,
        @target,
        0,
        @proxy_id,
        0,
        NULL,
        NULL,
        @collection_mode,
        @logging_level,
        @days_until_expiration,
        @description
    )

    SET @collection_set_id = SCOPE_IDENTITY()

    IF (@collection_set_id IS NULL)
    BEGIN
        DECLARE @collection_set_id_as_char VARCHAR(36)
        SELECT @collection_set_id_as_char = CONVERT(VARCHAR(36), @collection_set_id)
        RAISERROR(14262, -1, -1, '@collection_set_id', @collection_set_id_as_char)
        RETURN (1)
    END

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)

    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_create_collection_set

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)        
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_update_job_proxy]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_update_job_proxy]...'
    DROP PROCEDURE [dbo].[sp_syscollector_update_job_proxy]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_update_job_proxy]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_update_job_proxy]
    @job_id                    uniqueidentifier,
    @proxy_id                int = NULL,
    @proxy_name                sysname = NULL
AS
BEGIN
    -- update the proxy id for the all job steps
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_syscollector_update_proxy
    ELSE
        BEGIN TRANSACTION
    
    BEGIN TRY
        DECLARE @step_id         INT
        DECLARE cursor_job_steps CURSOR FOR
            SELECT step_id FROM dbo.sysjobsteps WHERE job_id = @job_id AND subsystem = N'CMDEXEC'

        OPEN cursor_job_steps
        FETCH NEXT FROM cursor_job_steps INTO @step_id

        WHILE @@FETCH_STATUS = 0
        BEGIN
            EXEC dbo.sp_update_jobstep
                @job_id = @job_id,
                @step_id = @step_id,
                @proxy_id = @proxy_id,
                @proxy_name = @proxy_name

            FETCH NEXT FROM cursor_job_steps INTO @step_id
        END

        CLOSE      cursor_job_steps
        DEALLOCATE cursor_job_steps

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_syscollector_update_proxy

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)        
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_update_collection_set_internal]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_update_collection_set_internal]...'
    DROP PROCEDURE [dbo].[sp_syscollector_update_collection_set_internal]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_update_collection_set_internal]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_update_collection_set_internal]
    @collection_set_id          int,
    @collection_set_uid         uniqueidentifier,
    @name                       sysname,
    @new_name                   sysname,
    @target                     nvarchar(128),
    @collection_mode            smallint,         
    @days_until_expiration      smallint,
    @proxy_id                   int,              
    @proxy_name                 sysname,          
    @schedule_uid               uniqueidentifier, 
    @schedule_name              sysname,          
    @logging_level              smallint,
    @description                nvarchar(4000),   
    @schedule_id                int,
    @old_collection_mode        smallint,
    @old_proxy_id               int,
    @old_collection_job_id      uniqueidentifier,
    @old_upload_job_id          uniqueidentifier    
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_update_collection_set
    ELSE
        BEGIN TRANSACTION
    BEGIN TRY
        DECLARE @old_upload_schedule_id    int
        DECLARE @old_upload_schedule_uid uniqueidentifier

        SELECT  @old_upload_schedule_id = sv.schedule_id,
                @old_upload_schedule_uid = cs.schedule_uid
        FROM dbo.syscollector_collection_sets cs 
        JOIN sysschedules_localserver_view sv ON (cs.schedule_uid = sv.schedule_uid)
        WHERE collection_set_id = @collection_set_id

        -- update job names, schedule, and collection mode in a transaction to maintain a consistent state in case of failures
        IF (@collection_mode IS NOT NULL AND @collection_mode != @old_collection_mode)
        BEGIN
            IF (@schedule_id IS NULL)
            BEGIN
                -- if no schedules is supplied as a parameter to this update SP, 
                -- we can use the one that is already in the collection set table
                SET @schedule_uid = @old_upload_schedule_uid
                
                SELECT @schedule_id = schedule_id 
                FROM sysschedules_localserver_view 
                WHERE @schedule_uid = schedule_uid
            END

            IF (@schedule_name IS NOT NULL AND @schedule_name = N'')
            BEGIN
                SET @schedule_id = NULL 
            END

            -- make sure there exists a schedule we can use
            IF (@old_collection_mode = 1 AND @schedule_id IS NULL) -- a switch from non-cached to cached mode require a schedule
            BEGIN
                -- no schedules specified in input or collection set table, raise error
                RAISERROR(14683, -1, -1)
                RETURN (1)
            END

            -- Only update the jobs if we have jobs already created. Otherwise the right
            -- jobs will be created when the collection set starts for the first time.
            IF (@old_collection_job_id IS NOT NULL AND @old_upload_job_id IS NOT NULL)
            BEGIN
                -- create new jobs 
                DECLARE @collection_job_id        uniqueidentifier 
                DECLARE @upload_job_id            uniqueidentifier 

                DECLARE @collection_set_name sysname;
                SET @collection_set_name = ISNULL(@new_name, @name);
                EXEC [dbo].[sp_syscollector_create_jobs] 
                    @collection_set_id        = @collection_set_id,
                    @collection_set_uid     = @collection_set_uid,
                    @collection_set_name    = @collection_set_name,
                    @proxy_id                = @proxy_id,
                    @schedule_id            = @schedule_id,
                    @collection_mode        = @collection_mode,
                    @collection_job_id        = @collection_job_id OUTPUT,
                    @upload_job_id            = @upload_job_id OUTPUT

                UPDATE [dbo].[syscollector_collection_sets_internal]
                SET
                    upload_job_id        = @upload_job_id,
                    collection_job_id    = @collection_job_id
                WHERE @collection_set_id = collection_set_id
                
                -- drop old upload and collection jobs
                EXEC dbo.sp_syscollector_delete_jobs 
                    @collection_job_id        = @old_collection_job_id,
                    @upload_job_id            = @old_upload_job_id,
                    @schedule_id            = @old_upload_schedule_id,
                    @collection_mode        = @old_collection_mode
            END
        END
        ELSE -- collection mode unchanged, we do not have to recreate the jobs
        BEGIN
            -- we need to update the proxy id for all job steps
            IF (@old_proxy_id <> @proxy_id) OR (@old_proxy_id IS NULL AND @proxy_id IS NOT NULL)
            BEGIN
                IF (@old_collection_job_id IS NOT NULL)
                BEGIN
                    EXEC dbo.sp_syscollector_update_job_proxy
                        @job_id    = @old_collection_job_id, 
                        @proxy_id  = @proxy_id
                END

                IF (@old_upload_job_id IS NOT NULL)
                BEGIN
                    EXEC dbo.sp_syscollector_update_job_proxy
                        @job_id    = @old_upload_job_id, 
                        @proxy_id  = @proxy_id
                END
            END
            IF (@proxy_name = N'' AND @old_proxy_id IS NOT NULL) 
            BEGIN
                IF (@old_collection_job_id IS NOT NULL)
                BEGIN
                    EXEC dbo.sp_syscollector_update_job_proxy
                        @job_id    = @old_collection_job_id, 
                        @proxy_name = @proxy_name
                END

                IF (@old_upload_job_id IS NOT NULL)
                BEGIN
                    EXEC dbo.sp_syscollector_update_job_proxy
                        @job_id    = @old_upload_job_id, 
                        @proxy_name = @proxy_name
                END
            END

            -- need to update the schedule
            IF (@old_upload_schedule_id <> @schedule_id) OR (@old_upload_schedule_id IS NULL AND @schedule_id IS NOT NULL)
            BEGIN
                -- detach the old schedule 
                IF (@old_upload_job_id IS NOT NULL) AND (@old_upload_schedule_id IS NOT NULL)
                BEGIN
                    EXEC dbo.sp_detach_schedule 
                        @job_id            = @old_upload_job_id,
                        @schedule_id    = @old_upload_schedule_id,
                        @delete_unused_schedule = 0
                END

                -- attach the new schedule
                IF (@old_upload_job_id IS NOT NULL)
                BEGIN
                    EXEC dbo.sp_attach_schedule
                        @job_id            = @old_upload_job_id,
                        @schedule_id    = @schedule_id
                END
            END

            -- special case - remove the existing schedule
            IF (@schedule_name = N'') AND (@old_upload_schedule_id IS NOT NULL)
            BEGIN
                EXEC dbo.sp_detach_schedule 
                    @job_id            = @old_upload_job_id,
                    @schedule_id    = @old_upload_schedule_id,
                    @delete_unused_schedule = 0
            END
        END

        -- after the all operations succeed, update the sollection_sets table
        DECLARE @new_proxy_id int
        SET @new_proxy_id = @proxy_id
        IF (@proxy_name    = N'')    SET @new_proxy_id = NULL

        UPDATE [dbo].[syscollector_collection_sets_internal]
        SET
            name                    = ISNULL(@new_name, name),
            target                    = ISNULL(@target, target),
            proxy_id                = @new_proxy_id,
            collection_mode            = ISNULL(@collection_mode, collection_mode),
            logging_level            = ISNULL(@logging_level, logging_level),
            days_until_expiration   = ISNULL(@days_until_expiration, days_until_expiration)
        WHERE @collection_set_id = collection_set_id

        IF (@schedule_uid IS NOT NULL OR @schedule_name IS NOT NULL)
        BEGIN
            IF (@schedule_name = N'')    SET @schedule_uid = NULL

            UPDATE [dbo].[syscollector_collection_sets_internal]
            SET schedule_uid = @schedule_uid
            WHERE @collection_set_id = collection_set_id
        END

        IF (@description IS NOT NULL)
        BEGIN
            IF (@description = N'')      SET @description = NULL

            UPDATE [dbo].[syscollector_collection_sets_internal]
            SET description = @description
            WHERE @collection_set_id = collection_set_id
        END

        IF (@TranCounter = 0)
        COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_update_collection_set

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_update_collection_set]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_update_collection_set]...'
    DROP PROCEDURE [dbo].[sp_syscollector_update_collection_set]
END
GO

-- Updates a collection set. These are the steps involved
-- 1- Security checks
-- 2- Stop the collection set if currently running (sp_syscollector_stop_collection_set)
-- 3- Perform the actual update transactionally (sp_syscollector_update_collection_set_internal)
-- 4- Restart the collection set if it was running (sp_syscollector_start_collection_set
PRINT 'Creating procedure [dbo].[sp_syscollector_update_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_update_collection_set]
    @collection_set_id        int = NULL,
    @name                    sysname = NULL,
    @new_name                sysname = NULL,
    @target                    nvarchar(128) = NULL,
    @collection_mode        smallint = NULL,         -- 0: cached, 1: non-cached
    @days_until_expiration  smallint = NULL,
    @proxy_id               int = NULL,              -- mutual exclusive; must specify either proxy_id or proxy_name to identify the proxy
    @proxy_name             sysname = NULL,          -- @proxy_name = N'' is a special case to allow change of an existing proxy with NULL
    @schedule_uid           uniqueidentifier = NULL, -- mutual exclusive; must specify either schedule_uid or schedule_name to identify the schedule
    @schedule_name          sysname = NULL,          -- @schedule_name = N'' is a special case to allow change of an existing schedule with NULL
    @logging_level            smallint = NULL,
    @description            nvarchar(4000) = NULL   -- @description = N'' is a special case to allow change of an existing description with NULL
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    -- Security checks will be performed against caller's security context
    EXECUTE AS CALLER;

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN (1)
    END

    -- Security checks (restrict functionality for non-dc_admin-s)
    IF (((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1)) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        AND (@new_name IS NOT NULL))
    BEGIN
        REVERT;
        RAISERROR(14676, -1, -1, '@new_name', 'dc_admin')
        RETURN (1)
    END

    IF (((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1)) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        AND (@target IS NOT NULL))
    BEGIN
        REVERT;
        RAISERROR(14676, -1, -1, '@target', 'dc_admin')
        RETURN (1)
    END

    IF (((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1)) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        AND (@proxy_id IS NOT NULL))
    BEGIN
        REVERT;
        RAISERROR(14676, -1, -1, '@proxy_id', 'dc_admin')
        RETURN (1)
    END

    IF (((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1)) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        AND (@collection_mode IS NOT NULL))
    BEGIN
        REVERT;
        RAISERROR(14676, -1, -1, '@collection_mode', 'dc_admin')
        RETURN (1)
    END

    IF (((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1)) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        AND (@description IS NOT NULL))
    BEGIN
        REVERT;
        RAISERROR(14676, -1, -1, '@description', 'dc_admin')
        RETURN (1)
    END

    IF (((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1)) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        AND (@days_until_expiration IS NOT NULL))
    BEGIN
        REVERT;
        RAISERROR(14676, -1, -1, '@days_until_expiration', 'dc_admin')
        RETURN (1) -- Failure
    END

    -- Security checks done, reverting now to internal data collector user security context
    REVERT;

    -- check for inconsistencies/errors in the parameters
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    IF (@collection_mode IS NOT NULL AND (@collection_mode < 0 OR @collection_mode > 1))
    BEGIN
        RAISERROR(14266, -1, -1, '@collection_mode', '0, 1')
        RETURN (1)
    END

    IF (@logging_level IS NOT NULL AND (@logging_level < 0 OR @logging_level > 2))
    BEGIN
        RAISERROR(14266, -1, -1, '@logging_level', '0, 1, or 2')
        RETURN(1)
    END

    IF (LEN(@new_name) = 0)
    BEGIN
      RAISERROR(21263, -1, -1, '@new_name')
      RETURN(1) -- Failure
    END    

    -- Remove any leading/trailing spaces from parameters
    SET @target                    = NULLIF(LTRIM(RTRIM(@target)), N'')
    SET @new_name                = NULLIF(LTRIM(RTRIM(@new_name)), N'')
    SET @description            = LTRIM(RTRIM(@description))
    
    DECLARE @is_system                    bit
    DECLARE @is_running                    bit
    DECLARE @collection_set_uid            uniqueidentifier
    DECLARE @old_collection_mode        smallint
    DECLARE @old_upload_job_id            uniqueidentifier
    DECLARE @old_collection_job_id        uniqueidentifier
    DECLARE @old_proxy_id                int

    SELECT    @is_running = is_running,
            @is_system = is_system,
            @collection_set_uid = collection_set_uid,
            @old_collection_mode = collection_mode,
            @old_collection_job_id = collection_job_id, 
            @old_upload_job_id = upload_job_id,
            @old_proxy_id = proxy_id
    FROM dbo.syscollector_collection_sets
    WHERE collection_set_id = @collection_set_id

    IF (@is_system = 1 AND (
            @new_name IS NOT NULL OR 
            @description IS NOT NULL))
    BEGIN
        -- cannot update, delete, or add new collection items to a system collection set
        RAISERROR(14696, -1, -1);
        RETURN (1)
    END
    
    IF (@proxy_id IS NOT NULL) OR (@proxy_name IS NOT NULL AND @proxy_name <> N'')
    BEGIN
        -- verify the proxy exists
        EXEC sp_verify_proxy_identifiers '@proxy_name',
                                         '@proxy_id',
                                         @proxy_name OUTPUT,
                                         @proxy_id   OUTPUT

        -- check if proxy_id is granted to dc_admin
        IF (@proxy_id NOT IN (SELECT proxy_id 
                              FROM sysproxylogin 
                              WHERE sid = USER_SID(USER_ID('dc_admin'))
                              )
            )
        BEGIN
            RAISERROR(14719, -1, -1, N'dc_admin')
            RETURN (1)
        END
    END
    ELSE -- if no proxy_id provided, get the existing proxy_id, might need it later to create new jobs
    BEGIN
        SET @proxy_id = @old_proxy_id
    END

    -- can't have both uid and name passed for the schedule
    IF (@schedule_uid IS NOT NULL) AND (@schedule_name IS NOT NULL AND @schedule_name <> N'')
    BEGIN
        RAISERROR(14373, -1, -1, '@schedule_uid', '@schedule_name')
        RETURN (1)
    END

    -- check if it attempts to remove a schedule when the collection mode is cached
    IF    (@schedule_name = N'' AND @collection_mode = 0)    OR 
        (@collection_mode IS NULL AND @old_collection_mode = 0 AND @schedule_name = N'')
    BEGIN
        RAISERROR(14683, -1, -1)    
        RETURN (1)
    END    

    -- Execute the check for the schedule as caller to ensure only schedules owned by caller can be attached
    EXECUTE AS CALLER;

    DECLARE @schedule_id int
    SET @schedule_id = NULL
    IF (@schedule_uid IS NOT NULL)
    BEGIN
        SElECT @schedule_id = schedule_id FROM sysschedules_localserver_view WHERE @schedule_uid = schedule_uid
    
        IF (@schedule_id IS NULL)
        BEGIN
            DECLARE @schedule_uid_as_char VARCHAR(36)
            SELECT @schedule_uid_as_char = CONVERT(VARCHAR(36), @schedule_uid)
            REVERT;
            RAISERROR(14262, -1, -1, N'@schedule_uid', @schedule_uid_as_char)
            RETURN (1)
        END
    END
    ELSE IF (@schedule_name IS NOT NULL AND @schedule_name <> N'') -- @schedule_name is not null
    BEGIN
        SELECT @schedule_id = schedule_id, @schedule_uid = schedule_uid FROM sysschedules_localserver_view WHERE name = @schedule_name 
    
        IF (@schedule_id IS NULL)
        BEGIN
            REVERT;
            RAISERROR(14262, -1, -1, N'@schedule_name', @schedule_name)
            RETURN (1)
        END
    END

    REVERT;
    
    -- Stop the collection set if it is currently running
    IF (@is_running = 1 AND (
            @new_name IS NOT NULL OR 
            @target IS NOT NULL OR 
            @proxy_id IS NOT NULL OR 
            @logging_level IS NOT NULL OR 
            @collection_mode IS NOT NULL))
    BEGIN
        EXEC @retVal = sp_syscollector_stop_collection_set @collection_set_id = @collection_set_id
        IF (@retVal <> 0)
            RETURN (1)
    END

    -- Passed all necessary checks, go ahead with the update
    EXEC @retVal = sp_syscollector_update_collection_set_internal
        @collection_set_id = @collection_set_id,
        @collection_set_uid = @collection_set_uid,
        @name = @name,
        @new_name = @new_name,
        @target = @target,
        @collection_mode = @collection_mode,
        @days_until_expiration = @days_until_expiration,
        @proxy_id = @proxy_id,
        @proxy_name = @proxy_name,
        @schedule_uid = @schedule_uid,
        @schedule_name = @schedule_name,
        @logging_level = @logging_level,
        @description = @description,
        @schedule_id = @schedule_id,
        @old_collection_mode = @old_collection_mode,
        @old_proxy_id = @old_proxy_id,
        @old_collection_job_id = @old_collection_job_id,
        @old_upload_job_id = @old_upload_job_id
            
     IF (@retVal <> 0)
        RETURN (1)
        
     -- Restart the collection set if it has been already running
     IF (@is_running = 1)
     BEGIN
         EXEC @retVal = sp_syscollector_start_collection_set
                            @collection_set_id = @collection_set_id
         IF (@retVal <> 0)
            RETURN (1)
     END
        
     RETURN (0)
END
GO

IF (NOT OBJECT_ID('dbo.sp_syscollector_configure_sql_dumper', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_configure_sql_dumper]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_configure_sql_dumper]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_configure_sql_dumper]
    @collection_set_id        int = NULL,
    @name                    sysname = NULL,
    @dump_on_any_error      bit = NULL,                -- configure SQL dumper to dump on any SSIS errors
    @dump_on_codes          nvarchar(max) = NULL    -- configure SQL dumper to dump when we hit one of the specified SSIS errors. Set to N'' to remove the codes.
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN(1) -- Failure
    END

    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    DECLARE @is_running bit
    SELECT    @is_running = is_running
    FROM dbo.syscollector_collection_sets
    WHERE collection_set_id = @collection_set_id
    IF (@is_running = 1)
    BEGIN
        RAISERROR(14711, 0, 1)
    END

    IF (@dump_on_codes = N'')
    BEGIN
        UPDATE [dbo].[syscollector_collection_sets_internal]
        SET dump_on_codes = NULL
        WHERE @collection_set_id = collection_set_id
    END
    ELSE IF (@dump_on_codes IS NOT NULL)
    BEGIN
        UPDATE [msdb].[dbo].[syscollector_collection_sets_internal]
        SET dump_on_codes = @dump_on_codes
        WHERE @collection_set_id = collection_set_id
    END    

    IF (@dump_on_any_error IS NOT NULL)
    BEGIN
        UPDATE [msdb].[dbo].[syscollector_collection_sets_internal]
        SET dump_on_any_error = @dump_on_any_error
        WHERE @collection_set_id = collection_set_id
    END

    RETURN (0)
END
GO

---------------------------------------------------------------
-- Collector type
---------------------------------------------------------------
IF (OBJECT_ID(N'[dbo].[syscollector_collector_types_internal]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [dbo].[syscollector_collector_types_internal]...'
    CREATE TABLE [dbo].[syscollector_collector_types_internal] (
        collector_type_uid                uniqueidentifier NOT NULL,
        name                            sysname NOT NULL,
        parameter_schema                xml NULL,
        parameter_formatter                xml NULL,
        schema_collection                sysname NULL,
        collection_package_name            sysname NOT NULL,
        collection_package_folderid        uniqueidentifier NOT NULL,
        upload_package_name                sysname NOT NULL,
        upload_package_folderid            uniqueidentifier NOT NULL,
        is_system                        bit default 0 NOT NULL,
        CONSTRAINT [PK_syscollector_collector_types_internal] PRIMARY KEY CLUSTERED (collector_type_uid ASC),
        CONSTRAINT [UQ_syscollector_collection_types_internal_name] UNIQUE (name)
        )
    ALTER TABLE syscollector_collector_types_internal
        ADD CONSTRAINT [FK_syscollector_collector_types_internal_upload_sysssispackages] FOREIGN KEY(upload_package_folderid, upload_package_name)
        REFERENCES sysssispackages (folderid, [name])
    ALTER TABLE syscollector_collector_types_internal
        ADD CONSTRAINT [FK_syscollector_collector_types_internal_collection_sysssispackages] FOREIGN KEY(collection_package_folderid, collection_package_name)
        REFERENCES sysssispackages (folderid, [name])
END
GO

-- [fn_syscollector_get_package_path]
-- This function returns the full path of a SSIS package given the package id
IF (NOT OBJECT_ID('[dbo].[fn_syscollector_get_package_path]', 'FN') IS NULL)
BEGIN
    PRINT 'Dropping function [dbo].[fn_syscollector_get_package_path]...'
    DROP FUNCTION [dbo].[fn_syscollector_get_package_path]
END
GO

PRINT 'Creating function [dbo].[fn_syscollector_get_package_path]...'
GO
CREATE FUNCTION [dbo].[fn_syscollector_get_package_path] 
(
    @package_id uniqueidentifier
)
RETURNS NVARCHAR(4000)
AS
BEGIN
    IF @package_id IS NULL
        RETURN NULL

    DECLARE @package_path nvarchar(4000)
    DECLARE @prevfolderid uniqueidentifier
    DECLARE @folderid uniqueidentifier
    DECLARE @package_name sysname
    SET @package_path = ''

    SELECT @package_name = name, 
            @folderid = folderid 
    FROM dbo.sysssispackages
    WHERE id = @package_id

    WHILE (@folderid != '00000000-0000-0000-0000-000000000000')
    BEGIN
        SET @prevfolderid = @folderid

        DECLARE @foldername sysname
        SELECT @foldername = foldername, 
                @folderid = parentfolderid 
        FROM dbo.sysssispackagefolders
        WHERE folderid = @prevfolderid
        SET @package_path = @foldername + N'\\' + @package_path
    END

    SET @package_path = N'\\' + @package_path + @package_name
    RETURN @package_path
END
GO

IF (NOT OBJECT_ID(N'[dbo].[syscollector_collector_types]', 'V') IS NULL)
BEGIN
    PRINT 'Dropping view [dbo].[syscollector_collector_types]...'
    DROP VIEW [dbo].[syscollector_collector_types]
END
GO

PRINT 'Creating view [dbo].[syscollector_collector_types]...'
GO
CREATE VIEW [dbo].[syscollector_collector_types]
AS
    SELECT 
        t.collector_type_uid,
        t.name,
        t.parameter_schema,
        t.parameter_formatter,
        s1.id AS collection_package_id,
        dbo.fn_syscollector_get_package_path(s1.id) AS collection_package_path,
        s1.name AS collection_package_name,
        s2.id AS upload_package_id,
        dbo.fn_syscollector_get_package_path(s2.id) AS upload_package_path,
        s2.name AS upload_package_name,
        t.is_system
    FROM 
        [dbo].[syscollector_collector_types_internal] AS t,
        sysssispackages s1,
        sysssispackages s2
    WHERE t.collection_package_folderid = s1.folderid
      AND t.collection_package_name = s1.name
      AND t.upload_package_folderid = s2.folderid
      AND t.upload_package_name = s2.name
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_verify_collector_type]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_verify_collector_type]...'
    DROP PROCEDURE [dbo].[sp_syscollector_verify_collector_type]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_verify_collector_type]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_verify_collector_type]
    @collector_type_uid        uniqueidentifier = NULL OUTPUT,
    @name                    sysname = NULL OUTPUT
AS
BEGIN
    IF (@name IS NOT NULL)
    BEGIN
        -- Remove any leading/trailing spaces from parameters
        SET @name                    = NULLIF(LTRIM(RTRIM(@name)), N'')
    END

    IF (@collector_type_uid IS NULL AND @name IS NULL)
    BEGIN
        RAISERROR(14624, -1, -1, '@collector_type_uid, @name')
        RETURN(1)
    END

    IF (@collector_type_uid IS NOT NULL AND @name IS NOT NULL)
    BEGIN
        IF (NOT EXISTS(SELECT *
                        FROM dbo.syscollector_collector_types
                        WHERE collector_type_uid = @collector_type_uid
                        AND name = @name))
        BEGIN
            DECLARE @errMsg NVARCHAR(196)
            SELECT @errMsg = CONVERT(NVARCHAR(36), @collector_type_uid) + ', ' + @name
            RAISERROR(14262, -1, -1, '@collector_type_uid, @name', @errMsg)
            RETURN(1)
        END
    END
    -- Check id
    ELSE IF (@collector_type_uid IS NOT NULL)
    BEGIN
        SELECT @name = name
        FROM dbo.syscollector_collector_types
        WHERE (collector_type_uid = @collector_type_uid)
    
        -- the view would take care of all the permissions issues.
        IF (@name IS NULL) 
        BEGIN
            DECLARE @collector_type_uid_as_char VARCHAR(36)
            SELECT @collector_type_uid_as_char = CONVERT(VARCHAR(36), @collector_type_uid)
            RAISERROR(14262, -1, -1, '@collector_type_uid', @collector_type_uid_as_char)
            RETURN(1) -- Failure
        END
    END
    -- Check name
    ELSE IF (@name IS NOT NULL)
    BEGIN
        -- get the corresponding collector_type_uid (if the collector type exists)
        SELECT @collector_type_uid = collector_type_uid
        FROM dbo.syscollector_collector_types
        WHERE (name = @name)

        -- the view would take care of all the permissions issues.
        IF (@collector_type_uid IS NULL)
        BEGIN
            RAISERROR(14262, -1, -1, '@name', @name)
            RETURN(1) -- Failure
        END
    END
    RETURN (0)
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_create_collector_type]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_create_collector_type]...'
    DROP PROCEDURE [dbo].[sp_syscollector_create_collector_type]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_create_collector_type]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_create_collector_type]
    @collector_type_uid            uniqueidentifier = NULL OUTPUT,
    @name                        sysname,
    @parameter_schema            xml = NULL,
    @parameter_formatter        xml = NULL,
    @collection_package_id        uniqueidentifier,
    @upload_package_id            uniqueidentifier
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_create_collector_type
    ELSE
        BEGIN TRANSACTION
    BEGIN TRY

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN (1)
    END

    SET @name                = NULLIF(LTRIM(RTRIM(@name)), N'')
    IF (@name IS NULL) 
    BEGIN
        RAISERROR(21263, -1, -1, '@name', @name)
        RETURN (1)
    END

    IF (@collector_type_uid IS NULL) 
    BEGIN
        SET @collector_type_uid = NEWID()
    END
    
    IF (NOT EXISTS(SELECT * from sysssispackages
        WHERE @collection_package_id = id))
    BEGIN
        DECLARE @collection_package_id_as_char VARCHAR(36)
        SELECT @collection_package_id_as_char = CONVERT(VARCHAR(36), @collection_package_id)
        RAISERROR(14262, -1, -1, '@collection_package_id', @collection_package_id_as_char)
        RETURN (1)
    END

    IF (NOT EXISTS(SELECT * from sysssispackages
        WHERE @upload_package_id = id))
    BEGIN
        DECLARE @upload_package_id_as_char VARCHAR(36)
        SELECT @upload_package_id_as_char = CONVERT(VARCHAR(36), @upload_package_id)
        RAISERROR(14262, -1, -1, '@upload_package_id', @upload_package_id_as_char)
        RETURN (1)
    END

    DECLARE @collection_package_name sysname
    DECLARE @collection_package_folderid uniqueidentifier
    DECLARE @upload_package_name sysname
    DECLARE @upload_package_folderid uniqueidentifier    

    SELECT 
        @collection_package_name = name,
        @collection_package_folderid = folderid
    FROM sysssispackages
    WHERE @collection_package_id = id

    SELECT 
        @upload_package_name = name,
        @upload_package_folderid = folderid
    FROM sysssispackages
    WHERE @upload_package_id = id

    DECLARE @schema_collection sysname
    IF (@parameter_schema IS NOT NULL)
    BEGIN
        SET @schema_collection = N'schema_collection_' + @name
        WHILE (EXISTS (SELECT * FROM sys.xml_schema_collections WHERE name = @schema_collection))
        BEGIN
            SET @schema_collection = LEFT(@schema_collection, 119) + '_' + RIGHT(STR(FLOOR(RAND() * 100000000)),8)
        END

        DECLARE @retVal int
        DECLARE @sql_string nvarchar(2048)
        DECLARE @param_definition nvarchar(16)
        SET @param_definition = N'@schema xml'
        SET @sql_string = N'CREATE XML SCHEMA COLLECTION ' + QUOTENAME(@schema_collection, '[') + N' AS @schema; '
        SET @sql_string = @sql_string + N'GRANT EXECUTE ON XML SCHEMA COLLECTION::[dbo].' + QUOTENAME(@schema_collection, '[') + N' TO dc_admin; ' 
        SET @sql_string = @sql_string + N'GRANT VIEW DEFINITION ON XML SCHEMA COLLECTION::[dbo].' + QUOTENAME(@schema_collection, '[') + N' TO dc_admin; '

        EXEC sp_executesql @sql_string, @param_definition, @schema = @parameter_schema
    END

    INSERT INTO [dbo].[syscollector_collector_types_internal]
    (
        collector_type_uid,
        name,
        parameter_schema,
        parameter_formatter,
        schema_collection,
        collection_package_name,
        collection_package_folderid,
        upload_package_name,
        upload_package_folderid
    )
    VALUES
    (
        @collector_type_uid,
        @name,
        @parameter_schema,
        @parameter_formatter,
        @schema_collection,
        @collection_package_name,
        @collection_package_folderid,
        @upload_package_name,
        @upload_package_folderid
    )

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_create_collector_type

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)    
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_update_collector_type]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_update_collector_type]...'
    DROP PROCEDURE [dbo].[sp_syscollector_update_collector_type]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_update_collector_type]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_update_collector_type]
    @collector_type_uid            uniqueidentifier = NULL,
    @name                        sysname = NULL,
    @parameter_schema            xml = NULL,
    @parameter_formatter        xml = NULL,
    @collection_package_id        uniqueidentifier,
    @upload_package_id            uniqueidentifier
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_update_collector_type
    ELSE
        BEGIN TRANSACTION
    BEGIN TRY

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN (1)
    END

    -- Check the validity of the name/uid pair
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collector_type @collector_type_uid OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)
    
    DECLARE @old_parameter_schema       xml
    DECLARE @old_parameter_formatter    xml
    DECLARE @old_collection_package_id  uniqueidentifier
    DECLARE @old_upload_package_id      uniqueidentifier

    SELECT  @old_parameter_schema = parameter_schema,
            @old_parameter_formatter = parameter_formatter,
            @old_collection_package_id = collection_package_id,
            @old_upload_package_id = upload_package_id
    FROM [dbo].[syscollector_collector_types]
    WHERE name = @name
    AND collector_type_uid = @collector_type_uid

    IF (@collection_package_id IS NULL)
    BEGIN
        SET @collection_package_id = @old_collection_package_id
    END
    ELSE IF (NOT EXISTS(SELECT * from sysssispackages
                        WHERE @collection_package_id = id))
    BEGIN
        DECLARE @collection_package_id_as_char VARCHAR(36)
        SELECT @collection_package_id_as_char = CONVERT(VARCHAR(36), @collection_package_id)
        RAISERROR(14262, -1, -1, '@collection_package_id', @collection_package_id_as_char)
        RETURN (1)
    END

    IF (@upload_package_id IS NULL)
    BEGIN
        SET @upload_package_id = @old_upload_package_id
    END
    ELSE IF (NOT EXISTS(SELECT * from sysssispackages
                        WHERE @upload_package_id = id))
    BEGIN
        DECLARE @upload_package_id_as_char VARCHAR(36)
        SELECT @upload_package_id_as_char = CONVERT(VARCHAR(36), @upload_package_id)
        RAISERROR(14262, -1, -1, '@upload_package_id', @upload_package_id_as_char)
        RETURN (1)
    END

    DECLARE @collection_package_name sysname
    DECLARE @collection_package_folderid uniqueidentifier
    DECLARE @upload_package_name sysname
    DECLARE @upload_package_folderid uniqueidentifier    

    SELECT 
        @collection_package_name = name,
        @collection_package_folderid = folderid
    FROM sysssispackages
    WHERE @collection_package_id = id

    SELECT 
        @upload_package_name = name,
        @upload_package_folderid = folderid
    FROM sysssispackages
    WHERE @upload_package_id = id

    DECLARE @schema_collection sysname
    IF (@parameter_schema IS NULL)
    BEGIN
        SET @parameter_schema = @old_parameter_schema
    END
    ELSE
    BEGIN
        SELECT @schema_collection = schema_collection
        FROM [dbo].[syscollector_collector_types_internal]
        WHERE name = @name
        AND collector_type_uid = @collector_type_uid

        -- if a previous xml schema collection existed with the same name, drop it in favor of the new schema
        IF (EXISTS (SELECT * FROM sys.xml_schema_collections WHERE name = @schema_collection))
        BEGIN
            DECLARE @sql_drop_schema nvarchar(512)
            SET @sql_drop_schema = N'DROP XML SCHEMA COLLECTION ' + QUOTENAME(@schema_collection)
            EXECUTE sp_executesql @sql_drop_schema
        END

        IF(@schema_collection IS NULL)
        BEGIN
            SELECT @schema_collection = 'schema_collection' + name
            FROM [dbo].[syscollector_collector_types_internal]
            WHERE collector_type_uid = @collector_type_uid
        END

        -- recreate it with the new schema
        DECLARE @sql_create_schema nvarchar(2048)
        DECLARE @param_definition nvarchar(16)
        SET @param_definition = N'@schema xml'
        SET @sql_create_schema = N'CREATE XML SCHEMA COLLECTION ' + QUOTENAME(@schema_collection) + N' AS @schema; '
        SET @sql_create_schema = @sql_create_schema + N'GRANT EXECUTE ON XML SCHEMA COLLECTION::[dbo].' + QUOTENAME(@schema_collection) + N' TO dc_admin; ' 
        SET @sql_create_schema = @sql_create_schema + N'GRANT VIEW DEFINITION ON XML SCHEMA COLLECTION::[dbo].' + QUOTENAME(@schema_collection) + N' TO dc_admin; '
            
        EXEC sp_executesql @sql_create_schema, @param_definition, @schema = @parameter_schema
    END

    UPDATE [dbo].[syscollector_collector_types_internal]
    SET parameter_schema = @parameter_schema,
        parameter_formatter = @parameter_formatter,
        schema_collection = @schema_collection,
        collection_package_name = @collection_package_name,
        collection_package_folderid = @collection_package_folderid,
        upload_package_name = @upload_package_name,
        upload_package_folderid = @upload_package_folderid
    WHERE @collector_type_uid = collector_type_uid
    AND   @name = name

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_update_collector_type

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)    
    END CATCH
END
GO



IF (NOT OBJECT_ID('[dbo].[sp_syscollector_validate_xml]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_validate_xml]...'
    DROP PROCEDURE [dbo].[sp_syscollector_validate_xml]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_validate_xml]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_validate_xml]
    @collector_type_uid        uniqueidentifier = NULL,
    @name                    sysname = NULL,
    @parameters                xml
AS
BEGIN
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collector_type @collector_type_uid OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    DECLARE @schema_collection sysname
    SET @schema_collection = (SELECT schema_collection 
                                FROM syscollector_collector_types_internal
                                WHERE @collector_type_uid = collector_type_uid)

    IF (@schema_collection IS NULL)
    BEGIN
        RAISERROR(21263, -1, -1, '@schema_collection')
        RETURN (1)
    END

        -- @sql_string should have enough buffer to include 2n+2 max size for QUOTENAME(@schema_collection).
    DECLARE @sql_string nvarchar(328)
    DECLARE @param_definition nvarchar(16)
    SET @param_definition = N'@param xml'
    SET @sql_string = N'DECLARE @validated_xml XML (DOCUMENT ' + QUOTENAME(@schema_collection, '[') + N'); SELECT @validated_xml = @param';
    EXEC @retVal = sp_executesql @sql_string, @param_definition, @param = @parameters

    IF (@retVal <> 0)
    BEGIN
        RETURN (1)
    END
END
GO

---------------------------------------------------------------
-- Collection item
---------------------------------------------------------------
IF (OBJECT_ID(N'[dbo].[syscollector_collection_items_internal]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [dbo].[syscollector_collection_items_internal]...'
    CREATE TABLE [dbo].[syscollector_collection_items_internal] (
        collection_set_id            int NOT NULL,
        collection_item_id            int IDENTITY NOT NULL,
        collector_type_uid            uniqueidentifier NOT NULL,
        name                        sysname NOT NULL,
        name_id                        int NULL,                        -- sysmessage id of the name of the item (for localizing system collection set)
        frequency                    int NOT NULL,
        parameters                    xml NULL,
        CONSTRAINT [PK_syscollector_collection_items_internal] PRIMARY KEY CLUSTERED (collection_set_id ASC, collection_item_id ASC),
        CONSTRAINT [UQ_syscollector_collection_items_internal_name] UNIQUE (name)
        )
    ALTER TABLE syscollector_collection_items_internal
        ADD CONSTRAINT [FK_syscollector_collection_items_internal_syscollector_collection_sets_internal] FOREIGN KEY(collection_set_id)
        REFERENCES syscollector_collection_sets_internal (collection_set_id) ON DELETE CASCADE
    ALTER TABLE syscollector_collection_items_internal
        ADD CONSTRAINT [FK_syscollector_collection_items_internal_syscollector_collector_types_internal] FOREIGN KEY(collector_type_uid)
        REFERENCES syscollector_collector_types_internal (collector_type_uid) ON DELETE CASCADE
END ELSE BEGIN   
    IF (NOT EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name = N'name_id' AND id = OBJECT_ID(N'[dbo].[syscollector_collection_items_internal]')))
    BEGIN
        PRINT 'Altering table [dbo].[syscollector_collection_items_internal]...'
        ALTER TABLE [dbo].[syscollector_collection_items_internal] ADD name_id int NULL
    END    
END
GO

IF (NOT OBJECT_ID(N'[dbo].[syscollector_collection_items]', 'V') IS NULL)
BEGIN
    PRINT 'Dropping view [dbo].[syscollector_collection_items]...'
    DROP VIEW [dbo].[syscollector_collection_items]
END
GO

PRINT 'Creating view [dbo].[syscollector_collection_items]...'
GO
CREATE VIEW [dbo].[syscollector_collection_items]
AS
    SELECT
        i.collection_set_id,
        i.collection_item_id,
        i.collector_type_uid,
        CASE 
            WHEN i.name_id IS NULL THEN i.name 
            ELSE FORMATMESSAGE(i.name_id)
        END AS name,        
        i.frequency,
        i.parameters
    FROM 
        [dbo].[syscollector_collection_items_internal] i
GO

-- This is a stored procedure of collector_type, but it is created here because it 
-- makes references to the collection item view
IF (NOT OBJECT_ID('[dbo].[sp_syscollector_delete_collector_type]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_delete_collector_type]...'
    DROP PROCEDURE [dbo].[sp_syscollector_delete_collector_type]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_delete_collector_type]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_delete_collector_type]
    @collector_type_uid            uniqueidentifier = NULL,
    @name                        sysname = NULL
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_delete_collector_type
    ELSE
        BEGIN TRANSACTION
    BEGIN TRY

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN (1)
    END
    
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collector_type @collector_type_uid OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    IF (EXISTS(SELECT * from dbo.syscollector_collection_items
        WHERE @collector_type_uid = collector_type_uid))
    BEGIN
        RAISERROR(14673, -1, -1, @name)
        RETURN (1)
    END
    
    DECLARE @schema_collection sysname
        -- @sql_string should have enough buffer to include 2n+2 max size for QUOTENAME(@schema_collection).
    DECLARE @sql_string nvarchar(285)
    SET @schema_collection = (SELECT schema_collection 
                                FROM syscollector_collector_types_internal
                                WHERE @collector_type_uid = collector_type_uid)
    SET @sql_string = N'DROP XML SCHEMA COLLECTION ' + QUOTENAME(@schema_collection, '[');
    EXEC sp_executesql @sql_string

    DELETE [dbo].[syscollector_collector_types_internal]
    WHERE collector_type_uid = @collector_type_uid

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_delete_collector_type

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_verify_collection_item]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_verify_collection_item]...'
    DROP PROCEDURE [dbo].[sp_syscollector_verify_collection_item]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_verify_collection_item]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_verify_collection_item]
    @collection_item_id        int = NULL OUTPUT,
    @name                    sysname = NULL OUTPUT
AS
BEGIN
    IF (@name IS NOT NULL)
    BEGIN
        -- Remove any leading/trailing spaces from parameters
        SET @name                    = NULLIF(LTRIM(RTRIM(@name)), N'')
    END

    IF (@collection_item_id IS NULL AND @name IS NULL)
    BEGIN
        RAISERROR(14624, -1, -1, '@collection_item_id, @name')
        RETURN(1)
    END

    IF (@collection_item_id IS NOT NULL AND @name IS NOT NULL)
    BEGIN
        IF (NOT EXISTS(SELECT *
                        FROM dbo.syscollector_collection_items
                        WHERE collection_item_id = @collection_item_id
                        AND name = @name))
        BEGIN
            DECLARE @errMsg NVARCHAR(196)
            SELECT @errMsg = CONVERT(NVARCHAR(36), @collection_item_id) + ', ' + @name
            RAISERROR(14262, -1, -1, '@collection_item_id, @name', @errMsg)
            RETURN(1)
        END
    END
    -- Check id
    ELSE IF (@collection_item_id IS NOT NULL)
    BEGIN
        SELECT @name = name
        FROM dbo.syscollector_collection_items
        WHERE (collection_item_id = @collection_item_id)
    
        -- the view would take care of all the permissions issues.
        IF (@name IS NULL) 
        BEGIN
            DECLARE @collection_item_id_as_char VARCHAR(36)
            SELECT @collection_item_id_as_char = CONVERT(VARCHAR(36), @collection_item_id)
            RAISERROR(14262, -1, -1, '@collection_item_id', @collection_item_id_as_char)
            RETURN(1) -- Failure
        END
    END
    -- Check name
    ELSE IF (@name IS NOT NULL)
    BEGIN
        -- get the corresponding collection_item_id (if the collection_item exists)
        SELECT @collection_item_id = collection_item_id
        FROM dbo.syscollector_collection_items
        WHERE (name = @name)

        -- the view would take care of all the permissions issues.
        IF (@collection_item_id IS NULL)
        BEGIN
            RAISERROR(14262, -1, -1, '@name', @name)
            RETURN(1) -- Failure
        END
    END
    RETURN (0)
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_create_collection_item]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_create_collection_item]...'
    DROP PROCEDURE [dbo].[sp_syscollector_create_collection_item]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_create_collection_item]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_create_collection_item]
    @collection_set_id        int,
    @collector_type_uid        uniqueidentifier,
    @name                    sysname,
    @frequency                int = 5,                -- set by default to the minimum frequency
    @parameters                xml = NULL,
    @collection_item_id        int OUTPUT
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_create_collection_item
    ELSE
        BEGIN TRANSACTION
    BEGIN TRY
        -- Security check (role membership)
        IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        BEGIN
            RAISERROR(14677, -1, -1, 'dc_admin')
            RETURN (1)
        END

        DECLARE @is_system bit
        SELECT    @is_system = is_system
        FROM dbo.syscollector_collection_sets
        WHERE collection_set_id = @collection_set_id
        
        IF (@is_system = 1)
        BEGIN
            -- cannot update, delete, or add new collection items to a system collection set
            RAISERROR(14696, -1, -1);
            RETURN (1)
        END

        SET @name            = NULLIF(LTRIM(RTRIM(@name)), N'')
        IF (@name IS NULL) 
        BEGIN
            RAISERROR(21263, -1, -1, '@name')
            RETURN (1)
        END
        
        IF (@frequency < 5)
        BEGIN
            DECLARE @frequency_as_char VARCHAR(36)
            SELECT @frequency_as_char = CONVERT(VARCHAR(36), @frequency)
            RAISERROR(21405, 16, -1, @frequency_as_char, '@frequency', 5)
            RETURN (1)
        END

        IF (NOT EXISTS(SELECT * from dbo.syscollector_collector_types
            WHERE @collector_type_uid = collector_type_uid))
        BEGIN
            DECLARE @collector_type_uid_as_char VARCHAR(36)
            SELECT @collector_type_uid_as_char = CONVERT(VARCHAR(36), @collector_type_uid)
            RAISERROR(14262, -1, -1, '@collector_type_uid', @collector_type_uid_as_char)
            RETURN (1)
        END
        
        IF (NOT EXISTS(SELECT * from dbo.syscollector_collection_sets
            WHERE @collection_set_id = collection_set_id))
        BEGIN
            DECLARE @collection_set_id_as_char VARCHAR(36)
            SELECT @collection_set_id_as_char = CONVERT(VARCHAR(36), @collection_set_id)
            RAISERROR(14262, -1, -1, '@collection_set_id', @collection_set_id_as_char)
            RETURN (1)
        END

        DECLARE @is_running bit
        SELECT    @is_running = is_running
        FROM dbo.syscollector_collection_sets
        WHERE collection_set_id = @collection_set_id
        IF (@is_running = 1)
        BEGIN
            RAISERROR(14675, -1, -1, @name)
            RETURN (1)
        END

        IF (@parameters IS NULL)
        BEGIN
            DECLARE @parameter_schema xml
            SELECT @parameter_schema = parameter_schema FROM syscollector_collector_types WHERE collector_type_uid = @collector_type_uid
            IF (@parameter_schema IS NOT NULL)    -- only allows parameters to be null if the collector type has a null schema
            BEGIN
                RAISERROR(21263, -1, -1, '@parameters')
                RETURN (1)
            END
        END
        ELSE IF (LTRIM(RTRIM(CONVERT(nvarchar(max), @parameters))) <> N'') -- don't check if the parameters are empty string
        BEGIN
            EXEC dbo.sp_syscollector_validate_xml @collector_type_uid = @collector_type_uid, @parameters = @parameters
        END

        INSERT INTO [dbo].[syscollector_collection_items_internal]
        (
            collection_set_id,
            collector_type_uid,
            name,
            frequency,
            parameters
        )
        VALUES
        (
            @collection_set_id,
            @collector_type_uid,
            @name,
            @frequency,
            @parameters
        )

        SET @collection_item_id = SCOPE_IDENTITY()

        IF (@collection_item_id IS NULL)
        BEGIN
            DECLARE @collection_item_id_as_char VARCHAR(36)
            SELECT @collection_item_id_as_char = CONVERT(VARCHAR(36), @collection_item_id)
            RAISERROR(14262, -1, -1, '@collection_item_id', @collection_item_id_as_char)
            RETURN (1)
        END

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_create_collection_item

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_update_collection_item_internal]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_update_collection_item_internal]...'
    DROP PROCEDURE [dbo].[sp_syscollector_update_collection_item_internal]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_update_collection_item_internal]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_update_collection_item_internal]
    @collection_item_id        int = NULL,
    @name                    sysname = NULL,
    @new_name                sysname = NULL,
    @frequency                int = NULL,
    @parameters                xml = NULL
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_update_collection_item
    ELSE
        BEGIN TRANSACTION
    BEGIN TRY
        UPDATE [dbo].[syscollector_collection_items_internal]
        SET
            name                = ISNULL(@new_name, name),
            frequency            = ISNULL(@frequency, frequency),
            parameters            = ISNULL(@parameters, parameters)
        WHERE @collection_item_id = collection_item_id

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_update_collection_item

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_update_collection_item]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_update_collection_item]...'
    DROP PROCEDURE [dbo].[sp_syscollector_update_collection_item]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_update_collection_item]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_update_collection_item]
    @collection_item_id        int = NULL,
    @name                    sysname = NULL,
    @new_name                sysname = NULL,
    @frequency                int = NULL,
    @parameters                xml = NULL
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END

    -- Security checks (restrict functionality for non-dc_admin-s)
    IF ((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1)) 
        AND (@new_name IS NOT NULL))
    BEGIN
        RAISERROR(14676, -1, -1, '@new_name', 'dc_admin')
        RETURN (1) -- Failure
    END
    IF ((NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
        AND (@parameters IS NOT NULL))
    BEGIN
        RAISERROR(14676, -1, -1, '@parameters', 'dc_admin')
        RETURN (1) -- Failure
    END

    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_item @collection_item_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (@retVal)

    IF (@frequency < 5)
    BEGIN
        DECLARE @frequency_as_char VARCHAR(36)
        SELECT @frequency_as_char = CONVERT(VARCHAR(36), @frequency)
        RAISERROR(21405, 16, -1, @frequency_as_char, '@frequency', 5)
        RETURN (1)
    END

    IF (LEN(@new_name) = 0)  -- can't rename to an empty string
    BEGIN
      RAISERROR(21263, -1, -1, '@new_name')
      RETURN(1) -- Failure
    END    

    -- Remove any leading/trailing spaces from parameters
    SET @new_name            = LTRIM(RTRIM(@new_name))

    DECLARE @collection_set_name sysname
    DECLARE @is_system              bit
    DECLARE @is_running             bit
    DECLARE @collector_type_uid     uniqueidentifier
    DECLARE @collection_set_id      int
    SELECT @is_running = s.is_running,
           @is_system = s.is_system,
           @collection_set_name = s.name,
           @collector_type_uid = i.collector_type_uid,
           @collection_set_id = s.collection_set_id
    FROM dbo.syscollector_collection_sets s,
         dbo.syscollector_collection_items i
    WHERE s.collection_set_id = i.collection_set_id
    AND i.collection_item_id = @collection_item_id

    IF (@is_system = 1 AND (@new_name IS NOT NULL))
    BEGIN
        -- cannot update, delete, or add new collection items to a system collection set
        RAISERROR(14696, -1, -1);
        RETURN (1)
    END

    IF (@parameters IS NOT NULL)
    BEGIN
        EXEC @retVal = dbo.sp_syscollector_validate_xml @collector_type_uid = @collector_type_uid, @parameters = @parameters
        IF (@retVal <> 0)
            RETURN (@retVal)
    END

    -- if the collection item is running, stop it before update
    IF (@is_running = 1)
    BEGIN
        EXEC @retVal = sp_syscollector_stop_collection_set @collection_set_id = @collection_set_id
        IF (@retVal <> 0)
            RETURN(1)
    END

    -- all conditions go, perform the update
    EXEC @retVal = sp_syscollector_update_collection_item_internal     
                            @collection_item_id = @collection_item_id,
                            @name = @name,
                            @new_name = @new_name,
                            @frequency = @frequency,
                            @parameters = @parameters
                        
    -- if you stopped the collection set, restart it
    IF (@is_running = 1)
    BEGIN
        EXEC @retVal = sp_syscollector_start_collection_set @collection_set_id = @collection_set_id
        IF (@retVal <> 0)
            RETURN (1)
    END
    
    RETURN (0)
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_delete_collection_item_internal]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_delete_collection_item_internal]...'
    DROP PROCEDURE [dbo].[sp_syscollector_delete_collection_item_internal]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_delete_collection_item_internal]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_delete_collection_item_internal]
    @collection_item_id         int,
    @name                       sysname
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_delete_collection_item
    ELSE
        BEGIN TRANSACTION
    BEGIN TRY
        DELETE [dbo].[syscollector_collection_items_internal]
        WHERE collection_item_id = @collection_item_id
          AND name = @name

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_delete_collection_item

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_delete_collection_item]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_delete_collection_item]...'
    DROP PROCEDURE [dbo].[sp_syscollector_delete_collection_item]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_delete_collection_item]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_delete_collection_item]
    @collection_item_id        int = NULL,
    @name                    sysname = NULL
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN(1) -- Failure
    END

    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_item @collection_item_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    DECLARE @is_system          bit
    DECLARE @is_running         bit
    DECLARE @collection_set_id  int
    SELECT @is_running = s.is_running,
           @is_system = s.is_system,
           @collection_set_id = s.collection_set_id
    FROM dbo.syscollector_collection_sets s,
         dbo.syscollector_collection_items i
    WHERE i.collection_item_id = @collection_item_id
    AND s.collection_set_id = i.collection_set_id

    IF (@is_system = 1)
    BEGIN
        -- cannot update, delete, or add new collection items to a system collection set
        RAISERROR(14696, -1, -1);
        RETURN(1)
    END

    IF (@is_running = 1)
    BEGIN
        -- stop the collection set if it was running
        EXEC @retVal = sp_syscollector_stop_collection_set @collection_set_id = @collection_set_id
        IF (@retVal <> 0)
            RETURN (1)
    END

    -- all checks go, perform delete
    EXEC @retVal = sp_syscollector_delete_collection_item_internal @collection_item_id = @collection_item_id, @name = @name
    IF (@retVal <> 0)
        RETURN (1)
        
    RETURN (0)
END
GO

---------------------------------------------------------------
-- Collection Set runtime procedures
---------------------------------------------------------------

IF (NOT OBJECT_ID('dbo.sp_syscollector_start_collection_set', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_start_collection_set]...'
    DROP PROCEDURE [dbo].[sp_syscollector_start_collection_set]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_start_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_start_collection_set]
    @collection_set_id        int = NULL,
    @name                     sysname = NULL
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_start_collection_set
    ELSE
        BEGIN TRANSACTION

    BEGIN TRY

    -- Security check (role membership)
    EXECUTE AS CALLER;
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END
    REVERT;

    -- check if SQL Server Agent is enabled
    DECLARE @agent_enabled int
    SELECT @agent_enabled = CAST(value_in_use AS int) FROM sys.configurations WHERE name = N'Agent XPs'
    IF @agent_enabled <> 1
    BEGIN
        RAISERROR(14688, -1, -1)
        RETURN (1)
    END

    -- check if MDW is setup
    DECLARE @instance_name sysname
    SELECT @instance_name = CONVERT(sysname,parameter_value)
    FROM [msdb].[dbo].[syscollector_config_store_internal]
    WHERE parameter_name = N'MDWInstance'
    IF (@instance_name IS NULL)
    BEGIN
        RAISERROR(14689, -1, -1)
        RETURN (1)
    END    
    DECLARE @database_name sysname
    SELECT @database_name = CONVERT(sysname,parameter_value)
    FROM [msdb].[dbo].[syscollector_config_store_internal]
    WHERE parameter_name = N'MDWDatabase'
    IF (@database_name IS NULL)
    BEGIN
        RAISERROR(14689, -1, -1)
        RETURN (1)
    END

    -- Verify the input parameters
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    -- Check if the collection set does not have any collection items
    IF NOT EXISTS(
        SELECT i.collection_item_id 
        FROM [dbo].[syscollector_collection_sets] AS s
        INNER JOIN [dbo].[syscollector_collection_items] AS i
            ON(s.collection_set_id = i.collection_set_id)
        WHERE s.collection_set_id = @collection_set_id
    )
    BEGIN
        RAISERROR(14685, 10, -1, @name) -- Raise a warning message
        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END

    DECLARE @proxy_id int;
    DECLARE @collection_job_id uniqueidentifier
    DECLARE @upload_job_id uniqueidentifier
    DECLARE @schedule_uid uniqueidentifier;

    SELECT 
        @collection_job_id = collection_job_id, 
        @upload_job_id = upload_job_id, 
        @proxy_id = proxy_id,
        @schedule_uid = schedule_uid
    FROM [dbo].[syscollector_collection_sets_internal]
    WHERE collection_set_id = @collection_set_id;

    -- Check if the set does not have a proxy
    IF (@proxy_id IS NULL)
    BEGIN
        -- to start a collection set without a proxy, the caller has to be a sysadmin
        EXECUTE AS CALLER;
            IF (NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
            BEGIN
                REVERT;
                RAISERROR(14692, -1, -1, @name)
                RETURN (1)
            END
        REVERT;
    END

    -- Starting a collection set requires a schedule
    IF @schedule_uid IS NULL
    BEGIN
        RAISERROR(14693, -1, -1)
        RETURN (1)
    END

    -- Check if we have jobs created, and if not, create them
    IF (@collection_job_id IS NULL AND @upload_job_id IS NULL)
    BEGIN
        -- Jobs not created yet, go and crete them
        -- We need to get some data from collection_sets table 
        -- before we do that.
        DECLARE @collection_set_uid uniqueidentifier;
        DECLARE @schedule_id int;
        DECLARE @collection_mode int;

        SELECT 
            @collection_set_uid = collection_set_uid,
            @collection_mode = collection_mode
        FROM
            [dbo].[syscollector_collection_sets_internal]
        WHERE
            collection_set_id = @collection_set_id;
        
        -- Sanity check
        -- Make sure the proxy and schedule are still there, someone could have
        -- remove them between when the collection set was created and now.
        IF (@proxy_id IS NOT NULL)
        BEGIN
            DECLARE @proxy_name sysname
            
            -- this will throw if the id does not exist
            EXEC @retVal = sp_verify_proxy_identifiers '@proxy_name', '@proxy_id', @proxy_name OUTPUT, @proxy_id OUTPUT
            IF (@retVal <> 0)
                RETURN (1)
        END

        SELECT @schedule_id = schedule_id FROM sysschedules_localserver_view WHERE @schedule_uid = schedule_uid
        EXEC @retVal = sp_verify_schedule_identifiers  @name_of_name_parameter = '@schedule_name',
                                                       @name_of_id_parameter   = '@schedule_id',
                                                       @schedule_name          = NULL,
                                                       @schedule_id            = @schedule_id,
                                                       @owner_sid              = NULL,
                                                       @orig_server_id         = NULL 
        IF (@retVal <> 0)
            RETURN (1)

        -- Go add the jobs
        EXEC [dbo].[sp_syscollector_create_jobs]
            @collection_set_id    = @collection_set_id,
            @collection_set_uid = @collection_set_uid,
            @collection_set_name = @name,
            @proxy_id            = @proxy_id,
            @schedule_id        = @schedule_id,
            @collection_mode    = @collection_mode,
            @collection_job_id    = @collection_job_id OUTPUT,
            @upload_job_id        = @upload_job_id OUTPUT

        -- Finally, update the collection_sets table
        UPDATE [dbo].[syscollector_collection_sets_internal]
        SET
            upload_job_id        = @upload_job_id,
            collection_job_id    = @collection_job_id
        WHERE @collection_set_id = collection_set_id
    END

    -- Update the is_running column for this collection set
    -- There is a trigger defined for that table that turns on
    -- the collection and upload jobs in response to that bit
    -- changing.
    UPDATE [dbo].[syscollector_collection_sets_internal]
    SET is_running = 1
    WHERE collection_set_id = @collection_set_id

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)

    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_start_collection_set

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)        
    END CATCH
END
GO


IF (NOT OBJECT_ID('dbo.sp_syscollector_stop_collection_set', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_stop_collection_set]...'
    DROP PROCEDURE [dbo].[sp_syscollector_stop_collection_set]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_stop_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_stop_collection_set]
    @collection_set_id        int = NULL,
    @name                     sysname = NULL,
    @stop_collection_job      bit = 1           -- Do we need to stop the collection job, YES by default
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END

    -- Verify the input parameters
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    IF (@stop_collection_job = 1)
    BEGIN
        DECLARE @collection_mode INT
        DECLARE @collection_job_id UNIQUEIDENTIFIER

        SELECT  @collection_mode = collection_mode, @collection_job_id = collection_job_id
        FROM    dbo.syscollector_collection_sets
        WHERE   collection_set_id = @collection_set_id
       
        DECLARE @is_collection_job_running INT
        EXECUTE [dbo].[sp_syscollector_get_collection_set_execution_status]
                @collection_set_id = @collection_set_id,
                @is_collection_running = @is_collection_job_running OUTPUT

        -- Stop the collection job if we are in cached mode, this should signal the runtime to exit
        IF (@is_collection_job_running = 1      -- Collection job is running
            AND @collection_mode = 0
            AND @stop_collection_job = 1)
        BEGIN
            EXEC sp_stop_job @job_id = @collection_job_id
        END
    END


    -- Update the is_running column for this collection set
    -- There is a trigger defined for that table that turns off
    -- the collection and uplaod jobs in response to that bit
    -- changing.
    UPDATE [dbo].[syscollector_collection_sets_internal]
    SET is_running = 0
    WHERE collection_set_id = @collection_set_id

    RETURN (0)
END
GO


IF (NOT OBJECT_ID('[dbo].[sp_syscollector_get_collection_set_execution_status]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_get_collection_set_execution_status]...'
    DROP PROCEDURE [dbo].[sp_syscollector_get_collection_set_execution_status]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_get_collection_set_execution_status]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_get_collection_set_execution_status]
    @collection_set_id            int,
    @is_running                    int = NULL OUTPUT,
    @is_collection_running        int = NULL OUTPUT,
    @collection_job_state        int = NULL OUTPUT,
    @is_upload_running            int = NULL OUTPUT,
    @upload_job_state            int = NULL OUTPUT
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_get_execution_status
    ELSE
        BEGIN TRANSACTION

    BEGIN TRY

    DECLARE @xp_results TABLE (job_id             UNIQUEIDENTIFIER NOT NULL,
                            last_run_date         INT              NOT NULL,
                            last_run_time         INT              NOT NULL,
                            next_run_date         INT              NOT NULL,
                            next_run_time         INT              NOT NULL,
                            next_run_schedule_id  INT              NOT NULL,
                            requested_to_run      INT              NOT NULL, -- BOOL
                            request_source        INT              NOT NULL,
                            request_source_id     sysname          COLLATE database_default NULL,
                            running               INT              NOT NULL, -- BOOL
                            current_step          INT              NOT NULL,
                            current_retry_attempt INT              NOT NULL,
                            job_state             INT              NOT NULL)


    DECLARE @is_sysadmin INT
    SELECT @is_sysadmin = ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0)

    DECLARE @collection_job_id UNIQUEIDENTIFIER
    DECLARE @upload_job_id UNIQUEIDENTIFIER
    
    SELECT @collection_job_id = collection_job_id, @upload_job_id = upload_job_id 
    FROM dbo.syscollector_collection_sets WHERE collection_set_id = @collection_set_id

    DECLARE @agent_enabled int
    SELECT @agent_enabled = CAST(value_in_use AS int) FROM sys.configurations WHERE name = N'Agent XPs'

    --  initialize to 0  == not running state; when agent XPs are disabled, call to xp_sqlagent_enum_jobs is never made in 
    -- code below.  When Agent Xps are disabled agent would not be running, in this case, jobs will also be in 'not running" state
    SET @is_collection_running = 0  
    SET @is_upload_running = 0
    SET @collection_job_state = 0
    SET @upload_job_state = 0

    IF (@agent_enabled <> 0)
    BEGIN
        INSERT  INTO @xp_results    
        EXECUTE master.dbo.xp_sqlagent_enum_jobs @is_sysadmin, N'', @upload_job_id

        INSERT  INTO @xp_results    
        EXECUTE master.dbo.xp_sqlagent_enum_jobs @is_sysadmin, N'', @collection_job_id

        SELECT @is_collection_running = running, 
        @collection_job_state = job_state 
        FROM @xp_results WHERE job_id = @collection_job_id

        SELECT @is_upload_running = running, 
        @upload_job_state = job_state 
        FROM @xp_results WHERE job_id = @upload_job_id
    END

    SELECT @is_running = is_running FROM dbo.syscollector_collection_sets WHERE collection_set_id = @collection_set_id

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)

    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_get_execution_status

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)        
    END CATCH
END
GO


IF (NOT OBJECT_ID('dbo.sp_syscollector_upload_collection_set', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_upload_collection_set]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_upload_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_upload_collection_set]
    @collection_set_id        int = NULL,
    @name                     sysname = NULL
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    EXECUTE AS CALLER;
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END
    REVERT;

    -- Verify the input parameters
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    -- Make sure the collection set is running and is in the right mode
    DECLARE @is_running bit
    DECLARE @collection_mode smallint

    SELECT 
        @is_running = is_running,
        @collection_mode = collection_mode            
    FROM [dbo].[syscollector_collection_sets]
    WHERE collection_set_id = @collection_set_id

    IF (@collection_mode <> 0)
    BEGIN
        RAISERROR(14694, -1, -1, @name)
        RETURN(1)
    END

    IF (@is_running = 0)
    BEGIN
        RAISERROR(14674, -1, -1, @name)
        RETURN(1)
    END

    -- Make sure the collector is enabled
    EXEC @retVal = [dbo].[sp_syscollector_verify_collector_state] @desired_state = 1
    IF (@retVal <> 0)
        RETURN (1)

    -- Check if the upload job is currently running
    DECLARE @is_upload_job_running INT
    EXECUTE [dbo].[sp_syscollector_get_collection_set_execution_status]
        @collection_set_id = @collection_set_id,
        @is_upload_running = @is_upload_job_running OUTPUT

    IF (@is_upload_job_running = 0)
    BEGIN
        -- Job is not running, we can trigger it now
        DECLARE @job_id UNIQUEIDENTIFIER
        SELECT @job_id = upload_job_id 
            FROM [dbo].[syscollector_collection_sets] 
            WHERE collection_set_id = @collection_set_id

        EXEC @retVal = sp_start_job @job_id = @job_id
        IF (@retVal <> 0)
            RETURN (1)
    END

    RETURN (0)
END
GO


IF (NOT OBJECT_ID('dbo.sp_syscollector_run_collection_set', 'P') IS NULL)
BEGIN
    DROP PROCEDURE [dbo].[sp_syscollector_run_collection_set]
END
GO

PRINT ''
PRINT 'Creating procedure [dbo].[sp_syscollector_run_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_run_collection_set]
    @collection_set_id        int = NULL,
    @name                     sysname = NULL
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_run_collection_set
    ELSE
        BEGIN TRANSACTION

    BEGIN TRY


    -- Security check (role membership)
    EXECUTE AS CALLER;
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END
    REVERT;

    -- Verify the input parameters
    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    -- Make sure the collection set is in the right mode
    DECLARE @collection_mode smallint
    DECLARE @collection_set_uid uniqueidentifier;

    SELECT 
        @collection_set_uid = collection_set_uid,
        @collection_mode = collection_mode            
    FROM [dbo].[syscollector_collection_sets]
    WHERE collection_set_id = @collection_set_id

    IF (@collection_mode <> 1)
    BEGIN
        RAISERROR(14695, -1, -1, @name)
        RETURN(1)
    END


    -- Make sure the collector is enabled
    EXEC @retVal = [dbo].[sp_syscollector_verify_collector_state] @desired_state = 1
    IF (@retVal <> 0)
        RETURN (1)

    -- check if SQL Server Agent is enabled
    DECLARE @agent_enabled int
    SELECT @agent_enabled = CAST(value_in_use AS int) FROM sys.configurations WHERE name = N'Agent XPs'
    IF @agent_enabled <> 1
    BEGIN
        RAISERROR(14688, -1, -1)
        RETURN (1)
    END

    -- check if MDW is setup
    DECLARE @instance_name sysname
    SELECT @instance_name = CONVERT(sysname,parameter_value)
    FROM [msdb].[dbo].[syscollector_config_store_internal]
    WHERE parameter_name = N'MDWInstance'
    IF (@instance_name IS NULL)
    BEGIN
        RAISERROR(14689, -1, -1)
        RETURN (1)
    END    
    DECLARE @database_name sysname
    SELECT @database_name = CONVERT(sysname,parameter_value)
    FROM [msdb].[dbo].[syscollector_config_store_internal]
    WHERE parameter_name = N'MDWDatabase'
    IF (@database_name IS NULL)
    BEGIN
        RAISERROR(14689, -1, -1)
        RETURN (1)
    END

    -- Make sure the jobs are created for the collection set
    -- Verify the input parameters
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    -- Check if the collection set does not have any collection items
    IF NOT EXISTS(
        SELECT i.collection_item_id 
        FROM [dbo].[syscollector_collection_sets] AS s
        INNER JOIN [dbo].[syscollector_collection_items] AS i
            ON(s.collection_set_id = i.collection_set_id)
        WHERE s.collection_set_id = @collection_set_id
    )
    BEGIN
        RAISERROR(14685, 10, -1, @name) -- Raise a warning message
        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END

    DECLARE @proxy_id int;
    DECLARE @collection_job_id uniqueidentifier
    DECLARE @upload_job_id uniqueidentifier

    SELECT @collection_job_id = collection_job_id, 
           @upload_job_id = upload_job_id, 
           @proxy_id = proxy_id
    FROM [dbo].[syscollector_collection_sets_internal]
    WHERE collection_set_id = @collection_set_id;

    -- Check if the set does not have a proxy
    IF (@proxy_id IS NULL)
    BEGIN
        -- to start a collection set without a proxy, the caller has to be a sysadmin
        EXECUTE AS CALLER;
            IF (NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
            BEGIN
                REVERT;
                RAISERROR(14692, -1, -1, @name)
                RETURN (1)
            END
        REVERT;
    END

    -- Check if we have jobs created, and if not, create them
    DECLARE @jobs_just_created bit
    SET @jobs_just_created = 0  -- False until further notice
    IF (@collection_job_id IS NULL AND @upload_job_id IS NULL)
    BEGIN
        DECLARE @schedule_id int;
        DECLARE @schedule_uid uniqueidentifier;

        SELECT 
            @schedule_uid = schedule_uid
        FROM [dbo].[syscollector_collection_sets_internal]
        WHERE collection_set_id = @collection_set_id;
        
        IF (@schedule_uid IS NOT NULL)
        BEGIN
            SELECT @schedule_id = schedule_id FROM sysschedules_localserver_view WHERE @schedule_uid = schedule_uid
        END

        -- Sanity check
        -- Make sure the proxy and schedule are still there, someone could have
        -- remove them between when the collection set was created and now.
        IF (@proxy_id IS NOT NULL)
        BEGIN
            DECLARE @proxy_name sysname
            
            -- this will throw an error of proxy_id does not exist
            EXEC @retVal = msdb.dbo.sp_verify_proxy_identifiers '@proxy_name', '@proxy_id', @proxy_name OUTPUT, @proxy_id OUTPUT
            IF (@retVal <> 0)
                RETURN (0)
        END

        IF (@schedule_uid IS NOT NULL)
        BEGIN
            EXEC @retVal = sp_verify_schedule_identifiers  @name_of_name_parameter = '@schedule_name',
                                                           @name_of_id_parameter   = '@schedule_id',
                                                           @schedule_name          = NULL,
                                                           @schedule_id            = @schedule_id,
                                                           @owner_sid              = NULL,
                                                           @orig_server_id         = NULL 
            IF (@retVal <> 0)
                RETURN (1)
        END

        -- Go add the jobs
        EXEC [dbo].[sp_syscollector_create_jobs]
            @collection_set_id    = @collection_set_id,
            @collection_set_uid = @collection_set_uid,
            @collection_set_name = @name,
            @proxy_id            = @proxy_id,
            @schedule_id        = @schedule_id,
            @collection_mode    = @collection_mode,
            @collection_job_id    = @collection_job_id OUTPUT,
            @upload_job_id        = @upload_job_id OUTPUT

        -- Finally, update the collection_sets table
        UPDATE [dbo].[syscollector_collection_sets_internal]
        SET
            upload_job_id        = @upload_job_id,
            collection_job_id    = @collection_job_id
        WHERE @collection_set_id = collection_set_id

        SET @jobs_just_created = 1  -- Record the fact that we have just created the job here
    END

    IF (@jobs_just_created = 1)
    BEGIN  -- We created the jobs here in this transaction, post a request for agent to start as soon as we commit
        EXEC @retVal = sp_start_job @job_id = @upload_job_id
        IF (@retVal <> 0)
            RETURN (1)
    END
    ELSE   
    BEGIN
        -- The jobs were created previously, we need to guard against it already executing by the schedule
        -- So, check if the job is currently running before asking agent to start it
        DECLARE @is_upload_job_running INT
        EXECUTE [dbo].[sp_syscollector_get_collection_set_execution_status]
            @collection_set_id = @collection_set_id,
            @is_upload_running = @is_upload_job_running OUTPUT

        IF (@is_upload_job_running = 0)
        BEGIN
            -- Job is not running, we can trigger it now
            -- We run only one job because for this (non-cached) mode there is only one job. The same id is stored
            -- as collection and upload job id
            EXEC @retVal = sp_start_job @job_id = @upload_job_id
            IF (@retVal <> 0)
                RETURN (1)
        END
    END

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)

    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_run_collection_set

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)        
    END CATCH
END
GO


PRINT 'Creating trigger [dbo].[syscollector_collection_set_is_running_update_trigger] on [dbo].[syscollector_collection_sets_internal]'
GO
IF NOT OBJECT_ID('dbo.syscollector_collection_set_is_running_update_trigger', 'TR') IS NULL
    DROP TRIGGER [dbo].[syscollector_collection_set_is_running_update_trigger]
GO

CREATE TRIGGER [dbo].[syscollector_collection_set_is_running_update_trigger] on [dbo].[syscollector_collection_sets_internal]
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
FOR UPDATE
AS
BEGIN
    DECLARE @collection_set_id INT
    DECLARE @is_running BIT
    DECLARE @old_is_running BIT
    DECLARE @collection_mode SMALLINT

    IF (NOT UPDATE (is_running))
       RETURN

    DECLARE @collector_enabled int
    SET @collector_enabled = CONVERT(int, (SELECT parameter_value FROM dbo.syscollector_config_store_internal
                            WHERE parameter_name = 'CollectorEnabled'))
    IF @collector_enabled = 0
    BEGIN
        -- flipping the is_running bit has no effect when the collector is disabled
        RAISERROR(14682, 10, -1) -- severity 10 emits a warning
    END
    ELSE
    BEGIN
        DECLARE inserted_cursor CURSOR LOCAL FOR 
            SELECT collection_set_id, is_running, collection_mode
            FROM inserted 

        OPEN inserted_cursor
        FETCH inserted_cursor INTO @collection_set_id, @is_running, @collection_mode
        
        WHILE @@FETCH_STATUS = 0 
        BEGIN
            SELECT @old_is_running = is_running FROM deleted WHERE collection_set_id = @collection_set_id

            -- If there is a change in the state, handle accordingly
            IF (@old_is_running <> @is_running)
            BEGIN
                IF (@is_running = 0)
                BEGIN
                    EXEC dbo.sp_syscollector_stop_collection_set_jobs @collection_set_id = @collection_set_id
                END
                ELSE IF (@is_running = 1)
                BEGIN              
                    EXEC dbo.sp_syscollector_start_collection_set_jobs @collection_set_id = @collection_set_id
                END
            END

            FETCH inserted_cursor INTO @collection_set_id, @is_running, @collection_mode
        END    

        CLOSE inserted_cursor
        DEALLOCATE inserted_cursor
    END
END
GO

PRINT ''
PRINT 'Creating stored procedure syscollector_stop_collection_set_jobs...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_stop_collection_set_jobs', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_stop_collection_set_jobs]
GO

CREATE PROCEDURE [dbo].[sp_syscollector_stop_collection_set_jobs]
    @collection_set_id    int
AS
BEGIN
    SET NOCOUNT ON

    -- Collection set stopped. Make sure the following happens:
    -- 1. Detach upload schedule
    -- 2. Collection job is stopped
    -- 3. Upload job is kicked once if it is not running now
    -- 4. Collection and upload jobs are disabled
    -- 5. Attach upload schedule
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_stop_collection_set_jobs
    ELSE
        BEGIN TRANSACTION
    
    BEGIN TRY
        DECLARE @collection_job_id    uniqueidentifier
        DECLARE @upload_job_id        uniqueidentifier
        DECLARE @schedule_uid        uniqueidentifier
        DECLARE @collection_mode    smallint

        SELECT    @collection_job_id = collection_job_id, 
                @upload_job_id = upload_job_id, 
                @collection_mode = collection_mode, 
                @schedule_uid = schedule_uid
        FROM dbo.syscollector_collection_sets
        WHERE collection_set_id = @collection_set_id

        DECLARE @schedule_id int
        IF (@collection_mode != 1)  -- detach schedule for continuous and snapshot modes
        BEGIN
            SELECT @schedule_id = schedule_id from sysschedules_localserver_view WHERE @schedule_uid = schedule_uid
            IF (@schedule_id IS NULL)
            BEGIN
                DECLARE @schedule_uid_as_char VARCHAR(36)
                SELECT @schedule_uid_as_char = CONVERT(VARCHAR(36), @schedule_uid)
                RAISERROR(14262, -1, -1, '@schedule_uid', @schedule_uid_as_char)
                RETURN (1)
            END

            -- Detach schedule
            EXEC dbo.sp_detach_schedule
                @job_id            = @upload_job_id,
                @schedule_id    = @schedule_id,
                @delete_unused_schedule = 0    -- do not delete schedule, might need to attach it back again
        END

        DECLARE @is_upload_job_running INT
        EXECUTE [dbo].[sp_syscollector_get_collection_set_execution_status]
                @collection_set_id = @collection_set_id,
                @is_upload_running = @is_upload_job_running OUTPUT

        -- Upload job (needs to be kicked off for continuous collection mode)
        IF (@is_upload_job_running = 0            -- If the upload job is not already in progress
            AND @collection_mode = 0)           -- don't do it for adhoc or snapshot, they will handle it on their own
        BEGIN
            EXEC sp_start_job @job_id = @upload_job_id, @error_flag = 0
        END

        -- Disable both jobs
        EXEC sp_update_job @job_id = @collection_job_id, @enabled = 0
        EXEC sp_update_job @job_id = @upload_job_id, @enabled = 0

        IF (@collection_mode != 1)    -- attach schedule for continuous and snapshot modes
        BEGIN
            -- Attach schedule
            EXEC dbo.sp_attach_schedule
                @job_id            = @upload_job_id,
                @schedule_id    = @schedule_id
        END

        -- Log the stop of the collection set
        EXEC sp_syscollector_event_oncollectionstop @collection_set_id = @collection_set_id

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_stop_collection_set_jobs

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO


PRINT ''
PRINT 'Creating stored procedure syscollector_start_collection_set_jobs...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_start_collection_set_jobs', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_start_collection_set_jobs]
GO

CREATE PROCEDURE [dbo].[sp_syscollector_start_collection_set_jobs]
    @collection_set_id    int
AS
BEGIN
    SET NOCOUNT ON

    -- Collection set started. Make sure the following happens:
    -- 1. Collection and upload jobs are enabled
    -- 2. Collection job is started if it is defined as running continously

    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_start_collection_set_jobs
    ELSE
        BEGIN TRANSACTION

    BEGIN TRY
        
        -- Log the start of the collection set
        DECLARE @log_id bigint
        EXEC sp_syscollector_event_oncollectionstart @collection_set_id = @collection_set_id, @log_id = @log_id OUTPUT

        -- Enable both jobs
        DECLARE @collection_job_id    uniqueidentifier
        DECLARE @upload_job_id        uniqueidentifier
        DECLARE @collection_mode    smallint

        SELECT    @collection_job_id = collection_job_id,
                @upload_job_id = upload_job_id,
                @collection_mode = collection_mode
        FROM dbo.syscollector_collection_sets
        WHERE collection_set_id = @collection_set_id

        EXEC sp_update_job @job_id = @collection_job_id, @enabled = 1
        EXEC sp_update_job @job_id = @upload_job_id, @enabled = 1

        -- Start the collection job if you are in ad hoc or continuous modes
        IF (@collection_mode = 1 OR @collection_mode = 0)
        BEGIN
            EXEC sp_start_job @job_id = @collection_job_id, @error_flag = 0
        END

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_start_collection_set_jobs

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO


---------------------------------------------------------------
-- Collection Set execution log                                        
---------------------------------------------------------------

IF (OBJECT_ID('dbo.syscollector_execution_log_internal', 'U') IS NULL)
BEGIN
    PRINT ''
    PRINT 'Creating table syscollector_execution_log_internal...'

    CREATE TABLE [dbo].[syscollector_execution_log_internal]
    (
        log_id                  bigint          IDENTITY(1,1) NOT NULL,     -- Unique log entry id. Each execution log gets a new one
        parent_log_id           bigint          NULL,                       -- Id of the parent execution context. NULL for the root node. 
        collection_set_id       int             NOT NULL,                   -- Id of the collection set that owns this entry
        collection_item_id      int             NULL,                       -- Collection item id
        start_time              datetime        NOT NULL,                   -- Collection set or package execution start time
        last_iteration_time     datetime        NULL,                       -- For continously running packages, last time an interation has been started
        finish_time             datetime        NULL,                       -- Collection set or package execution end time
        runtime_execution_mode  smallint        NULL,                       -- 0 - Collection, 1 - Upload
        status                  smallint        NOT NULL,                   -- 0 - Running, 1 - Finished, 2 - Error, 3 - Warning
        operator                nvarchar(128)   NOT NULL,                   -- Name of the user running the collection set or package
        package_id              uniqueidentifier NULL,                      -- Id of a package, NULL for collection sets
        package_execution_id    uniqueidentifier NULL,                      -- Execution Id generated by SSIS for each package execution. Use to join events from sysssislog. NULL for collection sets
        failure_message         nvarchar(2048)   NULL                       -- Message that indicates package failure. NULL if no failure

        CONSTRAINT [PK_syscollector_execution_log] PRIMARY KEY CLUSTERED (log_id ASC),
        CONSTRAINT [FK_syscollector_execution_log_collection_set_id] FOREIGN KEY (collection_set_id) 
            REFERENCES [dbo].[syscollector_collection_sets_internal] (collection_set_id),
    )
END
go

PRINT ''
PRINT 'Creating view syscollector_execution_log...'

IF (NOT OBJECT_ID('dbo.syscollector_execution_log', 'V') IS NULL)
    DROP VIEW [dbo].[syscollector_execution_log]
go

CREATE VIEW [dbo].[syscollector_execution_log] AS
    SELECT 
        log_id, 
        ISNULL(parent_log_id, 0) as parent_log_id, 
        collection_set_id, 
        collection_item_id,
        start_time,
        last_iteration_time,
        finish_time,
        runtime_execution_mode,
        [status],
        operator,
        package_id,
        msdb.dbo.fn_syscollector_get_package_path(package_id) as package_name,
        package_execution_id,
        failure_message
    FROM dbo.syscollector_execution_log_internal;
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_delete_jobs]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_delete_jobs]...'
    DROP PROCEDURE [dbo].[sp_syscollector_delete_jobs]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_delete_jobs]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_delete_jobs]
    @collection_job_id        uniqueidentifier,
    @upload_job_id            uniqueidentifier,
    @schedule_id            int = NULL,
    @collection_mode        smallint
AS
BEGIN
    -- delete the jobs corresponding to the collection set
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_syscollector_delete_jobs
    ELSE
        BEGIN TRANSACTION
    
    BEGIN TRY

    IF (@collection_mode = 1) -- non-cached mode
    BEGIN
        IF (@upload_job_id IS NOT NULL)
        BEGIN
            -- note, upload job id = collection job id in this mode
            IF (@schedule_id IS NOT NULL)
            BEGIN
                EXEC dbo.sp_detach_schedule
                    @job_id            = @upload_job_id, 
                    @schedule_id    = @schedule_id,
                    @delete_unused_schedule = 0
            END

            EXEC dbo.sp_delete_jobserver
                @job_id            = @upload_job_id,
                @server_name    = N'(local)'

            EXEC dbo.sp_delete_job 
                @job_id            = @upload_job_id
        END
    END
    ELSE -- cached mode
    BEGIN
        -- detach schedules, delete job servers, then delete jobs
        IF (@upload_job_id IS NOT NULL)
        BEGIN
            EXEC dbo.sp_detach_schedule
                @job_id            = @upload_job_id, 
                @schedule_id    = @schedule_id,
                @delete_unused_schedule = 0

            EXEC dbo.sp_delete_jobserver
                @job_id            = @upload_job_id,
                @server_name    = N'(local)'

            EXEC dbo.sp_delete_job 
                @job_id            = @upload_job_id
        END

        IF (@collection_job_id IS NOT NULL)
        BEGIN
            EXEC dbo.sp_detach_schedule
                @job_id            = @collection_job_id, 
                @schedule_name    = N'RunAsSQLAgentServiceStartSchedule',
                @delete_unused_schedule = 0

            EXEC dbo.sp_delete_jobserver
                @job_id            = @collection_job_id,
                @server_name    = N'(local)'

            EXEC dbo.sp_delete_job 
                @job_id            = @collection_job_id
        END
    END

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_syscollector_delete_jobs

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)
    END CATCH
END
GO


---------------------------------------------------------------
-- Collection Set execution stats                                      
---------------------------------------------------------------

IF (OBJECT_ID('dbo.syscollector_execution_stats_internal', 'U') IS NULL)
BEGIN
    PRINT ''
    PRINT 'Creating table syscollector_execution_stats_internal...'

    CREATE TABLE [dbo].[syscollector_execution_stats_internal]
    (
        log_id                  bigint          NOT NULL,                   -- Log_id of the package that inserts the row
        task_name               nvarchar(128)   NOT NULL,                   -- Name of the task/component in the package that reports the execution stats
        execution_row_count_in  int             NULL,                       -- Number of rows that entered the data flow from its source transformations
        execution_row_count_out int             NULL,                       -- Number of rows that exited the data flow into its destination transformations
        execution_row_count_errors int          NULL,                       -- Number of rows that were re-directed to an error output due to processing errors
        execution_time_ms       int             NULL,                       -- Execution time of the data flow
        log_time                datetime        NOT NULL                    -- Date and time when this entry was logged

        CONSTRAINT [PK_syscollector_execution_stats] PRIMARY KEY CLUSTERED (log_id ASC, task_name ASC, log_time DESC),
        CONSTRAINT [FK_syscollector_execution_stats_log_id] FOREIGN KEY (log_id) REFERENCES [dbo].[syscollector_execution_log_internal] (log_id) ON DELETE CASCADE
    )
END
go

PRINT ''
PRINT 'Creating view syscollector_execution_stats...'

IF (NOT OBJECT_ID('dbo.syscollector_execution_stats', 'V') IS NULL)
    DROP VIEW [dbo].[syscollector_execution_stats]
go

CREATE VIEW [dbo].[syscollector_execution_stats] AS
    SELECT
        log_id,
        task_name,
        execution_row_count_in,
        execution_row_count_out,
        execution_row_count_errors,
        execution_time_ms,
        log_time
    FROM dbo.syscollector_execution_stats_internal
go

---------------------------------------------------------------
-- Logging stored procedures  
---------------------------------------------------------------

PRINT ''
PRINT 'Creating stored procedure sp_syscollector_verify_event_log_id...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_verify_event_log_id', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_verify_event_log_id]
go

CREATE PROCEDURE [dbo].[sp_syscollector_verify_event_log_id]
    @log_id bigint,
    @allow_collection_set_id bit = 0
AS
BEGIN
    SET NOCOUNT ON

    DECLARE @log_id_as_char VARCHAR(36)

    IF (@log_id IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@log_id')
        RETURN (1)
    END
    ELSE IF @allow_collection_set_id = 0
    BEGIN
        IF (NOT EXISTS (SELECT log_id FROM dbo.syscollector_execution_log WHERE log_id = @log_id AND package_id IS NOT NULL))
        BEGIN
            SELECT @log_id_as_char = CONVERT(VARCHAR(36), @log_id)

            RAISERROR(14262, -1, -1, '@log_id', @log_id_as_char)
            RETURN (1)
        END
    END
    ELSE
    BEGIN
        IF (NOT EXISTS (SELECT log_id FROM dbo.syscollector_execution_log WHERE log_id = @log_id))
        BEGIN
            SELECT @log_id_as_char = CONVERT(VARCHAR(36), @log_id)

            RAISERROR(14262, -1, -1, '@log_id', @log_id_as_char)
            RETURN (1)
        END
    END

    RETURN (0)
END
go

PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_oncollectionstart...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_oncollectionstart', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_oncollectionstart]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_oncollectionstart]
    @collection_set_id int,
    @operator nvarchar(128) = NULL,
    @log_id bigint OUTPUT
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END

    -- Verify parameters
    --

    -- Check the collection_set_id
    IF (@collection_set_id IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@collection_set_id')
        RETURN (1)
    END
    ELSE IF (NOT EXISTS (SELECT collection_set_id FROM dbo.syscollector_collection_sets WHERE collection_set_id = @collection_set_id))
    BEGIN
        DECLARE @collection_set_id_as_char VARCHAR(36)
        SELECT @collection_set_id_as_char = CONVERT(VARCHAR(36), @collection_set_id)

        RAISERROR(14262, -1, -1, '@collection_set_id', @collection_set_id_as_char)
        RETURN (1)
    END


    -- Default operator to currently logged in user
    SET @operator = NULLIF(LTRIM(RTRIM(@operator)), '')
    SET @operator = ISNULL(@operator, suser_sname())

    -- Insert the log record
    --
    INSERT INTO dbo.syscollector_execution_log_internal (
        parent_log_id, 
        collection_set_id, 
        start_time,
        last_iteration_time,
        finish_time,
        runtime_execution_mode,
        [status],
        operator,
        package_id,
        package_execution_id,
        failure_message
    ) VALUES (
        NULL,
        @collection_set_id,
        GETDATE(),
        NULL,
        NULL,
        NULL,
        0, -- Running
        @operator,
        NULL,
        NULL,
        NULL
    )

    SET @log_id = SCOPE_IDENTITY()                
    
    RETURN (0)
END
go

PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_oncollectionstop...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_oncollectionstop', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_oncollectionstop]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_oncollectionstop]
    @collection_set_id int
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    -- Check the collection_set_id
    IF (@collection_set_id IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@collection_set_id')
        RETURN (1)
    END
    ELSE IF (NOT EXISTS (SELECT collection_set_id FROM dbo.syscollector_collection_sets WHERE collection_set_id = @collection_set_id))
    BEGIN
        DECLARE @collection_set_id_as_char VARCHAR(36)
        SELECT @collection_set_id_as_char = CONVERT(VARCHAR(36), @collection_set_id)

        RAISERROR(14262, -1, -1, '@collection_set_id', @collection_set_id_as_char)
        RETURN (1)
    END

    -- Find the log_id
    -- It will be a log entry for the same collection set, with no parent and not finished
    DECLARE @log_id bigint
    SELECT TOP 1 @log_id = log_id FROM dbo.syscollector_execution_log_internal 
        WHERE collection_set_id = @collection_set_id 
        AND parent_log_id IS NULL
        AND finish_time IS NULL
        ORDER BY start_time DESC

    IF (@log_id IS NULL)
    BEGIN
        -- Raise a warning message
        RAISERROR(14606, 9, -1, '@log_id')
    END
    ELSE
    BEGIN
        -- Mark the log as finished
        UPDATE dbo.syscollector_execution_log_internal SET
            finish_time = GETDATE(),
            [status] = CASE
                WHEN [status] = 0 THEN 1 -- Mark complete if it was running
                ELSE [status]            -- Leave the error status unchanged
            END
        WHERE log_id = @log_id
    END

    RETURN (0)
END
go


PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_oncollectionbegin...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_oncollectionbegin', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_oncollectionbegin]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_oncollectionbegin]
    @collection_set_id int,
    @mode smallint = NULL,
    @operator nvarchar(128) = NULL,
    @log_id bigint OUTPUT
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    -- Verify parameters
    --

    -- Check the collection_set_id
    IF (@collection_set_id IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@collection_set_id')
        RETURN (1)
    END
    ELSE IF (NOT EXISTS (SELECT collection_set_id FROM dbo.syscollector_collection_sets WHERE collection_set_id = @collection_set_id))
    BEGIN
        DECLARE @collection_set_id_as_char VARCHAR(36)
        SELECT @collection_set_id_as_char = CONVERT(VARCHAR(36), @collection_set_id)

        RAISERROR(14262, -1, -1, '@collection_set_id', @collection_set_id_as_char)
        RETURN (1)
    END


    -- Default operator to currently logged in user
    SET @operator = NULLIF(LTRIM(RTRIM(@operator)), '')
    SET @operator = ISNULL(@operator, suser_sname())

    -- Default mode to Collection
    SET @mode = ISNULL(@mode, 0)

    -- Find the parent log id.
    -- It will be a log entry for the same collection set, with no parent and not finished
    DECLARE @parent_log_id bigint
    SELECT TOP 1 @parent_log_id = log_id FROM dbo.syscollector_execution_log_internal 
        WHERE collection_set_id = @collection_set_id 
        AND parent_log_id IS NULL
        AND (@mode = 1 OR finish_time IS NULL)
        ORDER BY start_time DESC

    -- Insert the log record
    --
    INSERT INTO dbo.syscollector_execution_log_internal (
        parent_log_id, 
        collection_set_id, 
        collection_item_id,
        start_time,
        last_iteration_time,
        finish_time,
        runtime_execution_mode,
        [status],
        operator,
        package_id,
        package_execution_id,
        failure_message
    ) VALUES (
        @parent_log_id,
        @collection_set_id,
        NULL,
        GETDATE(),
        NULL,
        NULL,
        @mode,
        0, -- Running
        @operator,
        NULL,
        NULL,
        NULL
    )

    SET @log_id = SCOPE_IDENTITY()                
    
    RETURN (0)
END
go

PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_oncollectionend...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_oncollectionend', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_oncollectionend]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_oncollectionend]
    @log_id bigint
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    -- Check the log_id
    DECLARE @retVal INT
    EXEC @retVal = dbo.sp_syscollector_verify_event_log_id @log_id, 1
    IF (@retVal <> 0)
        RETURN (@retVal)

    -- Mark the log as finished
    UPDATE dbo.syscollector_execution_log_internal SET
        finish_time = GETDATE(),
        [status] = CASE
            WHEN [status] = 0 THEN 1 -- Mark complete if it was running
            ELSE [status]            -- Leave the error status unchanged
        END
    WHERE log_id = @log_id

    RETURN (0)
END
go

PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_onpackagebegin...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_onpackagebegin', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_onpackagebegin]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_onpackagebegin]
    @parent_log_id bigint,
    @package_id uniqueidentifier,
    @package_execution_id uniqueidentifier,
    @collection_item_id int = NULL,
    @mode smallint = NULL,
    @operator nvarchar(128) = NULL,
    @log_id bigint OUTPUT
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    -- Verify parameters
    --

    -- Check the @parent_log_id
    IF (@parent_log_id IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@parent_log_id')
        RETURN (1)
    END
    ELSE IF (NOT EXISTS (SELECT log_id FROM dbo.syscollector_execution_log WHERE log_id = @parent_log_id))
    BEGIN
        DECLARE @parent_log_id_as_char VARCHAR(36)
        SELECT @parent_log_id_as_char = CONVERT(VARCHAR(36), @parent_log_id)

        RAISERROR(14262, -1, -1, '@parent_log_id', @parent_log_id_as_char)
        RETURN (1)
    END

    -- Check the @package_id
    IF (@package_id IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@package_id')
        RETURN (1)
    END
    -- The 84CEC861... package is an id of our special Master package that is allowed to start 
    -- the log without being saved to sysssispackages
    ELSE IF (@package_id != N'84CEC861-D619-433D-86FB-0BB851AF454A' AND NOT EXISTS (SELECT id FROM dbo.sysssispackages WHERE id = @package_id))
    BEGIN
        DECLARE @package_id_as_char VARCHAR(50)
        SELECT @package_id_as_char = CONVERT(VARCHAR(50), @package_id)

        RAISERROR(14262, -1, -1, '@package_id', @package_id_as_char)
        RETURN (1)
    END

    -- Default operator to currently logged in user
    SET @operator = NULLIF(LTRIM(RTRIM(@operator)), '')
    SET @operator = ISNULL(@operator, suser_sname())

    -- Default mode to Collection
    SET @mode = ISNULL(@mode, 0)

    -- Find out the collection_set_id from the parent
    DECLARE @collection_set_id INT
    SELECT @collection_set_id = collection_set_id FROM dbo.syscollector_execution_log WHERE log_id = @parent_log_id

    -- Check the @package_execution_id
    IF (@package_execution_id IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@package_execution_id')
        RETURN (1)
    END
    

    -- Insert the log record
    --
    INSERT INTO dbo.syscollector_execution_log_internal (
        parent_log_id, 
        collection_set_id, 
        collection_item_id,
        start_time,
        last_iteration_time,
        finish_time,
        runtime_execution_mode,
        [status],
        operator,
        package_id,
        package_execution_id,
        failure_message
    ) VALUES (
        @parent_log_id,
        @collection_set_id,
        @collection_item_id,        
        GETDATE(),
        NULL,
        NULL,
        @mode,
        0, -- Running
        @operator,
        @package_id,
        @package_execution_id,        
        NULL
    )

    SET @log_id = SCOPE_IDENTITY()                

    RETURN (0)
END
go


PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_onpackageend...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_onpackageend', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_onpackageend]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_onpackageend]
    @log_id bigint
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    -- Check the log_id
    DECLARE @retVal INT
    EXEC @retVal = dbo.sp_syscollector_verify_event_log_id @log_id
    IF (@retVal <> 0)
        RETURN (@retVal)

    -- Mark the log as finished
    UPDATE dbo.syscollector_execution_log_internal SET
        finish_time = GETDATE(),
        [status] = CASE
            WHEN [status] = 0 THEN 1 -- Mark complete if it was running
            ELSE [status]            -- Leave the error status unchanged
        END
    WHERE log_id = @log_id

    DECLARE @runtime_execution_mode smallint
    DECLARE @status smallint
    SELECT @status = [status], @runtime_execution_mode = runtime_execution_mode
    FROM dbo.syscollector_execution_log_internal
    WHERE log_id = @log_id

    -- status was successful and this is logged by an upload package
    IF @status = 1 AND @runtime_execution_mode = 1
    BEGIN
        -- if the package ended succesfully, update the top most log to warning if it had failure
        -- this is because if there were a previous upload failure but the latest upload were successful, 
        -- we want indicated a warning rather than a failure throughout the lifetime of this collection set
        DECLARE @parent_log_id BIGINT
        SELECT @parent_log_id = parent_log_id FROM dbo.syscollector_execution_log_internal WHERE log_id = @log_id;
        WHILE @parent_log_id IS NOT NULL
        BEGIN
            -- get the next parent
            SET @log_id = @parent_log_id
            SELECT @parent_log_id = parent_log_id FROM dbo.syscollector_execution_log_internal WHERE log_id = @log_id;
        END

        UPDATE dbo.syscollector_execution_log_internal SET
            [status] = CASE
                WHEN [status] = 2 THEN 3 -- Mark warning if it indicated a failure
                ELSE [status]            -- Leave the original status unchanged
            END
        WHERE
            log_id = @log_id
    END

    RETURN (0)
END
go


PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_onpackageupdate...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_onpackageupdate', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_onpackageupdate]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_onpackageupdate]
    @log_id bigint
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    -- Check the log_id
    DECLARE @retVal INT
    EXEC @retVal = dbo.sp_syscollector_verify_event_log_id @log_id
    IF (@retVal <> 0)
        RETURN (@retVal)

    -- Update the log
    UPDATE dbo.syscollector_execution_log_internal SET
        last_iteration_time = GETDATE()
    WHERE log_id = @log_id

    RETURN (0)
END
go


PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_onerror...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_onerror', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_onerror]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_onerror]
    @log_id bigint,
    @message nvarchar(2048) = NULL
AS
BEGIN
    SET NOCOUNT ON
    
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_event_onerror
    ELSE
        BEGIN TRANSACTION
    
    BEGIN TRY
    -- Check the log_id
    -- If @message is passed, we can allow to enter the error for a collection set
    -- otherwise we will rely on the entries in sysssislog table to get the error message.
    DECLARE @retVal INT
    IF (@message IS NULL)
    BEGIN
        EXEC @retVal = dbo.sp_syscollector_verify_event_log_id @log_id, 0
    END
    ELSE
    BEGIN
        EXEC @retVal = dbo.sp_syscollector_verify_event_log_id @log_id, 1
    END
    IF (@retVal <> 0)
        RETURN (@retVal)


    DECLARE
         @failure_message   NVARCHAR(2048)
        ,@execution_id        UNIQUEIDENTIFIER

    IF @message IS NULL 
    BEGIN
        -- If no message is provided, find the last task that has failed
        -- for this package in the sysssislog table.
        -- Store the message as the failure_message for our package log.
        SELECT 
            @execution_id = package_execution_id
        FROM dbo.syscollector_execution_log
        WHERE log_id = @log_id

        SELECT TOP 1 
            @failure_message = [message]
        FROM dbo.sysssislog
        WHERE executionid = @execution_id
            AND (UPPER([event] COLLATE SQL_Latin1_General_CP1_CS_AS) = 'ONERROR')
        ORDER BY endtime DESC
    END 
    ELSE 
    BEGIN
        -- Otherwise use the provided message
        SET @failure_message = @message
    END

    -- Update the execution log
    UPDATE dbo.syscollector_execution_log_internal SET
         [status] = 2                    -- Mark as Failed
        ,failure_message = @failure_message
    WHERE
        log_id = @log_id

    -- Update all parent logs with the failure status
    SELECT @log_id = parent_log_id FROM dbo.syscollector_execution_log_internal WHERE log_id = @log_id;
    WHILE @log_id IS NOT NULL
    BEGIN
        UPDATE dbo.syscollector_execution_log_internal SET
            [status] = 2                    -- Mark as Failed
        WHERE
            log_id = @log_id;

        -- get the next parent
        SELECT @log_id = parent_log_id FROM dbo.syscollector_execution_log_internal WHERE log_id = @log_id;
    END

    IF (@TranCounter = 0)
        COMMIT TRANSACTION
    RETURN (0)

    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_event_onerror

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)    
    END CATCH
END
go
  
  
PRINT ''
PRINT 'Creating stored procedure sp_syscollector_event_onstatsupdate...'

IF (NOT OBJECT_ID('dbo.sp_syscollector_event_onstatsupdate', 'P') IS NULL)
    DROP PROCEDURE [dbo].[sp_syscollector_event_onstatsupdate]
go

CREATE PROCEDURE [dbo].[sp_syscollector_event_onstatsupdate]
    @log_id bigint,
    @task_name nvarchar(128),
    @row_count_in int = NULL,
    @row_count_out int = NULL,
    @row_count_error int = NULL,
    @execution_time_ms int = NULL
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    -- Check the log_id
    DECLARE @retVal INT
    EXEC @retVal = dbo.sp_syscollector_verify_event_log_id @log_id
    IF (@retVal <> 0)
        RETURN (@retVal)
    
    -- Check task name
    IF (@task_name IS NOT NULL)
    BEGIN
        SET @task_name = NULLIF(LTRIM(RTRIM(@task_name)), N'')
    END
    IF (@task_name IS NULL)
    BEGIN
        RAISERROR(14606, -1, -1, '@task_name')
        RETURN (1)
    END
    
    -- Insert the log entry
    INSERT INTO dbo.syscollector_execution_stats_internal (
        log_id,
        task_name,
        execution_row_count_in,
        execution_row_count_out,
        execution_row_count_errors,
        execution_time_ms,
        log_time
    ) VALUES (
        @log_id,
        @task_name,
        @row_count_in,
        @row_count_out,
        @row_count_error,
        NULLIF(@execution_time_ms, 0),
        GETDATE()
    )

    RETURN (0)
END
go

---------------------------------------------------------------
-- Data Collector log viewing functions and views
---------------------------------------------------------------

-- [fn_syscollector_find_collection_set_root]
-- This function finds the first log entry for a given collection set
-- run. It retunrs log_id of that entry. 
PRINT ''
PRINT 'Creating function [dbo].[fn_syscollector_find_collection_set_root] ...'
IF (NOT OBJECT_ID(N'dbo.fn_syscollector_find_collection_set_root', 'FN') IS NULL)
  DROP FUNCTION [dbo].[fn_syscollector_find_collection_set_root]
go

CREATE FUNCTION [dbo].[fn_syscollector_find_collection_set_root]
(
    @log_id BIGINT
)
RETURNS BIGINT
WITH RETURNS NULL ON NULL INPUT
AS
BEGIN
    DECLARE @root_id BIGINT;

    -- Derive result using a CTE as the table is self-referencing
    WITH graph AS
    (
        -- select the anchor (specified) node
        SELECT log_id, parent_log_id FROM dbo.syscollector_execution_log WHERE log_id = @log_id
        UNION ALL
        -- select the parent node recursively
        SELECT node.log_id, node.parent_log_id FROM dbo.syscollector_execution_log node
        INNER JOIN graph AS leaf ON (node.log_id = leaf.parent_log_id)
    )
    SELECT @root_id = log_id FROM graph WHERE parent_log_id = 0;
    
    --Return result
    RETURN ISNULL(@root_id, @log_id)
END 
go

-- [fn_syscollector_get_execution_log_tree]
-- This function returns a set of log entries related to a single run 
-- of a collection set. The entries are ordered in a hierarchy, starting from
-- the collection set first log entry and then down through all packages 
-- that were executed as part of the collection set.
PRINT ''
PRINT 'Creating function [dbo].[fn_syscollector_get_execution_log_tree] ...'
IF (NOT OBJECT_ID(N'dbo.fn_syscollector_get_execution_log_tree', 'IF') IS NULL)
    DROP FUNCTION [dbo].[fn_syscollector_get_execution_log_tree]
go

CREATE FUNCTION [dbo].[fn_syscollector_get_execution_log_tree] 
(
     @log_id                BIGINT,
     @from_collection_set    BIT = 1
) 
RETURNS TABLE
AS
RETURN
(
    -- Derive result using a CTE as the table is self-referencing
    WITH graph AS 
    (
        -- select the anchor (specified) node
        SELECT 
            log_id,
            parent_log_id,
            collection_set_id,
            start_time,
            last_iteration_time,
            finish_time,
            runtime_execution_mode,
            operator,
            [status],
            package_id,
            package_execution_id,
            failure_message,
            0 AS depth 
        FROM dbo.syscollector_execution_log
        WHERE log_id = CASE @from_collection_set
            WHEN 1 THEN dbo.fn_syscollector_find_collection_set_root(@log_id)
            ELSE @log_id
        END 
        -- select the child nodes recursively
        UNION ALL
        SELECT 
            leaf.log_id,
            leaf.parent_log_id,
            leaf.collection_set_id,
            leaf.start_time,
            leaf.last_iteration_time,
            leaf.finish_time,
            leaf.runtime_execution_mode,
            leaf.operator,
            leaf.[status],
            leaf.package_id,
            leaf.package_execution_id,
            leaf.failure_message,
            node.depth + 1 AS depth
        FROM dbo.syscollector_execution_log AS leaf
        INNER JOIN graph AS node ON (node.log_id = leaf.parent_log_id)
    )
    SELECT 
        log_id,
        parent_log_id,
        collection_set_id,
        start_time,
        last_iteration_time,
        finish_time,
        CASE 
            WHEN finish_time IS NOT NULL THEN DATEDIFF(ss, start_time, finish_time) 
            WHEN last_iteration_time IS NOT NULL THEN DATEDIFF(ss, start_time, last_iteration_time) 
            ELSE 0
        END AS duration,
        runtime_execution_mode,
        operator,
        [status],
        package_id,
        package_execution_id,
        failure_message,
        depth 
    FROM graph
) 
go


-- [fn_syscollector_get_execution_stats]
-- This function returns stats for each execution of a package.
-- The stats should be logged for each iteration within the package.
PRINT ''
PRINT 'Creating function [dbo].[fn_syscollector_get_execution_stats] ...'
IF (NOT OBJECT_ID(N'dbo.fn_syscollector_get_execution_stats', 'IF') IS NULL)
    DROP FUNCTION [dbo].[fn_syscollector_get_execution_stats]
go

CREATE FUNCTION [dbo].[fn_syscollector_get_execution_stats] 
(
     @log_id                BIGINT
) 
RETURNS TABLE
AS
RETURN
(
    SELECT 
        log_id,
        task_name,
        AVG(execution_row_count_in) AS avg_row_count_in,
        MIN(execution_row_count_in) AS min_row_count_in,
        MAX(execution_row_count_in) AS max_row_count_in,
        AVG(execution_row_count_out) AS avg_row_count_out,
        MIN(execution_row_count_out) AS min_row_count_out,
        MAX(execution_row_count_out) AS max_row_count_out,
        AVG(execution_row_count_errors) AS avg_row_count_errors,
        MIN(execution_row_count_errors) AS min_row_count_errors,
        MAX(execution_row_count_errors) AS max_row_count_errors,
        AVG(execution_time_ms) AS avg_duration,
        MIN(execution_time_ms) AS min_duration,
        MAX(execution_time_ms) AS max_duration
    FROM dbo.syscollector_execution_stats
    WHERE log_id = @log_id
    GROUP BY log_id, task_name
)
go


-- [fn_syscollector_get_execution_details]
-- This function returns detailed log entries from SSIS log for each package
-- execution. The log id passed as input is an id of a log entry for that package
-- from syscollector_execution_log view.
PRINT ''
PRINT 'Creating function [dbo].[fn_syscollector_get_execution_details] ...'
IF (NOT OBJECT_ID(N'dbo.fn_syscollector_get_execution_details', 'IF') IS NULL)
    DROP FUNCTION [dbo].[fn_syscollector_get_execution_details]
go
CREATE FUNCTION [dbo].[fn_syscollector_get_execution_details] 
(
     @log_id                BIGINT
)
RETURNS TABLE
AS
RETURN
(
    SELECT TOP (100) PERCENT
        l.source,
        l.event,
        l.message,
        l.starttime AS start_time,
        l.endtime AS finish_time,
        l.datacode,
        l.databytes
    FROM sysssislog l
    JOIN dbo.syscollector_execution_log e ON (e.package_execution_id = l.executionid)
    WHERE e.log_id = @log_id
    ORDER BY l.starttime
)
go            


-- [syscollector_execution_log_full]
-- An expanded log view that shows the log entries in a hierarchical order.
PRINT ''
PRINT 'Creating view syscollector_execution_log_full...'
IF (NOT OBJECT_ID('dbo.syscollector_execution_log_full', 'V') IS NULL)
    DROP VIEW [dbo].[syscollector_execution_log_full]
go

CREATE VIEW [dbo].[syscollector_execution_log_full]
AS
SELECT 
        t.log_id,
        ISNULL(t.parent_log_id, 0) as parent_log_id,
        CASE 
            WHEN t.package_id IS NULL THEN SPACE(t.depth * 4) + c.name
            WHEN t.package_id = N'84CEC861-D619-433D-86FB-0BB851AF454A' THEN SPACE(t.depth * 4) + N'Master'
            ELSE SPACE(t.depth * 4) + p.name 
        END AS [name],
        t.[status],
        t.runtime_execution_mode,
        t.start_time,
        t.last_iteration_time,
        t.finish_time,
        t.duration,
        t.failure_message,
        t.operator,
        t.package_execution_id,
        t.collection_set_id
    FROM dbo.syscollector_execution_log_internal l
    CROSS APPLY dbo.fn_syscollector_get_execution_log_tree(l.log_id, 0) t
    LEFT OUTER JOIN dbo.syscollector_collection_sets c ON( c.collection_set_id = t.collection_set_id)
    LEFT OUTER JOIN dbo.sysssispackages p ON (p.id = t.package_id AND p.id != N'84CEC861-D619-433D-86FB-0BB851AF454A')
    WHERE l.parent_log_id IS NULL
go
        
---------------------------------------------------------------
-- Data Collection log clean-up
---------------------------------------------------------------

-- [sp_syscollector_delete_execution_log_tree]
-- This stored procedure removes all log entries related to a single
-- run of a collection set. It also removes corresponding log entries
-- from SSIS log tables.
PRINT ''
IF (NOT OBJECT_ID(N'dbo.sp_syscollector_delete_execution_log_tree', 'P') IS NULL)
BEGIN
  PRINT 'Dropping procedure [dbo].[sp_syscollector_delete_execution_log_tree] ...'
  DROP PROCEDURE [dbo].[sp_syscollector_delete_execution_log_tree]
END
go

PRINT 'Creating procedure [dbo].[sp_syscollector_delete_execution_log_tree] ...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_delete_execution_log_tree]
    @log_id BIGINT,
    @from_collection_set    BIT = 1
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END

    SET NOCOUNT ON;
    CREATE TABLE #log_ids (log_id BIGINT);
    
    WITH graph AS
    (
        SELECT log_id FROM dbo.syscollector_execution_log
        WHERE log_id = CASE @from_collection_set
            WHEN 1 THEN dbo.fn_syscollector_find_collection_set_root(@log_id)
            ELSE @log_id
        END
        UNION ALL
        SELECT leaf.log_id FROM dbo.syscollector_execution_log AS leaf
        INNER JOIN graph AS node ON (node.log_id = leaf.parent_log_id)
    )
    INSERT INTO #log_ids
    SELECT log_id
    FROM graph
    
    -- Delete all ssis log records pertaining to the selected logs
    DELETE FROM dbo.sysssislog
        FROM dbo.sysssislog AS s
        INNER JOIN dbo.syscollector_execution_log_internal AS l ON (l.package_execution_id = s.executionid)
        INNER JOIN #log_ids AS i ON i.log_id = l.log_id
        
    -- Then delete the actual logs
    DELETE FROM syscollector_execution_log_internal
        FROM syscollector_execution_log_internal AS l
        INNER Join #log_ids AS i ON i.log_id = l.log_id

    DROP TABLE #log_ids
    RETURN (0)
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_delete_collection_set_internal]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_delete_collection_set_internal]...'
    DROP PROCEDURE [dbo].[sp_syscollector_delete_collection_set_internal]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_delete_collection_set_internal]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_delete_collection_set_internal]
    @collection_set_id      int,
    @name                   sysname,
    @collection_job_id      uniqueidentifier,
    @upload_job_id          uniqueidentifier,
    @collection_mode        smallint
AS
BEGIN
    DECLARE @TranCounter int
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_delete_collection_set
    ELSE
        BEGIN TRANSACTION
    
    BEGIN TRY
        -- clean log before deleting collection set
        DECLARE @log_id bigint
        SET @log_id = (SELECT TOP(1) log_id  FROM dbo.syscollector_execution_log WHERE collection_set_id = @collection_set_id)
        WHILE (@log_id IS NOT NULL)
        BEGIN
            EXEC dbo.sp_syscollector_delete_execution_log_tree @log_id = @log_id
            SET @log_id = (SELECT TOP(1) log_id  FROM dbo.syscollector_execution_log WHERE collection_set_id = @collection_set_id)
        END

        DECLARE @schedule_id    int
        SELECT @schedule_id = schedule_id
        FROM dbo.syscollector_collection_sets cs JOIN sysschedules_localserver_view sv
        ON (cs.schedule_uid = sv.schedule_uid)
        WHERE collection_set_id = @collection_set_id

        DELETE [dbo].[syscollector_collection_sets_internal]
        WHERE collection_set_id = @collection_set_id

        EXEC dbo.sp_syscollector_delete_jobs 
            @collection_job_id        = @collection_job_id,
            @upload_job_id            = @upload_job_id,
            @schedule_id            = @schedule_id,
            @collection_mode        = @collection_mode

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_delete_collection_set

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        
        RETURN (1)    
    END CATCH
END
GO

-- This is a stored procedure of collection_set, but it is created here because it 
-- makes references to the collection item view, execution log, 
-- and the [sp_syscollector_delete_execution_log_tree] stored proc
IF (NOT OBJECT_ID('[dbo].[sp_syscollector_delete_collection_set]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_delete_collection_set]...'
    DROP PROCEDURE [dbo].[sp_syscollector_delete_collection_set]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_delete_collection_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_delete_collection_set]
    @collection_set_id            int = NULL,
    @name                        sysname = NULL
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    -- Security check (role membership)
    EXECUTE AS CALLER;
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN (1)
    END
    REVERT;

    DECLARE @retVal int
    EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT, @name OUTPUT
    IF (@retVal <> 0)
        RETURN (1)

    DECLARE @is_system            bit
    DECLARE @is_running            bit
    DECLARE @upload_job_id        uniqueidentifier
    DECLARE @collection_job_id    uniqueidentifier
    DECLARE @collection_mode    smallint
    SELECT    @is_running = is_running,
            @is_system = is_system,
            @upload_job_id = upload_job_id, 
            @collection_job_id = collection_job_id,
            @collection_mode = collection_mode
    FROM [dbo].[syscollector_collection_sets]
    WHERE collection_set_id = @collection_set_id

    IF (@is_system = 1)
    BEGIN
        -- cannot update, delete, or add new collection items to a system collection set
        RAISERROR(14696, -1, -1);
        RETURN (1)
    END

    IF (@is_running = 1)
    BEGIN
        EXEC @retVal = sp_syscollector_stop_collection_set @collection_set_id = @collection_set_id
        IF (@retVal <> 0)
            RETURN (1)
    END

    -- All checks are go
    -- Do the actual delete
    EXEC @retVal = sp_syscollector_delete_collection_set_internal
                        @collection_set_id = @collection_set_id, 
                        @name = @name,
                        @collection_job_id = @collection_job_id,
                        @upload_job_id = @upload_job_id,
                        @collection_mode = @collection_mode
    RETURN (0)
END
GO

IF (NOT OBJECT_ID('dbo.sp_syscollector_purge_collection_logs', 'P') IS NULL)
BEGIN
    PRINT ''
    PRINT 'Dropping stored procedure sp_syscollector_purge_collection_logs...'
    DROP PROCEDURE [dbo].[sp_syscollector_purge_collection_logs]
END
GO

-- [sp_syscollector_purge_collection_logs]
-- The sp cleans any log record with an expired finish date
-- Expiration is measured from the date provided to the sp
-- and defaults to TODAY.
-- optional parameter @delete_batch_size  - specifies max number of log records
-- to delete in single iteration
PRINT ''
PRINT 'Creating stored procedure sp_syscollector_purge_collection_logs...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_purge_collection_logs]
    @reference_date datetime = NULL,
    @delete_batch_size int = 500
AS
BEGIN
    SET NOCOUNT ON

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_proxy')
        RETURN(1) -- Failure
    END

    IF (@reference_date IS NULL)
    BEGIN
        SET @reference_date = GETDATE()
    END
    
    -- An expired log record is any record of a collection set that is older than 
    -- the reference date minus the collection set's days_until_expiration
    CREATE TABLE #purged_log_ids (log_id BIGINT, package_execution_id uniqueidentifier)
    
    -- Identify logs to purge based on following criteria
    -- a) limit max batch size 
    -- b) do not delete last log record that is a root log record for a collection set
    INSERT INTO #purged_log_ids
    SELECT TOP (@delete_batch_size) log_id, package_execution_id
    FROM syscollector_execution_log_internal as l
    INNER JOIN syscollector_collection_sets s ON l.collection_set_id = s.collection_set_id
    WHERE s.days_until_expiration > 0
    AND @reference_date >= DATEADD(DAY, s.days_until_expiration, l.finish_time)
    AND log_id NOT IN (
                        SELECT TOP 1 log_id  from syscollector_execution_log_internal 
                        WHERE parent_log_id IS NULL 
                        AND collection_set_id = l.collection_set_id
                        ORDER BY start_time DESC
                        )

    DECLARE @purge_log_count int
    SELECT @purge_log_count  = COUNT(log_id) 
    FROM  #purged_log_ids

    -- Delete all ssis log records pertaining to expired logs
    DELETE FROM dbo.sysssislog
        FROM dbo.sysssislog AS s
        INNER JOIN #purged_log_ids AS i ON i.package_execution_id = s.executionid
        
    -- Then delete the actual logs
    DELETE FROM syscollector_execution_log_internal
        FROM syscollector_execution_log_internal AS l
        INNER Join #purged_log_ids AS i ON i.log_id = l.log_id


    DROP TABLE #purged_log_ids

    -- making sure that delete # record does not exceed given delete batch size
    DECLARE @orphaned_record_cleanup_count int
    SET @orphaned_record_cleanup_count = @delete_batch_size - @purge_log_count

    -- Go for another round to cleanup the orphans
    -- Ideally, the log heirarchy guarantees that a finish time by a parent log will always
    -- be higher than the finish time of any of its descendants.
    -- The purge step however does not delete log records with a null finish time
    -- A child log can have a null finish time while its parent is closed if there is an
    -- error in execution that causes the log to stay open.
    -- If such a child log exists, its parent will be purged leaving it as an orphan
    
    -- get orphan records and all their descendants in a cursor and purge them
    DECLARE orphaned_log_cursor INSENSITIVE CURSOR FOR
            SELECT TOP (@orphaned_record_cleanup_count) log_id 
            FROM syscollector_execution_log_internal
            WHERE parent_log_id NOT IN (
                SELECT log_id FROM syscollector_execution_log_internal
            )
            FOR READ ONLY
            
    DECLARE @log_id BIGINT

    -- for every orphan, delete all its remaining tree
    -- this is supposedly a very small fraction of the entire log
    OPEN orphaned_log_cursor    
    FETCH orphaned_log_cursor INTO @log_id
    WHILE @@FETCH_STATUS = 0
    BEGIN
        EXEC sp_syscollector_delete_execution_log_tree @log_id = @log_id, @from_collection_set = 0
        FETCH orphaned_log_cursor INTO @log_id
    END
    
    CLOSE orphaned_log_cursor
    DEALLOCATE orphaned_log_cursor
END
GO


---------------------------------------------------------------
-- Start and stop Data Collector
---------------------------------------------------------------

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_enable_collector]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_enable_collector]...'
    DROP PROCEDURE [dbo].[sp_syscollector_enable_collector]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_enable_collector]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_enable_collector]
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    -- Security check (role membership)
    EXECUTE AS CALLER;
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END
    REVERT;

    -- check if SQL Server Agent is enabled
    DECLARE @agent_enabled int
    SELECT @agent_enabled = CAST(value_in_use AS int) FROM sys.configurations WHERE name = N'Agent XPs'
    IF @agent_enabled <> 1
    BEGIN
        RAISERROR(14699, -1, -1)
        RETURN (1) -- Failure
    END
    REVERT;

    BEGIN TRANSACTION

    DECLARE @was_enabled int;

    SELECT @was_enabled = ISNULL(CONVERT(int, parameter_value),0)
    FROM [dbo].[syscollector_config_store_internal]
    WHERE parameter_name = 'CollectorEnabled'

    IF (@was_enabled = 0)
    BEGIN

        UPDATE [dbo].[syscollector_config_store_internal]
        SET parameter_value = 1
        WHERE parameter_name = 'CollectorEnabled'

        DECLARE @collection_set_id int

        DECLARE collection_set_cursor CURSOR LOCAL FOR
            SELECT collection_set_id
            FROM dbo.syscollector_collection_sets
            WHERE is_running = 1

        OPEN collection_set_cursor
        FETCH collection_set_cursor INTO @collection_set_id

        WHILE @@FETCH_STATUS = 0 
        BEGIN
            EXEC dbo.sp_syscollector_start_collection_set_jobs @collection_set_id = @collection_set_id
            FETCH collection_set_cursor INTO @collection_set_id
        END

        CLOSE collection_set_cursor
        DEALLOCATE collection_set_cursor

    END

    COMMIT TRANSACTION

END
GO


IF (NOT OBJECT_ID('[dbo].[sp_syscollector_disable_collector]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_disable_collector]...'
    DROP PROCEDURE [dbo].[sp_syscollector_disable_collector]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_disable_collector]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_disable_collector]
WITH EXECUTE AS OWNER -- 'MS_DataCollectorInternalUser'
AS
BEGIN
    -- Security check (role membership)
    EXECUTE AS CALLER;
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT;
        RAISERROR(14677, -1, -1, 'dc_operator')
        RETURN(1) -- Failure
    END
    REVERT;

    BEGIN TRANSACTION

    DECLARE @was_enabled int;

    SELECT @was_enabled = ISNULL(CONVERT(int, parameter_value),0)
    FROM [dbo].[syscollector_config_store_internal]
    WHERE parameter_name = 'CollectorEnabled'

    IF (@was_enabled <> 0)
    BEGIN

        UPDATE [dbo].[syscollector_config_store_internal]
        SET parameter_value = 0
        WHERE parameter_name = 'CollectorEnabled'

        DECLARE @collection_set_id INT
        DECLARE @collection_mode SMALLINT
        DECLARE @collection_job_id UNIQUEIDENTIFIER

        DECLARE collection_set_cursor CURSOR LOCAL FOR
            SELECT collection_set_id, collection_mode, collection_job_id
            FROM dbo.syscollector_collection_sets
            WHERE is_running = 1

        OPEN collection_set_cursor
        FETCH collection_set_cursor INTO @collection_set_id, @collection_mode, @collection_job_id

        WHILE @@FETCH_STATUS = 0 
        BEGIN
            -- If this collection set is running in cached mode, and the collection job is running, we need to stop the job explicitly here
            DECLARE @is_collection_job_running INT
            EXECUTE [dbo].[sp_syscollector_get_collection_set_execution_status]
                    @collection_set_id = @collection_set_id,
                    @is_collection_running = @is_collection_job_running OUTPUT    

            IF (@is_collection_job_running = 1
                AND @collection_mode = 0)           -- Cached mode
            BEGIN
                EXEC sp_stop_job @job_id = @collection_job_id
            END

            -- Now, disable the jobs and detach them from the upload schedules
            EXEC dbo.sp_syscollector_stop_collection_set_jobs @collection_set_id = @collection_set_id
            FETCH collection_set_cursor INTO @collection_set_id, @collection_mode, @collection_job_id
        END
        CLOSE collection_set_cursor
        DEALLOCATE collection_set_cursor

    END

    COMMIT TRANSACTION

END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_get_trace_info]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_get_trace_info]'
    DROP PROCEDURE [dbo].[sp_syscollector_get_trace_info]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_get_trace_info]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_get_trace_info]
    @trace_path  nvarchar(512),
    @use_default int
AS
BEGIN
    SELECT 
        CONVERT(nvarchar(30), t.start_time, 126) as start_time,
        CASE t.status 
            WHEN 1 THEN 1 
            ELSE 0 
        END AS is_running, 
        ISNULL(t.dropped_event_count,0) as dropped_event_count,
        t.id
    FROM sys.traces t
    WHERE (@use_default=1 and t.is_default=1)
          OR (@use_default=0 AND t.path LIKE (@trace_path + N'%.trc'))
END
GO

---------------------------------------------------------------
-- Data Collector: Helper procedures
---------------------------------------------------------------

-- Procedure to retrieve a query plan from cache
IF (NOT OBJECT_ID('[dbo].[sp_syscollector_text_query_plan_lookpup]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_text_query_plan_lookpup]'
    DROP PROCEDURE [dbo].[sp_syscollector_text_query_plan_lookpup]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_text_query_plan_lookpup]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_text_query_plan_lookpup]
    @plan_handle varbinary(64),
    @statement_start_offset int,
    @statement_end_offset int
AS
BEGIN
    SET NOCOUNT ON
    SELECT    
        @plan_handle AS plan_handle,
        @statement_start_offset AS statement_start_offset,
        @statement_end_offset AS statement_end_offset,
        [dbid] AS database_id,
        [objectid] AS object_id,
        OBJECT_NAME(objectid, dbid) AS object_name,
        [query_plan] AS query_plan
    FROM    
        [sys].[dm_exec_text_query_plan](@plan_handle, @statement_start_offset, @statement_end_offset) dm
END
GO

-- Procedure to retrieve a query text from cache
IF (NOT OBJECT_ID('[dbo].[sp_syscollector_sql_text_lookup]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_sql_text_lookup]'
    DROP PROCEDURE [dbo].[sp_syscollector_sql_text_lookup]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_sql_text_lookup]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_sql_text_lookup]
    @sql_handle varbinary(64)
AS
BEGIN
    SET NOCOUNT ON
    SELECT    
        @sql_handle as sql_handle,
        dm.[dbid] AS database_id,
        dm.[objectid] AS object_id,
        OBJECT_NAME(objectid, dbid) AS object_name,
        CASE dm.[encrypted]
            WHEN 1 THEN N'Query SQL Text Encrypted'
            ELSE dm.[text]
        END AS sql_text
        FROM    
            [sys].[dm_exec_sql_text](@sql_handle) dm
END
GO

---------------------------------------------------------------
-- Install out-of-the-box objects
---------------------------------------------------------------
PRINT 'Installing out of the box Collector objects'
PRINT ''

-- We need agent XP's to be on for many parts of the coming installation script
-- Enable them here once and return them to their original state when done

DECLARE @advopt_old_value int
DECLARE @comp_old_value int
EXEC #sp_enable_component 'Agent XPs', @advopt_old_value out, @comp_old_value out

-- We need the old values to endure beyond batches
-- insert them into temp tables

IF (OBJECT_ID('tempdb..#advopt_old_value', 'U') IS NOT NULL)
BEGIN
	DROP TABLE #advopt_old_value
END

SELECT @advopt_old_value AS advopt_old_value
INTO #advopt_old_value

IF (OBJECT_ID('tempdb..#comp_old_value', 'U') IS NOT NULL)
BEGIN
	DROP TABLE #comp_old_value
END

SELECT @comp_old_value AS comp_old_value
INTO #comp_old_value
GO

-- disable the collector first
EXEC sp_syscollector_disable_collector
GO

---------------------------------------------------------------
-- Out-of-the-box SSIS folders for Data Collector
---------------------------------------------------------------

PRINT 'Creating SSIS folders...'
-- create 'Data Collector' folder under the root
IF(NOT EXISTS(SELECT * 
                FROM dbo.sysssispackagefolders
                WHERE folderid = '8877FE4B-A938-4a51-84B9-C5BDAD74B0AD'))
BEGIN
    EXEC dbo.sp_ssis_addfolder
        @parentfolderid = '00000000-0000-0000-0000-000000000000',
        @name = 'Data Collector',
        @folderid = '8877FE4B-A938-4a51-84B9-C5BDAD74B0AD'
END
GO
-- create 'Generated' folder under 'Data Collector'
IF(NOT EXISTS(SELECT * 
                FROM dbo.sysssispackagefolders
                WHERE folderid = '39163C42-602B-42C9-B4F7-1843614F9625'))
BEGIN
    EXEC dbo.sp_ssis_addfolder
        @parentfolderid = '8877FE4B-A938-4a51-84B9-C5BDAD74B0AD',
        @name = 'Generated',
        @folderid = '39163C42-602B-42c9-B4F7-1843614F9625'
END
GO

---------------------------------------------------------------
-- Loading instmdw.sql
---------------------------------------------------------------

-- a data collector table to store BLOB
IF (OBJECT_ID(N'[dbo].[syscollector_blobs_internal]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [dbo].[syscollector_blobs_internal]...'
    CREATE TABLE [dbo].[syscollector_blobs_internal] (
        parameter_name                nvarchar(128) NOT NULL,
        parameter_value               varbinary(max) NOT NULL,
        CONSTRAINT [PK_syscollector_blobs_internal_paremeter_name] PRIMARY KEY CLUSTERED (parameter_name ASC)
        )
END
GO

-- an SP to read a parameter value from the syscollector BLOB table
-- this stored procedure is called by the wizard to retrieve the instmdw.sql when setting up MDW
IF (NOT OBJECT_ID('[dbo].[sp_syscollector_get_instmdw]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_get_instmdw]'
    DROP PROCEDURE [dbo].[sp_syscollector_get_instmdw]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_get_instmdw]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_get_instmdw]
AS
BEGIN
    -- only dc_admin and dbo can setup MDW
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14712, -1, -1) WITH LOG
        RETURN(1) -- Failure
    END

    -- if the script has not been loaded, load it now
    IF (NOT EXISTS(SELECT parameter_name 
                   FROM syscollector_blobs_internal
                   WHERE parameter_name = N'InstMDWScript'))
    BEGIN
        EXECUTE sp_syscollector_upload_instmdw
    END
               
    SELECT cast(parameter_value as nvarchar(max)) 
    FROM syscollector_blobs_internal
    WHERE parameter_name = N'InstMDWScript'
END
GO

-- the script that would upload or update instmdw.sql
-- this is used when a hotfix, service pack, or upgrade is issued in instmdw.sql
-- user can specify the path to the new instmdw.sql to be installed or updated to.
IF (NOT OBJECT_ID('[dbo].[sp_syscollector_upload_instmdw]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_upload_instmdw]'
    DROP PROCEDURE [dbo].[sp_syscollector_upload_instmdw]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_upload_instmdw]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_upload_instmdw]
    @installpath              nvarchar(2048) = NULL
AS
BEGIN
    -- only dc_admin and dbo can setup MDW
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14712, -1, -1) WITH LOG
        RETURN(1) -- Failure
    END

    IF (@installpath IS NULL)
    BEGIN
        EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\Setup', N'SQLPath', @installpath OUTPUT;
    
        IF RIGHT(@installpath, 1) != N'\' set @installpath = @installpath + N'\'
        SET @installpath  = @installpath + N'Install\'
    END

    DECLARE @filename nvarchar(2048);
    SET @filename = @installpath + N'instmdw.sql'
    PRINT 'Uploading instmdw.sql from disk: ' + @filename

    CREATE TABLE #bulkuploadinstmdwscript
        (
            [instmdwscript] nvarchar(max) NOT NULL
        );


    DECLARE @stmt_bulkinsert nvarchar(2048);
    SET @stmt_bulkinsert = N'
    BULK INSERT #bulkuploadinstmdwscript
    FROM ' 
        -- Escape any embedded single quotes (we can't use QUOTENAME here b/c it can't handle strings > 128 chars)
        + '''' + REPLACE(@filename COLLATE database_default, N'''', N'''''') + '''
    WITH
        (
            DATAFILETYPE = ''char'',
            FIELDTERMINATOR = ''dc:stub:ft'',
            ROWTERMINATOR = ''dc:stub:rt''
        );
    ';

    EXECUTE sp_executesql @stmt_bulkinsert;

    DECLARE @bytesLoaded int
    SELECT @bytesLoaded = ISNULL (DATALENGTH ([instmdwscript]), 0) FROM #bulkuploadinstmdwscript
    PRINT 'Loaded ' + CONVERT (nvarchar, @bytesLoaded)
            + ' bytes from ' + '''' + REPLACE(@filename COLLATE database_default, N'''', N'''''') + ''''

    DECLARE @scriptdata varbinary(max)
    SELECT @scriptdata = convert(varbinary(max), [instmdwscript]) FROM #bulkuploadinstmdwscript;

    IF (EXISTS(SELECT * FROM [dbo].[syscollector_blobs_internal]
        WHERE parameter_name = N'InstMDWScript'))
    BEGIN
        UPDATE [dbo].[syscollector_blobs_internal]
        SET parameter_value = @scriptdata
        WHERE parameter_name = N'InstMDWScript'
    END
    ELSE
    BEGIN
        INSERT INTO [dbo].[syscollector_blobs_internal] (
            parameter_name, 
            parameter_value
        )
        VALUES
        (
            N'InstMDWScript',
            @scriptdata
        )
    END

    DROP TABLE #bulkuploadinstmdwscript
END
GO


---------------------------------------------------------------
-- Out-of-the-box data collector packages - loading SSIS packages
---------------------------------------------------------------

-- In case a failure occurs, procedure may have not been dropped, and the create will fail.
-- Execute CREATE OR ALTER to make sure it completes.
CREATE OR ALTER PROCEDURE #syscollector_upload_package_from_file 
    @filename nvarchar(2048),
    @packagename sysname,
    @packageid uniqueidentifier,
    @versionid uniqueidentifier
AS
BEGIN
    RAISERROR ('Uploading data collector package from disk: %s', 0, 1, @filename) WITH NOWAIT;

    CREATE TABLE #bulkpackage
        (
            [packagexml] xml NOT NULL
        );

    DECLARE @stmt_bulkinsert nvarchar(2048);
    SET @stmt_bulkinsert = N'
    BULK INSERT #bulkpackage
    FROM ' 
    -- Escape any embedded single quotes (we can't use QUOTENAME here b/c it can't handle strings > 128 chars)
    + '''' + REPLACE(@filename COLLATE database_default, N'''', N'''''') + '''
    WITH
        (
            DATAFILETYPE = N''widechar'',
            FIELDTERMINATOR = N''dc:stub:ft'',
            ROWTERMINATOR = N''dc:stub:rt''
        );
    ';

    EXECUTE sp_executesql @stmt_bulkinsert;

    DECLARE @bytesLoaded int
    SELECT @bytesLoaded = ISNULL (DATALENGTH ([packagexml]), 0) FROM #bulkpackage
    PRINT 'Loaded ' + CONVERT (varchar, @bytesLoaded)
            + ' bytes from ' 
            + '''' + REPLACE(@filename COLLATE database_default, N'''', N'''''') + ''''

    DECLARE @packagebin varbinary(max);
    SELECT @packagebin = convert(varbinary(max),[packagexml]) FROM #bulkpackage;

    DECLARE @loadtime datetime;
    SET @loadtime = getdate();

    DROP TABLE #bulkpackage

    EXECUTE sp_ssis_putpackage
            @name = @packagename
        ,    @id = @packageid
        ,    @description = N'System Data Collector Package'
        ,    @createdate = @loadtime
        ,    @folderid = '8877FE4B-A938-4a51-84B9-C5BDAD74B0AD'
        ,    @packagedata = @packagebin
        ,    @packageformat = 1
        ,    @packagetype = 5 -- DTSPKT_DTSDESIGNER100
        ,    @vermajor = 1
        ,    @verminor = 0
        ,    @verbuild = 0
        ,    @vercomments = N''
        ,    @verid = @versionid
    ;
END;
GO

-- In case a failure occurs, procedure may have not been dropped, and the create will fail. Use CREATE OR ALTER to make sure it succeeds.
CREATE OR ALTER PROCEDURE #syscollector_upload_package 
    @packagename sysname,
    @packageid uniqueidentifier,
    @versionid uniqueidentifier
AS
BEGIN
    DECLARE @installpath nvarchar(2048);
    EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\Setup', N'SQLPath', @installpath OUTPUT;
    IF RIGHT(@installpath,1) != N'\' set @installpath = @installpath + N'\'
    SET @installpath  = @installpath + N'Install\'

    DECLARE @filename nvarchar(2048);
    SET @filename = @installpath + @packagename + N'.dtsx'
    RAISERROR ('Uploading data collector package from disk: %s', 0, 1, @filename) WITH NOWAIT;

    EXEC #syscollector_upload_package_from_file @filename=@filename, @packagename=@packagename, @packageid=@packageid, @versionid=@versionid;    
END;
GO

--
-- Data Collector placeholder comment, dont move or remove.
-- 875f7de1-9e83-4be1-8b96-2df4a8533e88
--

-- Load SSIS packages needed by collector types

-- Temporarily enable the 'Agent XPs' config option so that sp_ssis_putpackage can 
-- succeed when SQLAgent is stopped. 

-- Skipping for Managed Instances as package files are not available.

IF (SERVERPROPERTY('EngineEdition') <> 8)
BEGIN
	EXECUTE #syscollector_upload_package 
        @packagename='SqlTraceCollect'
    ,    @packageid='0E149FC9-1046-4DE6-98BF-4B22ED6F6C42'
    ,    @versionid='244F0904-5CC6-49B8-AE90-905AEEA8BAF3';

	EXECUTE #syscollector_upload_package 
        @packagename='SqlTraceUpload'
    ,    @packageid='F389A8E6-5A17-4056-ABFD-C8B823F2092E'
    ,    @versionid='2D32AB4C-9929-4A85-A94E-7A01D8F40016';

	EXECUTE #syscollector_upload_package 
        @packagename='TSQLQueryCollect'
    ,    @packageid='292B1476-0F46-4490-A9B7-6DB724DE3C0B'
    ,    @versionid='E24C6D00-94C6-457B-BED4-1F9F018F3273';

	EXECUTE #syscollector_upload_package 
        @packagename='TSQLQueryUpload'
    ,    @packageid='6EB73801-39CF-489C-B682-497350C939F0'
    ,    @versionid='DA1210BC-C31B-43C6-B255-D8DDEB288CA1';

	EXECUTE #syscollector_upload_package 
        @packagename='PerfCountersCollect'
    ,    @packageid='C2EAABC1-5BF3-4127-BEB3-26E94D026E7D'
    ,    @versionid='09A5B959-21B3-44E1-A37F-4A62BE5D6244';

	EXECUTE #syscollector_upload_package 
        @packagename='PerfCountersUpload'
    ,    @packageid='08D854CB-0D45-4E96-92C6-227A5DCD7066'
    ,    @versionid='22A676DD-2025-493A-AD6B-C0186ABD556F';

	EXECUTE #syscollector_upload_package 
        @packagename='QueryActivityCollect'
    ,    @packageid='0B68FC9D-23DC-48F3-A937-90A0A8943D0E'
    ,    @versionid='75A3A143-2059-433B-A11C-C8E0C80A83CF';

	EXECUTE #syscollector_upload_package 
        @packagename='QueryActivityUpload'
    ,    @packageid='833DB628-8E19-47A3-92C5-FB1779B52E76'
    ,    @versionid='B1D79132-C6E6-46AA-8B14-E0AE4C4BA7BB';
END
GO

-- Cleanup the temp stored proc that we used to upload the SSIS packages
DROP PROCEDURE #syscollector_upload_package_from_file
DROP PROCEDURE #syscollector_upload_package
GO

---------------------------------------------------------------
-- Out-of-the-box collector type objects - definition for types
---------------------------------------------------------------

PRINT 'Creating or updating Collection Types...'
GO
-- Performance counters collector type
DECLARE @collector_type_uid uniqueidentifier
DECLARE @name sysname
DECLARE @parameter_schema xml
DECLARE @parameter_formatter xml
DECLARE @collection_package_id uniqueidentifier
DECLARE @upload_package_id uniqueidentifier

SET @collector_type_uid = '294605dd-21de-40b2-b20f-f3e170ea1ec3'
SET @name = 'Performance Counters Collector Type'
SET @parameter_schema =
		'<?xml version="1.0" encoding="utf-8"?>
			<xs:schema targetNamespace="DataCollectorType" xmlns:xs="http://www.w3.org/2001/XMLSchema">
			 <xs:element name="PerformanceCountersCollector">
			  <xs:complexType>
				  <xs:sequence>
					  <xs:element minOccurs="0" maxOccurs="unbounded" name="PerformanceCounters">
						  <xs:complexType>
							  <xs:attribute name="Objects" type="xs:string" use="required" />
							  <xs:attribute name="Counters" type="xs:string" use="required" />
							  <xs:attribute name="Instances" type="xs:string" use="optional" />
						  </xs:complexType>
					  </xs:element>
				  </xs:sequence>
			  	  <xs:attribute name="StoreLocalizedCounterNames" type="xs:boolean" use="optional" default="false" />
			  </xs:complexType>
			</xs:element>
			</xs:schema>
		'
SET @parameter_formatter = 
        N'<xsl:stylesheet 
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            version="1.0"
            xmlns:z="#RowsetSchema"
            >
        <xsl:template match="/PerformanceCountersCollector">
            <HTML>
            <HEAD>
            <TITLE></TITLE>
            </HEAD>
            <BODY>
            <UL>
            <xsl:apply-templates select="PerformanceCounters"/>
            </UL>
            <HR/>
            </BODY>
            </HTML>
        </xsl:template>
        <xsl:template match="PerformanceCounters">
            <LI>
            \<xsl:value-of select="@Objects"/>
            <xsl:if test="@Instances">(<xsl:value-of select="@Instances"/>)</xsl:if>
            \<xsl:value-of select="@Counters"/>
            </LI>
        </xsl:template>
        </xsl:stylesheet>'
SET @collection_package_id = 'C2EAABC1-5BF3-4127-BEB3-26E94D026E7D'
SET @upload_package_id = '08D854CB-0D45-4E96-92C6-227A5DCD7066'

IF(EXISTS (SELECT * FROM dbo.syscollector_collector_types 
            WHERE collector_type_uid = @collector_type_uid))
BEGIN
    PRINT 'Updating Performance counters collector type'
    EXEC sp_syscollector_update_collector_type 
            @collector_type_uid = @collector_type_uid,
            @name = @name,
            @parameter_schema = @parameter_schema,
            @parameter_formatter = @parameter_formatter,
            @collection_package_id = @collection_package_id,
            @upload_package_id = @upload_package_id
END
ELSE
BEGIN
    PRINT 'Creating Performance Counters collector type'
    EXEC sp_syscollector_create_collector_type
            @collector_type_uid = @collector_type_uid,
            @name = @name,
            @parameter_schema = @parameter_schema,
            @parameter_formatter = @parameter_formatter,
            @collection_package_id = @collection_package_id,
            @upload_package_id = @upload_package_id
END

UPDATE syscollector_collector_types 
SET is_system = 1
WHERE collector_type_uid = @collector_type_uid
GO

-- TSQL query collector type
DECLARE @collector_type_uid uniqueidentifier
DECLARE @name sysname
DECLARE @parameter_schema xml
DECLARE @parameter_formatter xml
DECLARE @collection_package_id uniqueidentifier
DECLARE @upload_package_id uniqueidentifier

SET @collector_type_uid = '302E93D1-3424-4be7-AA8E-84813ECF2419'
SET @name = 'Generic T-SQL Query Collector Type'
SET @parameter_schema = '<?xml version="1.0" encoding="utf-8"?>
        <xs:schema targetNamespace="DataCollectorType" xmlns:xs="http://www.w3.org/2001/XMLSchema">
         <xs:element name="TSQLQueryCollector">
          <xs:complexType>
           <xs:sequence>
            <xs:element name="Query" minOccurs="1" maxOccurs="unbounded">
             <xs:complexType>
              <xs:sequence>
               <xs:element name="Value" type="xs:string" />
               <xs:element name="OutputTable" type="xs:string" />
              </xs:sequence>
             </xs:complexType>
            </xs:element>
            <xs:element name="Databases" minOccurs="0" maxOccurs="1">
             <xs:complexType>
              <xs:sequence>
               <xs:element name="Database" minOccurs="0" maxOccurs="unbounded" type="xs:string" />
              </xs:sequence>
              <xs:attribute name="UseSystemDatabases" type="xs:boolean" use="optional" />
              <xs:attribute name="UseUserDatabases" type="xs:boolean" use="optional" />
             </xs:complexType>
            </xs:element>
           </xs:sequence>
          </xs:complexType>
         </xs:element>
        </xs:schema>'
SET @parameter_formatter = 
        N'<xsl:stylesheet 
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            version="1.0"
            xmlns:z="#RowsetSchema"
            >
        <xsl:template match="/TSQLQueryCollector">
            <HTML>
            <HEAD>
            <TITLE></TITLE>
            </HEAD>
            <BODY>
            <xsl:apply-templates select="Query"/>
            </BODY>
            </HTML>
        </xsl:template>
        <xsl:template match="Query">
            <I>
            <xsl:value-of select="OutputTable"/>
            </I> <BR/>
            <PRE>
            <xsl:value-of select="Value"/>
            </PRE>
            <HR/>
        </xsl:template>
        </xsl:stylesheet>'
SET @collection_package_id = '292B1476-0F46-4490-A9B7-6DB724DE3C0B'
SET @upload_package_id = '6EB73801-39CF-489C-B682-497350C939F0'

IF(EXISTS (SELECT * FROM dbo.syscollector_collector_types 
            WHERE collector_type_uid = @collector_type_uid))
BEGIN
    PRINT 'Updating TSQL Query collector type'
    EXEC sp_syscollector_update_collector_type 
            @collector_type_uid = @collector_type_uid,
            @name = @name,
            @parameter_schema = @parameter_schema,
            @parameter_formatter = @parameter_formatter,
            @collection_package_id = @collection_package_id,
            @upload_package_id = @upload_package_id
END
ELSE
BEGIN
    PRINT 'Creating T-SQL Query collector type'
    EXEC sp_syscollector_create_collector_type
            @collector_type_uid = @collector_type_uid,
            @name = @name,
            @parameter_schema = @parameter_schema,
            @parameter_formatter = @parameter_formatter,
            @collection_package_id = @collection_package_id,
            @upload_package_id = @upload_package_id
END

-- mark the collector type as system
UPDATE syscollector_collector_types 
SET is_system = 1
WHERE collector_type_uid = @collector_type_uid
GO

---------------------------------------------------------------
-- Database objects for TSQL query collector type 
---------------------------------------------------------------

IF (OBJECT_ID(N'[dbo].[syscollector_tsql_query_collector]', 'U') IS NULL)
BEGIN
    PRINT 'Creating table [dbo].[syscollector_tsql_query_collector]...'
    CREATE TABLE [dbo].[syscollector_tsql_query_collector] (
        collection_set_uid            uniqueidentifier NOT NULL,
        collection_set_id            int NOT NULL,
        collection_item_id            int NOT NULL,
        collection_package_id        uniqueidentifier NOT NULL,
        upload_package_id            uniqueidentifier NOT NULL,
        )
    ALTER TABLE syscollector_tsql_query_collector
        ADD CONSTRAINT [FK_syscollector_tsql_query_collector_syscollector_collection_items_internal] FOREIGN KEY(collection_set_id, collection_item_id)
        REFERENCES syscollector_collection_items_internal (collection_set_id, collection_item_id) ON DELETE CASCADE
END
GO

IF (OBJECT_ID('dbo.syscollector_collection_item_parameter_update_trigger', 'TR') IS NOT NULL)
BEGIN
    PRINT 'Dropping trigger [dbo].[syscollector_collection_item_parameter_update_trigger] on [dbo].[syscollector_collection_items_internal]'
    DROP TRIGGER [dbo].[syscollector_collection_item_parameter_update_trigger]
END
GO

PRINT 'Creating trigger [dbo].[syscollector_collection_item_parameter_update_trigger] on [dbo].[syscollector_collection_items_internal]'
GO
CREATE TRIGGER [dbo].[syscollector_collection_item_parameter_update_trigger] on [dbo].[syscollector_collection_items_internal]
FOR UPDATE
AS
BEGIN
    DECLARE @collection_set_id int
    DECLARE @collection_item_id int

    -- remove the TSQL query collection item that was updated so packages will be regenerated 
    -- base on the new parameters
    IF (NOT UPDATE (parameters))
       RETURN

    -- clean up the SSIS packages that are left behind
    DECLARE inserted_cursor CURSOR LOCAL FOR
        SELECT collection_set_id, collection_item_id
        FROM inserted
    
    OPEN inserted_cursor
    FETCH inserted_cursor INTO @collection_set_id, @collection_item_id

    WHILE @@FETCH_STATUS = 0
    BEGIN
        DELETE FROM dbo.syscollector_tsql_query_collector 
        WHERE collection_set_id = @collection_set_id
        AND collection_item_id = @collection_item_id

        FETCH inserted_cursor INTO @collection_set_id, @collection_item_id
    END

    CLOSE inserted_cursor
    DEALLOCATE inserted_cursor
END
GO

IF (OBJECT_ID('dbo.syscollector_tsql_query_collector_delete_trigger', 'TR') IS NOT NULL)
BEGIN
    PRINT 'Dropping trigger [dbo].[syscollector_tsql_query_collector_delete_trigger] on [dbo].[syscollector_tsql_query_collector]'
    DROP TRIGGER [dbo].[syscollector_tsql_query_collector_delete_trigger]
END
GO

PRINT 'Creating trigger [dbo].[syscollector_tsql_query_collector_delete_trigger] on [dbo].[syscollector_tsql_query_collector]'
GO
CREATE TRIGGER [dbo].[syscollector_tsql_query_collector_delete_trigger] on [dbo].[syscollector_tsql_query_collector]
FOR DELETE
AS
BEGIN
    -- remove the SSIS packages left behind when the collection item is deleted 
    DECLARE @collection_package_id uniqueidentifier
    DECLARE @collection_package_folderid uniqueidentifier
    DECLARE @collection_package_name sysname

    DECLARE @upload_package_id  uniqueidentifier
    DECLARE @upload_package_folderid  uniqueidentifier
    DECLARE @upload_package_name  sysname

    DECLARE deleted_cursor CURSOR LOCAL FOR
        SELECT collection_package_id, upload_package_id
        FROM deleted
    
    OPEN deleted_cursor
    FETCH deleted_cursor INTO @collection_package_id, @upload_package_id

    WHILE @@FETCH_STATUS = 0
    BEGIN
        SELECT 
            @collection_package_name = name,
            @collection_package_folderid = folderid
        FROM sysssispackages
        WHERE @collection_package_id = id

        SELECT 
            @upload_package_name = name,
            @upload_package_folderid = folderid
        FROM sysssispackages
        WHERE @upload_package_id = id

        EXEC dbo.sp_ssis_deletepackage
            @name = @collection_package_name,
            @folderid = @collection_package_folderid

        EXEC dbo.sp_ssis_deletepackage
            @name = @upload_package_name,
            @folderid = @upload_package_folderid

        FETCH deleted_cursor INTO @collection_package_id, @upload_package_id
    END

    CLOSE deleted_cursor
    DEALLOCATE deleted_cursor
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_create_tsql_query_collector]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_create_tsql_query_collector]...'
    DROP PROCEDURE [dbo].[sp_syscollector_create_tsql_query_collector]
END
GO

PRINT 'Creating procedure [dbo].[sp_syscollector_create_tsql_query_collector]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_create_tsql_query_collector]
    @collection_set_uid            uniqueidentifier,
    @collection_item_id            int,
    @collection_package_id        uniqueidentifier,
    @upload_package_id            uniqueidentifier
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND 
        NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND 
        NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_operator'' or ''dc_proxy')
        RETURN(1) -- Failure
    END

    DECLARE @errMsg VARCHAR(256)
    DECLARE @collection_set_id int
    SELECT @collection_set_id = s.collection_set_id
    FROM dbo.syscollector_collection_items i, dbo.syscollector_collection_sets s
    WHERE i.collection_item_id = @collection_item_id
    AND i.collector_type_uid = '302E93D1-3424-4be7-AA8E-84813ECF2419'
    AND s.collection_set_uid = @collection_set_uid

    -- Verify that the collection item exists of the correct type
    IF (@collection_set_id IS NULL)
    BEGIN        
        SELECT @errMsg = CONVERT(VARCHAR(36), @collection_set_uid) + ', ' + CONVERT(VARCHAR(36), @collection_item_id)
        RAISERROR(14262, -1, -1, '@collection_set_uid, @collection_item_id', @errMsg)
        RETURN(1)
    END

    -- Get the names and folder ids for the generated packages
    DECLARE @upload_package_name sysname
    DECLARE @upload_package_folder_id uniqueidentifier
    SELECT @upload_package_name = name, @upload_package_folder_id = folderid
    FROM sysssispackages
    WHERE id = @upload_package_id
    
    IF (@upload_package_name IS NULL) 
    BEGIN
        SELECT @errMsg = @upload_package_name + ', ' + CONVERT(VARCHAR(36), @upload_package_folder_id)
        RAISERROR(14262, -1, -1, '@upload_package_name, @upload_package_folder_id', @errMsg)
        RETURN(1)
    END

    DECLARE @collection_package_name sysname
    DECLARE @collection_package_folder_id uniqueidentifier
    SELECT @collection_package_name = name, @collection_package_folder_id = folderid
    FROM sysssispackages
    WHERE id = @collection_package_id
    
    IF (@collection_package_name IS NULL) 
    BEGIN
        SELECT @errMsg = @collection_package_name + ', ' + CONVERT(VARCHAR(36), @collection_package_folder_id)
        RAISERROR(14262, -1, -1, '@collection_package_name, @collection_package_folder_id', @errMsg)
        RETURN(1)
    END

    -- we need to allow dc_admin to delete these packages along with the collection set when 
    -- the set is deleted
    EXEC sp_ssis_setpackageroles @name = @upload_package_name, @folderid = @upload_package_folder_id, @readrole = NULL, @writerole = N'dc_admin'
    EXEC sp_ssis_setpackageroles @name = @collection_package_name, @folderid = @collection_package_folder_id, @readrole = NULL, @writerole = N'dc_admin'

    INSERT INTO [dbo].[syscollector_tsql_query_collector]
    (
        collection_set_uid,
        collection_set_id, 
        collection_item_id,
        collection_package_id,
        upload_package_id
    )
    VALUES
    (
        @collection_set_uid,
        @collection_set_id,
        @collection_item_id,
        @collection_package_id,
        @upload_package_id
    )
END
GO

IF (NOT OBJECT_ID('[dbo].[sp_syscollector_get_tsql_query_collector_package_ids]', 'P') IS NULL)
BEGIN
    PRINT 'Dropping procedure [dbo].[sp_syscollector_get_tsql_query_collector_package_ids]...'
    DROP PROCEDURE [dbo].[sp_syscollector_get_tsql_query_collector_package_ids]
END
GO

-- get and return the collection and upload package IDs
-- if they do not exist, return empty IDs
PRINT 'Creating procedure [dbo].[sp_syscollector_get_tsql_query_collector_package_ids]...'
GO
CREATE PROCEDURE [dbo].[sp_syscollector_get_tsql_query_collector_package_ids]
    @collection_set_uid            uniqueidentifier,
    @collection_item_id            int,
    @collection_package_id        uniqueidentifier OUTPUT,
    @upload_package_id            uniqueidentifier OUTPUT,
    @collection_package_name    sysname OUTPUT,
    @upload_package_name        sysname OUTPUT    
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_operator'), 0) = 1) AND 
        NOT (ISNULL(IS_MEMBER(N'dc_proxy'), 0) = 1) AND
        NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        RAISERROR(14677, -1, -1, 'dc_operator'' or ''dc_proxy')
        RETURN(1) -- Failure
    END

    SELECT @collection_package_id = collection_package_id,
        @upload_package_id = upload_package_id
    FROM dbo.syscollector_tsql_query_collector
    WHERE @collection_item_id = collection_item_id 
      AND @collection_set_uid = collection_set_uid

    IF(@collection_package_id IS NOT NULL AND @upload_package_id IS NOT NULL)
    BEGIN
        SELECT @collection_package_name = name
        FROM dbo.sysssispackages
        WHERE @collection_package_id = id

        SELECT @upload_package_name = name
        FROM dbo.sysssispackages
        WHERE @upload_package_id = id
    END
END
GO

--
-- This stored procedure is used to cleanup all activities done while configuring Data collector
-- In case a collection_set_id is passed to this stored procedure, only that collection set will be cleaned up
-- otherwise, data collector will be restored to its original state
-- Following cleanup  tasks are done
-- a) Delete collect, upload jobs 
-- b) Set Data collector to non-configured state 
--    only if collection_set_id is not mentioned or the passed collection set is the last running
--    collection set
-- c) Delete all collection set logs
--
IF (NOT OBJECT_ID(N'[dbo].[sp_syscollector_cleanup_collector]', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [dbo].[sp_syscollector_cleanup_collector] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [dbo].[sp_syscollector_cleanup_collector]
END
GO 

RAISERROR('Creating procedure [dbo].[sp_syscollector_cleanup_collector] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROC [dbo].[sp_syscollector_cleanup_collector]
    @collection_set_id INT = NULL
AS
BEGIN
    IF (@collection_set_id IS NOT NULL)
    BEGIN
        DECLARE @retVal int
        EXEC @retVal = dbo.sp_syscollector_verify_collection_set @collection_set_id OUTPUT
        IF (@retVal <> 0)
        BEGIN
            RETURN (1)
        END
    END

    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
        SAVE TRANSACTION tran_cleanup_collection_set
    ELSE
        BEGIN TRANSACTION

    BEGIN TRY
    -- changing isolation level to repeatable to avoid any conflicts that may happen
    -- while running this stored procedure and sp_syscollector_start_collection_set concurrently
    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'dc_admin'), 0) = 1) AND NOT (ISNULL(IS_MEMBER(N'db_owner'), 0) = 1))
    BEGIN
        REVERT
        RAISERROR(14677, -1, -1, 'dc_admin')
        RETURN (1)
    END

    -- Disable constraints
    -- this is done to make sure that constraint logic does not interfere with cleanup process
    ALTER TABLE dbo.syscollector_collection_sets_internal NOCHECK CONSTRAINT FK_syscollector_collection_sets_collection_sysjobs
    ALTER TABLE dbo.syscollector_collection_sets_internal NOCHECK CONSTRAINT FK_syscollector_collection_sets_upload_sysjobs

    -- Delete data collector jobs
    DECLARE @job_id uniqueidentifier
    DECLARE datacollector_jobs_cursor CURSOR LOCAL 
    FOR
        SELECT collection_job_id AS job_id FROM syscollector_collection_sets
        WHERE collection_job_id IS NOT NULL
        AND ( collection_set_id = @collection_set_id OR @collection_set_id IS NULL)
        UNION
        SELECT upload_job_id AS job_id FROM syscollector_collection_sets
        WHERE upload_job_id IS NOT NULL
        AND ( collection_set_id = @collection_set_id OR @collection_set_id IS NULL)

    OPEN datacollector_jobs_cursor
    FETCH NEXT FROM datacollector_jobs_cursor INTO @job_id
  
    WHILE (@@fetch_status = 0)
    BEGIN
        IF EXISTS ( SELECT COUNT(job_id) FROM sysjobs WHERE job_id = @job_id )
        BEGIN
            DECLARE @job_name sysname
            SELECT @job_name = name from sysjobs WHERE job_id = @job_id
            PRINT 'Removing job '+ @job_name
            EXEC dbo.sp_delete_job @job_id=@job_id, @delete_unused_schedule=0
        END
        FETCH NEXT FROM datacollector_jobs_cursor INTO @job_id
    END
    
    CLOSE datacollector_jobs_cursor
    DEALLOCATE datacollector_jobs_cursor

    -- Enable Constraints back
    ALTER TABLE dbo.syscollector_collection_sets_internal CHECK CONSTRAINT FK_syscollector_collection_sets_collection_sysjobs
    ALTER TABLE dbo.syscollector_collection_sets_internal CHECK CONSTRAINT FK_syscollector_collection_sets_upload_sysjobs


    -- Disable trigger on syscollector_collection_sets_internal
    -- this is done to make sure that trigger logic does not interfere with cleanup process
    EXEC('DISABLE TRIGGER syscollector_collection_set_is_running_update_trigger ON syscollector_collection_sets_internal')

    -- Set collection sets as not running state and update collect and upload jobs as null
    UPDATE syscollector_collection_sets_internal
    SET is_running = 0, 
        collection_job_id = NULL, 
        upload_job_id = NULL
    WHERE (collection_set_id = @collection_set_id OR @collection_set_id IS NULL)

    -- Enable back trigger on syscollector_collection_sets_internal
    EXEC('ENABLE TRIGGER syscollector_collection_set_is_running_update_trigger ON syscollector_collection_sets_internal')

    -- re-set collector config store if there is no enabled collector
    DECLARE @counter INT
    SELECT @counter= COUNT(is_running) 
    FROM syscollector_collection_sets_internal 
    WHERE is_running = 1

    IF (@counter = 0)  
    BEGIN
        UPDATE syscollector_config_store_internal
        SET parameter_value = 0
        WHERE parameter_name IN ('CollectorEnabled');

        UPDATE syscollector_config_store_internal
        SET parameter_value = NULL
        WHERE parameter_name IN ( 'MDWDatabase', 'MDWInstance' )
    END

    -- Delete collection set logs
    DELETE FROM syscollector_execution_log_internal
    WHERE (collection_set_id = @collection_set_id OR @collection_set_id IS NULL)

    IF (@TranCounter = 0)
    BEGIN
        COMMIT TRANSACTION
    END
    RETURN(0)
    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_cleanup_collection_set

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        RETURN (1)
    END CATCH
END
GO

-- SQLTrace collector type
DECLARE @collector_type_uid uniqueidentifier
DECLARE @name sysname
DECLARE @parameter_schema xml
DECLARE @parameter_formatter xml
DECLARE @collection_package_id uniqueidentifier
DECLARE @upload_package_id uniqueidentifier

SET @collector_type_uid = '0E218CF8-ECB5-417B-B533-D851C0251271'
SET @name = 'Generic SQL Trace Collector Type'
SET @parameter_schema = '<?xml version="1.0" encoding="utf-8"?>
                        <xs:schema targetNamespace="DataCollectorType" xmlns:xs="http://www.w3.org/2001/XMLSchema">
                         <xs:element name="SqlTraceCollector">
                          <xs:complexType>
                           <xs:sequence>
                            <xs:element name="Events">
                             <xs:complexType>
                              <xs:sequence>
                               <xs:element minOccurs="0" maxOccurs="unbounded" name="EventType">
                                <xs:complexType>
                                 <xs:sequence>
                                  <xs:element maxOccurs="unbounded" name="Event">
                                   <xs:complexType>
                                    <xs:attribute name="id" type="xs:unsignedByte" use="required" />
                                    <xs:attribute name="name" type="xs:string" use="required" />
                                    <xs:attribute name="columnslist" type="xs:string" use="optional" />
                                   </xs:complexType>
                                  </xs:element>
                                 </xs:sequence>
                                 <xs:attribute name="id" type="xs:unsignedByte" use="optional" />
                                 <xs:attribute name="name" type="xs:string" use="required" />
                                </xs:complexType>
                               </xs:element>
                              </xs:sequence>
                             </xs:complexType>
                            </xs:element>
                            <xs:element name="Filters">
                             <xs:complexType>
                              <xs:sequence>
                               <xs:element name="Filter" minOccurs="0" maxOccurs="unbounded">
                                <xs:complexType>
                                 <xs:attribute name="columnid" type="xs:unsignedByte" use="required" />
                                 <xs:attribute name="columnname" type="xs:string" use="required" />
                                 <xs:attribute name="logical_operator" type="xs:string" use="required" />
                                 <xs:attribute name="comparison_operator" type="xs:string" use="required" />
                                 <xs:attribute name="value" type="xs:string" use="required" />
                                </xs:complexType>
                               </xs:element>
                              </xs:sequence>
                             </xs:complexType>
                            </xs:element>
                           </xs:sequence>
                           <xs:attribute name="use_default" type="xs:boolean" />
                          </xs:complexType>
                         </xs:element>
                        </xs:schema>'
SET @parameter_formatter = 
        N'<xsl:stylesheet 
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            version="1.0"
            xmlns:z="#RowsetSchema"
            >
        <xsl:template match="/SqlTraceCollector">
            <HTML>
            <HEAD>
            <TITLE></TITLE>
            </HEAD>
            <BODY>
            <xsl:apply-templates select="Events"/>
            <HR/>
            <xsl:apply-templates select="Filters"/>
            </BODY>
            </HTML>
        </xsl:template>
        <xsl:template match="Events">
            <xsl:apply-templates select="EventType"/>
            <BR/>
        </xsl:template>
        <xsl:template match="EventType">
            <I> <PRE> ID = <xsl:value-of select="@id"/> - <xsl:value-of select="@name"/> </PRE> </I> 
            <UL>
            <xsl:apply-templates select="Event"/>
            </UL>
        </xsl:template>
        <xsl:template match="Event">
            <LI>
            <PRE> ID = <xsl:value-of select="@id"/> - <xsl:value-of select="@name"/> </PRE>
            </LI>
        </xsl:template>
        <xsl:template match="Filters">
            <UL>
            <xsl:apply-templates select="Filter" />
            </UL>
        </xsl:template>
        <xsl:template match="Filter">
            <PRE> <xsl:value-of select="@logical_operator"/> - <xsl:value-of select="@columnname"/> - <xsl:value-of select="@comparison_operator"/> - <xsl:value-of select="@value"/> </PRE>
        </xsl:template>
        </xsl:stylesheet>'
SET @collection_package_id = '0E149FC9-1046-4DE6-98BF-4B22ED6F6C42'
SET @upload_package_id = 'F389A8E6-5A17-4056-ABFD-C8B823F2092E' 

IF(EXISTS (SELECT * FROM dbo.syscollector_collector_types 
            WHERE collector_type_uid = @collector_type_uid))
BEGIN
    PRINT 'Updating SQL Trace collector type'
    EXEC sp_syscollector_update_collector_type 
            @collector_type_uid = @collector_type_uid,
            @name = @name,
            @parameter_schema = @parameter_schema,
            @parameter_formatter = @parameter_formatter,
            @collection_package_id = @collection_package_id,
            @upload_package_id = @upload_package_id
END
ELSE
BEGIN
    PRINT 'Creating SQL Trace collector type'
    EXEC sp_syscollector_create_collector_type
            @collector_type_uid = @collector_type_uid,
            @name = @name,
            @parameter_schema = @parameter_schema,
            @parameter_formatter = @parameter_formatter,
            @collection_package_id = @collection_package_id,
            @upload_package_id = @upload_package_id
END

-- mark the collector type as system
UPDATE syscollector_collector_types 
SET is_system = 1
WHERE collector_type_uid = @collector_type_uid
GO

-- Query Activity Collector Type
DECLARE @collector_type_uid uniqueidentifier
DECLARE @name sysname
DECLARE @parameter_schema xml
DECLARE @parameter_formatter xml
DECLARE @collection_package_id uniqueidentifier
DECLARE @upload_package_id uniqueidentifier

SET @collector_type_uid = '14AF3C12-38E6-4155-BD29-F33E7966BA23'
SET @name = 'Query Activity Collector Type'
SET @collection_package_id = '0B68FC9D-23DC-48F3-A937-90A0A8943D0E'
SET @upload_package_id = '833DB628-8E19-47A3-92C5-FB1779B52E76'

SET @parameter_schema = '<?xml version="1.0" encoding="utf-8"?>
        <xs:schema targetNamespace="DataCollectorType" xmlns:xs="http://www.w3.org/2001/XMLSchema">
         <xs:element name="QueryActivityCollector">
          <xs:complexType>
           <xs:sequence>
            <xs:element name="Databases" minOccurs="0" maxOccurs="1">
              <xs:complexType>
                <xs:attribute name="IncludeSystemDatabases" type="xs:boolean" use="optional" />
              </xs:complexType>
            </xs:element>
           </xs:sequence>
          </xs:complexType>
         </xs:element>
        </xs:schema>'
SET @parameter_formatter = 
        N'<xsl:stylesheet 
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
            version="1.0"
            xmlns:z="#RowsetSchema"
            >
        <xsl:template match="/QueryActivityCollector">
            <HTML>
            <HEAD>
            <TITLE></TITLE>
            </HEAD>
            <BODY>
            <xsl:apply-templates select="Databases"/>
            </BODY>
            </HTML>
        </xsl:template>
        <xsl:template match="Databases">
            IncludeSystemDatabases:<xsl:value-of select="@IncludeSystemDatabases"/>
        </xsl:template>
        </xsl:stylesheet>'

IF(EXISTS (SELECT * FROM dbo.syscollector_collector_types 
            WHERE collector_type_uid = @collector_type_uid))
BEGIN
    PRINT 'Updating Query Activity Collector Type'
    EXEC sp_syscollector_update_collector_type
        @collector_type_uid = @collector_type_uid,
        @name = @name,
        @parameter_schema = @parameter_schema,
        @parameter_formatter = @parameter_formatter,
        @collection_package_id = @collection_package_id,
        @upload_package_id = @upload_package_id
END
ELSE
BEGIN
    PRINT 'Creating Query Activity Collector Type'
    EXEC sp_syscollector_create_collector_type
        @collector_type_uid = @collector_type_uid,
        @name = @name,
        @parameter_schema = @parameter_schema,
        @parameter_formatter = @parameter_formatter,
        @collection_package_id = @collection_package_id,
        @upload_package_id = @upload_package_id
END

-- mark the collector type as system
UPDATE syscollector_collector_types
SET is_system = 1
WHERE collector_type_uid = @collector_type_uid
GO

---------------------------------------------------------------
-- Out-of-the-box collector objects - Generic schedules for system collection sets
---------------------------------------------------------------
PRINT 'Creating data collector schedules'

DECLARE @schedule_name sysname
SET @schedule_name = N'CollectorSchedule_Every_5min'
IF  NOT EXISTS (SELECT name FROM msdb.dbo.sysschedules_localserver_view WHERE name = @schedule_name)
BEGIN
    EXEC dbo.sp_add_schedule
        @schedule_name = @schedule_name,    -- Schedule name
        @freq_type = 4,                        -- Daily
        @freq_interval = 1,                    -- Recurs every 1 day
        @freq_subday_type = 0x4,            -- Frequency type is "minutes"
        @freq_subday_interval = 5            -- Occurs every 5 minutes
END

SET @schedule_name = N'CollectorSchedule_Every_10min'
IF  NOT EXISTS (SELECT name FROM msdb.dbo.sysschedules_localserver_view WHERE name = @schedule_name)
BEGIN
    EXEC dbo.sp_add_schedule
        @schedule_name = @schedule_name,    -- Schedule name
        @freq_type = 4,                        -- Daily
        @freq_interval = 1,                    -- Recurs every 1 day
        @freq_subday_type = 0x4,            -- Frequency type is "minutes"
        @freq_subday_interval = 10            -- Occurs every 10 minutes
END

SET @schedule_name = N'CollectorSchedule_Every_15min'
IF  NOT EXISTS (SELECT name FROM msdb.dbo.sysschedules_localserver_view WHERE name = @schedule_name)
BEGIN
    EXEC dbo.sp_add_schedule
        @schedule_name = @schedule_name,    -- Schedule name
        @freq_type = 4,                        -- Daily
        @freq_interval = 1,                    -- Recurs every 1 day
        @freq_subday_type = 0x4,            -- Frequency type is "minutes"
        @freq_subday_interval = 15            -- Occurs every 15 minutes
END

SET @schedule_name = N'CollectorSchedule_Every_30min'
IF  NOT EXISTS (SELECT name FROM msdb.dbo.sysschedules_localserver_view WHERE name = @schedule_name)
BEGIN
    EXEC dbo.sp_add_schedule
        @schedule_name = @schedule_name,    -- Schedule name
        @freq_type = 4,                        -- Daily
        @freq_interval = 1,                    -- Recurs every 1 day
        @freq_subday_type = 0x4,            -- Frequency type is "minutes"
        @freq_subday_interval = 30            -- Occurs every 30 minutes
END

SET @schedule_name = N'CollectorSchedule_Every_60min'
IF  NOT EXISTS (SELECT name FROM msdb.dbo.sysschedules_localserver_view WHERE name = @schedule_name)
BEGIN
    EXEC dbo.sp_add_schedule
        @schedule_name = @schedule_name,    -- Schedule name
        @freq_type = 4,                        -- Daily
        @freq_interval = 1,                    -- Recurs every 1 day
        @freq_subday_type = 0x4,            -- Frequency type is "minutes"
        @freq_subday_interval = 60            -- Occurs every 60 minutes
END

SET @schedule_name = N'CollectorSchedule_Every_6h'
IF  NOT EXISTS (SELECT name FROM msdb.dbo.sysschedules_localserver_view WHERE name = @schedule_name)
BEGIN
    EXEC dbo.sp_add_schedule
        @schedule_name = @schedule_name,    -- Schedule name
        @freq_type = 4,                        -- Daily
        @freq_interval = 1,                    -- Recurs every 1 day
        @freq_subday_type = 0x8,            -- Frequency type is "hours"
        @freq_subday_interval = 6            -- Occurs every 6 hours
END
GO



---------------------------------------------------------------
-- Out-of-the-box system Collection Sets 
---------------------------------------------------------------
PRINT 'Creating system Collection Sets...'

------------------------------------------------
-- System collection set: Disk Usage
------------------------------------------------
DECLARE @collection_set_name NVARCHAR(128);
DECLARE @description NVARCHAR(4000);
DECLARE @collection_set_id int;
DECLARE @collection_set_uid uniqueidentifier;
DECLARE @collection_mode smallint;
DECLARE @schedule_name sysname;
DECLARE @days_until_expiration smallint;
DECLARE @name_id int;
DECLARE @description_id int;

-- The GUID below identifies this collection set and is used to locate the data collected by this collection set. 
-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection set detils (syscollector_collection_sets) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14701;
SET @description_id = 14700;
SET @collection_set_uid = N'7B191952-8ECF-4E12-AEB2-EF646EF79FEF';
SET @collection_mode = 1; -- Non-cached
SET @schedule_name = N'CollectorSchedule_Every_6h';
SET @days_until_expiration = 730;

SET @description = FORMATMESSAGE(@description_id);
SET @collection_set_name = ISNULL (FORMATMESSAGE(@name_id), 'Disk Usage');

IF EXISTS (SELECT * FROM dbo.syscollector_collection_sets_internal WHERE collection_set_uid = @collection_set_uid)
BEGIN
    RAISERROR ('Updating system Collection Set "%s"...', 0, 1, @collection_set_name) WITH NOWAIT;
    -- We are updating an existing collection set -- get its ID 
    SELECT @collection_set_id = collection_set_id
    FROM syscollector_collection_sets 
    WHERE collection_set_uid = @collection_set_uid;
    
    -- Temporarily clear the is_system flag so that we can modify the collection set definition
    UPDATE syscollector_collection_sets
    SET is_system = 0
    WHERE collection_set_id = @collection_set_id
    
    -- Don't override the current expiration period or schedule settings, since the user may have customized these
    EXEC dbo.sp_syscollector_update_collection_set
        @collection_set_id = @collection_set_id, 
        @new_name = @collection_set_name, 
        @schedule_name = @schedule_name, -- avoid mismatch in uid if schedule was deleted and recreated
        @collection_mode = @collection_mode, 
        @logging_level = 0,
        @description = @description;
END
ELSE
BEGIN    
    RAISERROR ('Creating system Collection Set "%s"...', 0, 1, @collection_set_name) WITH NOWAIT;
    EXEC dbo.sp_syscollector_create_collection_set
        @collection_set_uid = @collection_set_uid,
        @name = @collection_set_name, 
        @schedule_name = @schedule_name, 
        @collection_mode = @collection_mode, 
        @days_until_expiration = @days_until_expiration, 
        @description = @description,
        @logging_level = 0, 
        @collection_set_id = @collection_set_id OUTPUT;
END

-- for localization of collection set name and description
UPDATE syscollector_collection_sets_internal
SET name_id = @name_id, description_id = @description_id
WHERE collection_set_uid = @collection_set_uid;

-- Add collection items
DECLARE @collection_item_name NVARCHAR(128);
DECLARE @collection_item_old_name NVARCHAR(128);
DECLARE @collection_item_id int;
DECLARE @frequency int;

-- Item 1: disk_usage DMV query
DECLARE @parameters xml;
SELECT @parameters = convert(xml, N'<ns:TSQLQueryCollector xmlns:ns="DataCollectorType">
<Query>
<Value>
DECLARE @dbsize bigint 
DECLARE @logsize bigint 
DECLARE @ftsize bigint 
DECLARE @reservedpages bigint 
DECLARE @pages bigint 
DECLARE @usedpages bigint

SELECT @dbsize = SUM(convert(bigint,case when type = 0 then size else 0 end)) 
      ,@logsize = SUM(convert(bigint,case when type = 1 then size else 0 end)) 
      ,@ftsize = SUM(convert(bigint,case when type = 4 then size else 0 end)) 
FROM sys.database_files

DECLARE @allocateUnits table( 
        total_pages bigint
,       used_pages bigint
,       data_pages bigint
,       container_id bigint
,       type tinyint
); 

INSERT @allocateUnits SELECT total_pages, used_pages, data_pages, container_id, type FROM sys.allocation_units;

SELECT @reservedpages = SUM(a.total_pages) 
       ,@usedpages = SUM(a.used_pages) 
       ,@pages = SUM(CASE 
                        WHEN it.internal_type IN (202,204) THEN 0 
                        WHEN a.type != 1 THEN a.used_pages 
                        WHEN p.index_id &lt; 2 THEN a.data_pages 
                        ELSE 0 
                     END) 
FROM sys.partitions p  
JOIN @allocateUnits a ON p.partition_id = a.container_id 
LEFT JOIN sys.internal_tables it ON p.object_id = it.object_id 

SELECT 
        @dbsize as ''dbsize'',
        @logsize as ''logsize'',
        @ftsize as ''ftsize'',
        @reservedpages as ''reservedpages'',
        @usedpages as ''usedpages'',
        @pages as ''pages''
</Value>
<OutputTable>disk_usage</OutputTable>
</Query>
<Databases UseSystemDatabases="true" UseUserDatabases="true" />
</ns:TSQLQueryCollector>');

-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection item detils (syscollector_collection_items) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14702;
SET @collection_item_name = FORMATMESSAGE(@name_id);
SET @collection_item_old_name = 'Disk Usage - Data Files';
SET @frequency = 60;  -- Ignored (this collection set uses non-cached collection mode)

IF ISNULL (@collection_item_name, '') = '' 
BEGIN
    SET @collection_item_name = @collection_item_old_name;
END;

SET @collection_item_id = NULL;
SELECT @collection_item_id = collection_item_id
FROM syscollector_collection_items_internal 
WHERE collection_set_id = @collection_set_id 
    AND (name = @collection_item_name OR name = @collection_item_old_name);

IF (@collection_item_id IS NOT NULL)
BEGIN
    RAISERROR ('Updating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_update_collection_item 
        @collection_item_id = @collection_item_id, 
        @new_name = @collection_item_name, 
        @frequency = @frequency, 
        @parameters = @parameters;
END
ELSE
BEGIN
    RAISERROR ('Creating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_create_collection_item
        @collection_set_id = @collection_set_id,
        @collector_type_uid = N'302E93D1-3424-4BE7-AA8E-84813ECF2419',
        @name = @collection_item_name,
        @parameters = @parameters,
        @frequency = @frequency, 
        @collection_item_id = @collection_item_id output;
END;

-- for localization of collection item name
UPDATE syscollector_collection_items_internal
SET name_id = @name_id
WHERE collection_item_id = @collection_item_id;

-- Item 2: log_usage DMV query
SELECT @parameters = convert(xml, N'<ns:TSQLQueryCollector xmlns:ns="DataCollectorType">
<Query>
<Value>
EXEC(''DBCC SQLPERF (LOGSPACE) WITH NO_INFOMSGS'')
WITH RESULT SETS(
  (
        database_name   sysname,
        log_size_mb     float,
        log_space_used  float,
        status          int
)
)
</Value>
<OutputTable>log_usage</OutputTable>
</Query>
</ns:TSQLQueryCollector>');

-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection item detils (syscollector_collection_items) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14703;
SET @collection_item_name = FORMATMESSAGE(@name_id);
SET @collection_item_old_name = 'Disk Usage - Log Files';
SET @frequency = 60;  -- Ignored (this collection set uses non-cached collection mode)

IF ISNULL (@collection_item_name, '') = '' 
BEGIN
    SET @collection_item_name = @collection_item_old_name;
END;

SET @collection_item_id = NULL;
SELECT @collection_item_id = collection_item_id
FROM syscollector_collection_items_internal 
WHERE collection_set_id = @collection_set_id 
    AND (name = @collection_item_name OR name = @collection_item_old_name);

IF (@collection_item_id IS NOT NULL)
BEGIN
    RAISERROR ('Updating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_update_collection_item 
        @collection_item_id = @collection_item_id, 
        @new_name = @collection_item_name, 
        @frequency = @frequency, 
        @parameters = @parameters;
END
ELSE
BEGIN
    RAISERROR ('Creating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_create_collection_item
        @collection_set_id = @collection_set_id,
        @collector_type_uid = N'302E93D1-3424-4BE7-AA8E-84813ECF2419',
        @name = @collection_item_name,
        @parameters = @parameters,
        @frequency = @frequency, 
        @collection_item_id = @collection_item_id output;
END;

-- for localization of collection item name
UPDATE syscollector_collection_items_internal
SET name_id = @name_id
WHERE collection_item_id = @collection_item_id;

-- Turn the is_system flag on so users can't change the definition of this collection set 
UPDATE syscollector_collection_sets
SET is_system = 1
WHERE collection_set_id = @collection_set_id
GO


------------------------------------------------
-- System collection set: Server Activity
------------------------------------------------
DECLARE @collection_set_name NVARCHAR(128);
DECLARE @description NVARCHAR(4000);
DECLARE @collection_set_id int;
DECLARE @collection_set_uid uniqueidentifier;
DECLARE @collection_mode smallint;
DECLARE @schedule_name sysname;
DECLARE @days_until_expiration smallint;
DECLARE @name_id int;
DECLARE @description_id int;

-- The GUID below identifies this collection set and is used to locate the data collected by this collection set. 
-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection set detils (syscollector_collection_sets) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14705;
SET @description_id = 14704;
SET @collection_set_uid = N'49268954-4FD4-4EB6-AA04-CD59D9BB5714';
SET @collection_mode = 0; -- Cached
SET @schedule_name = N'CollectorSchedule_Every_15min';
SET @days_until_expiration = 14;

SET @description = FORMATMESSAGE(@description_id);
SET @collection_set_name = ISNULL (FORMATMESSAGE(@name_id), 'Server Activity');

IF EXISTS (SELECT * FROM dbo.syscollector_collection_sets_internal WHERE collection_set_uid = @collection_set_uid)
BEGIN
    RAISERROR ('Updating system Collection Set "%s"...', 0, 1, @collection_set_name) WITH NOWAIT;
    -- We are updating an existing collection set -- get its ID 
    SELECT @collection_set_id = collection_set_id
    FROM syscollector_collection_sets 
    WHERE collection_set_uid = @collection_set_uid;
    
    -- Temporarily clear the is_system flag so that we can modify the collection set definition
    UPDATE syscollector_collection_sets
    SET is_system = 0
    WHERE collection_set_id = @collection_set_id
    
    -- Don't override the current expiration period or schedule settings, since the user may have customized these
    EXEC dbo.sp_syscollector_update_collection_set
        @collection_set_id = @collection_set_id, 
        @new_name = @collection_set_name, 
        @schedule_name = @schedule_name, -- avoid mismatch in uid if schedule was deleted and recreated
        @collection_mode = @collection_mode, 
        @logging_level = 0,
        @description = @description;
END
ELSE
BEGIN    
    RAISERROR ('Creating system Collection Set "%s"...', 0, 1, @collection_set_name) WITH NOWAIT;
    EXEC dbo.sp_syscollector_create_collection_set
        @collection_set_uid = @collection_set_uid,
        @name = @collection_set_name, 
        @schedule_name = @schedule_name, 
        @collection_mode = @collection_mode, 
        @days_until_expiration = @days_until_expiration, 
        @description = @description, 
        @logging_level = 0,
        @collection_set_id = @collection_set_id OUTPUT;
END

-- for localization of collection set name and description
UPDATE syscollector_collection_sets_internal
SET name_id = @name_id, description_id = @description_id
WHERE collection_set_uid = @collection_set_uid;

-- Add collection items
DECLARE @collection_item_name NVARCHAR(128);
DECLARE @collection_item_old_name NVARCHAR(128);
DECLARE @collection_item_id int;
DECLARE @frequency int;
DECLARE @parameters xml;

-- Item 1 - DMV SNAPSHOTS
SELECT @parameters = convert(xml, N'<ns:TSQLQueryCollector xmlns:ns="DataCollectorType">
<Query>
<Value>
SET NOCOUNT ON
SELECT 
    LEFT (wait_type, 45) AS wait_type, 
    SUM (waiting_tasks_count) AS waiting_tasks_count, 
    SUM (wait_time_ms) AS wait_time_ms, 
    SUM (signal_wait_time_ms) AS signal_wait_time_ms
FROM 
(
    SELECT 
        LEFT (wait_type, 45) AS wait_type, 
    waiting_tasks_count, 
    wait_time_ms,  
    signal_wait_time_ms
FROM sys.dm_os_wait_stats 
WHERE waiting_tasks_count &gt; 0 OR wait_time_ms &gt; 0 OR signal_wait_time_ms &gt; 0
    UNION ALL 
    SELECT 
        LEFT (wait_type, 45) AS wait_type, 
        1 AS waiting_tasks_count, 
        wait_duration_ms AS wait_time_ms, 
        0 AS signal_wait_time_ms
    FROM sys.dm_os_waiting_tasks
    WHERE wait_duration_ms &gt; 60000
) AS merged_wait_stats
GROUP BY wait_type
</Value>
<OutputTable>os_wait_stats</OutputTable>
</Query>
<Query>
<Value>
SET NOCOUNT ON
SELECT 
  LEFT(latch_class,45) as latch_class,
  waiting_requests_count,
  wait_time_ms
FROM sys.dm_os_latch_stats 
WHERE waiting_requests_count &gt; 0 OR wait_time_ms &gt; 0
</Value>
<OutputTable>os_latch_stats</OutputTable>
</Query>
<Query>
<Value>
SET NOCOUNT ON
SELECT 
    pm.physical_memory_in_use_kb            AS sql_physical_memory_in_use_kb, 
    pm.large_page_allocations_kb            AS sql_large_page_allocations_kb, 
    pm.locked_page_allocations_kb           AS sql_locked_page_allocations_kb, 
    pm.total_virtual_address_space_kb       AS sql_total_virtual_address_space_kb, 
    pm.virtual_address_space_reserved_kb    AS sql_virtual_address_space_reserved_kb, 
    pm.virtual_address_space_committed_kb   AS sql_virtual_address_space_committed_kb, 
    pm.virtual_address_space_available_kb   AS sql_virtual_address_space_available_kb, 
    pm.page_fault_count                     AS sql_page_fault_count, 
    pm.memory_utilization_percentage        AS sql_memory_utilization_percentage, 
    pm.available_commit_limit_kb            AS sql_available_commit_limit_kb, 
    pm.process_physical_memory_low          AS sql_process_physical_memory_low, 
    pm.process_virtual_memory_low           AS sql_process_virtual_memory_low, 
    
    sm.total_physical_memory_kb             AS system_total_physical_memory_kb, 
    sm.available_physical_memory_kb         AS system_available_physical_memory_kb, 
    sm.total_page_file_kb                   AS system_total_page_file_kb, 
    sm.available_page_file_kb               AS system_available_page_file_kb, 
    sm.system_cache_kb                      AS system_cache_kb, 
    sm.kernel_paged_pool_kb                 AS system_kernel_paged_pool_kb, 
    sm.kernel_nonpaged_pool_kb              AS system_kernel_nonpaged_pool_kb, 
    sm.system_high_memory_signal_state      AS system_high_memory_signal_state, 
    sm.system_low_memory_signal_state       AS system_low_memory_signal_state, 
    
    -- Three columns were removed from the dm_os_sys_info DMV in SQL11 as part of a change 
    -- to memory manager architecture: bpool_committed, bpool_commit_target, and bpool_visible.  
    -- While it is no longer correct, strictly speaking, to speak of such things as "buffer 
    -- pool target" memory in SQL Server versions after SQL 2008 R2, in the MDW database these 
    -- values were simply used as a proxy for "SQL Server committed memory" and "SQL Server 
    -- target memory"; the fact that it used to be buffer pool but now is the memory mgr that 
    -- tracks these values is immaterial to how they are used in MDW.  There are three new 
    -- columns that were added in versions after SQL 2008 R2; we map the new column that provides 
    -- comparable information to the old SQL 2008/2008 R2 column name.  This allows the same MDW 
    -- schema to work with data from any supported version.  The old DMV values were counts of 
    -- 8KB pages; the replacement columns provide a KB count. 
    (si.committed_target_kb / 8)            AS bpool_commit_target, 
    (si.committed_kb / 8)                   AS bpool_committed, 
    (si.visible_target_kb / 8)              AS bpool_visible
FROM sys.dm_os_process_memory AS pm
CROSS JOIN sys.dm_os_sys_memory AS sm   -- single-row DMV
CROSS JOIN sys.dm_os_sys_info AS si;    -- single-row DMV
</Value>
<OutputTable>sql_process_and_system_memory</OutputTable>
</Query>
<Query>
<Value>
SET NOCOUNT ON
SELECT 
    memory_node_id, 
    virtual_address_space_reserved_kb, 
    virtual_address_space_committed_kb, 
    locked_page_allocations_kb, 
    -- In SQL 2008 and 2008 R2, we collected the [single_pages_kb] and [multi_pages_kb] columns 
    -- from this DMV. In later versions, the memory manager has been changed so that the fundamental 
    -- distinction between single-page and multi-page allocations no longer exists.  As a result, 
    -- these two columns were removed, and a "pages_kb" column was added instead.  In the MDW 
    -- database, these two columns were just added together to calculate total memory allocated 
    -- by the memory node.  We don''t want to remove these columns from the destination table since 
    -- that would break all SQL2008/2008R2 clients until they could be patched.  So, to maximize 
    -- backwards compatibility and minimize the size of this change, we simply return the new 
    -- [pages_kb] value in the existing [single_pages_kb] column.  This isn''t an entirely 
    -- accurate name for the value, but it doesn''t affect the way that this value is used after 
    -- it has been uploaded to MDW. 
    pages_kb AS single_pages_kb, 
    0 AS multi_pages_kb, 
    shared_memory_reserved_kb, 
    shared_memory_committed_kb
FROM sys.dm_os_memory_nodes
</Value>
<OutputTable>os_memory_nodes</OutputTable>
</Query>
<Query>
<Value>
SET NOCOUNT ON
SELECT 
    type,
    memory_node_id as memory_node_id,
    -- See comment in the sys.dm_os_memory_nodes query (above) for more info on 
    -- [single_pages_kb] and [multi_pages_kb]. 
    SUM(pages_kb) as single_pages_kb,
    0 as multi_pages_kb,
    SUM(virtual_memory_reserved_kb) as virtual_memory_reserved_kb,
    SUM(virtual_memory_committed_kb) as virtual_memory_committed_kb,
    SUM(awe_allocated_kb) as awe_allocated_kb,
    SUM(shared_memory_reserved_kb) as shared_memory_reserved_kb,
    SUM(shared_memory_committed_kb) as shared_memory_committed_kb
FROM sys.dm_os_memory_clerks
GROUP BY type, memory_node_id</Value>
<OutputTable>os_memory_clerks</OutputTable>
</Query>
<Query>
<Value>
SET NOCOUNT ON
SELECT 
    [parent_node_id],
    [scheduler_id],
    [cpu_id],
    [status],
    [is_idle],
    [preemptive_switches_count],
    [context_switches_count],
    [yield_count],
    [current_tasks_count],
    [runnable_tasks_count],
    [work_queue_count],
    [pending_disk_io_count]
FROM sys.dm_os_schedulers
WHERE scheduler_id &lt; 128
</Value>
<OutputTable>os_schedulers</OutputTable>
</Query>
<Query>
<Value>
SELECT 
    DB_NAME (f.database_id) AS database_name, f.database_id, f.name AS logical_file_name, f.[file_id], f.type_desc, 
    CAST (CASE 
        -- Handle UNC paths (e.g. ''\\fileserver\readonlydbs\dept_dw.ndf'' --&gt; ''\\fileserver\readonlydbs'')
        WHEN LEFT (LTRIM (f.physical_name), 2) = ''\\'' 
            THEN LEFT (LTRIM (f.physical_name), CHARINDEX (''\'', LTRIM (f.physical_name), CHARINDEX (''\'', LTRIM (f.physical_name), 3) + 1) - 1)
        -- Handle local paths (e.g. ''C:\Program Files\...\master.mdf'' --&gt; ''C:'') 
        WHEN CHARINDEX (''\'', LTRIM(f.physical_name), 3) &gt; 0 
            THEN UPPER (LEFT (LTRIM (f.physical_name), CHARINDEX (''\'', LTRIM (f.physical_name), 3) - 1))
        ELSE f.physical_name
    END AS nvarchar(255)) AS logical_disk, 
    fs.num_of_reads, fs.num_of_bytes_read, fs.io_stall_read_ms, fs.num_of_writes, fs.num_of_bytes_written, 
    fs.io_stall_write_ms, fs.size_on_disk_bytes
FROM sys.dm_io_virtual_file_stats (default, default) AS fs
INNER JOIN sys.master_files AS f ON fs.database_id = f.database_id AND fs.[file_id] = f.[file_id]
</Value>
<OutputTable>io_virtual_file_stats</OutputTable>
</Query>
</ns:TSQLQueryCollector>');

-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection item detils (syscollector_collection_items) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14706;
SET @collection_item_name = FORMATMESSAGE(@name_id);
SET @collection_item_old_name = 'Server Activity - DMV Snapshots';
SET @frequency = 60;  

IF ISNULL (@collection_item_name, '') = '' 
BEGIN
    SET @collection_item_name = @collection_item_old_name;
END;

SET @collection_item_id = NULL;
SELECT @collection_item_id = collection_item_id
FROM syscollector_collection_items_internal 
WHERE collection_set_id = @collection_set_id 
    AND (name = @collection_item_name OR name = @collection_item_old_name);

IF (@collection_item_id IS NOT NULL)
BEGIN
    RAISERROR ('Updating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_update_collection_item 
        @collection_item_id = @collection_item_id, 
        @new_name = @collection_item_name, 
        @frequency = @frequency, 
        @parameters = @parameters;
END
ELSE
BEGIN
    RAISERROR ('Creating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_create_collection_item
        @collection_set_id = @collection_set_id,
        @collector_type_uid = N'302E93D1-3424-4BE7-AA8E-84813ECF2419',
        @name = @collection_item_name,
        @parameters = @parameters,
        @frequency = @frequency, 
        @collection_item_id = @collection_item_id output;
END;

-- for localization of collection item name
UPDATE syscollector_collection_items_internal
SET name_id = @name_id
WHERE collection_item_id = @collection_item_id;

-- Item 2 - PERFORMANCE COUNTERS
SELECT @parameters = convert(xml, N'<ns:PerformanceCountersCollector xmlns:ns="DataCollectorType">
 <PerformanceCounters Objects="Memory" Counters="% Committed Bytes In Use" />
 <PerformanceCounters Objects="Memory" Counters="Available Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Cache Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Cache Faults/sec" />
 <PerformanceCounters Objects="Memory" Counters="Committed Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Free &amp; Zero Page List Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Modified Page List Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Pages/sec" />
 <PerformanceCounters Objects="Memory" Counters="Page Reads/sec" />
 <PerformanceCounters Objects="Memory" Counters="Page Writes/sec" />
 <PerformanceCounters Objects="Memory" Counters="Page Faults/sec" />
 <PerformanceCounters Objects="Memory" Counters="Pool Nonpaged Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Pool Paged Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Standby Cache Core Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Standby Cache Normal Priority Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Standby Cache Reserve Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Pool Paged Bytes" />
 <PerformanceCounters Objects="Memory" Counters="Write Copies/sec" />
 <PerformanceCounters Objects="Process" Counters="*" Instances="_Total" />
 <PerformanceCounters Objects="Process" Counters="*" Instances="$(TARGETPROCESS)" />
 <PerformanceCounters Objects="Process" Counters="Thread Count" Instances="*" />
 <PerformanceCounters Objects="Process" Counters="% Processor Time" Instances="*" />
 <PerformanceCounters Objects="Process" Counters="IO Read Bytes/sec" Instances="*" />
 <PerformanceCounters Objects="Process" Counters="IO Write Bytes/sec" Instances="*" />
 <PerformanceCounters Objects="Process" Counters="Private Bytes" Instances="*" />
 <PerformanceCounters Objects="Process" Counters="Working Set" Instances="*" />
 <PerformanceCounters Objects="Processor" Counters="% Processor Time" Instances="*" />
 <PerformanceCounters Objects="Processor" Counters="% User Time" Instances="*" />
 <PerformanceCounters Objects="Processor" Counters="% Privileged Time" Instances="*" />
 <PerformanceCounters Objects="Server Work Queues" Counters="Queue Length" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="% Disk Time" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Avg. Disk Queue Length" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Avg. Disk Read Queue Length" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Avg. Disk Write Queue Length" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Avg. Disk sec/Read" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Avg. Disk sec/Write" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Avg. Disk sec/Transfer" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Disk Reads/sec" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Disk Bytes/sec" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Disk Writes/sec" Instances="*" />
 <PerformanceCounters Objects="LogicalDisk" Counters="Split IO/sec" Instances="*" />
 <PerformanceCounters Objects="System" Counters="Processor Queue Length" />
 <PerformanceCounters Objects="System" Counters="File Read Operations/sec" />
 <PerformanceCounters Objects="System" Counters="File Write Operations/sec" />
 <PerformanceCounters Objects="System" Counters="File Control Operations/sec" />
 <PerformanceCounters Objects="System" Counters="File Read Bytes/sec" />
 <PerformanceCounters Objects="System" Counters="File Write Bytes/sec" />
 <PerformanceCounters Objects="System" Counters="File Control Bytes/sec" />
 <PerformanceCounters Objects="Network Interface" Counters="Bytes Total/sec" Instances="*" />
 <PerformanceCounters Objects="Network Interface" Counters="Output Queue Length" Instances="*" />
 <PerformanceCounters Objects="$(INSTANCE):Buffer Manager" Counters="Stolen pages"/>
 <PerformanceCounters Objects="$(INSTANCE):Buffer Manager" Counters="Page life expectancy"/>
 <PerformanceCounters Objects="$(INSTANCE):Memory Manager" Counters="Memory Grants Outstanding"/>
 <PerformanceCounters Objects="$(INSTANCE):Memory Manager" Counters="Memory Grants Pending"/>
 <PerformanceCounters Objects="$(INSTANCE):Databases" Counters="Transactions/sec"  Instances="_Total"/>
 <PerformanceCounters Objects="$(INSTANCE):Databases" Counters="Transactions/sec"  Instances="tempdb"/>
 <PerformanceCounters Objects="$(INSTANCE):Databases" Counters="Active Transactions"  Instances="*"/>
 <PerformanceCounters Objects="$(INSTANCE):General Statistics" Counters="Logins/sec"  />
 <PerformanceCounters Objects="$(INSTANCE):General Statistics" Counters="Logouts/sec"  />
 <PerformanceCounters Objects="$(INSTANCE):General Statistics" Counters="User Connections"  />
 <PerformanceCounters Objects="$(INSTANCE):General Statistics" Counters="Logical Connections"  />
 <PerformanceCounters Objects="$(INSTANCE):General Statistics" Counters="Transactions"  />
 <PerformanceCounters Objects="$(INSTANCE):General Statistics" Counters="Processes blocked"  />
 <PerformanceCounters Objects="$(INSTANCE):General Statistics" Counters="Active Temp Tables"  />
 <PerformanceCounters Objects="$(INSTANCE):SQL Statistics" Counters="Batch Requests/sec" />
 <PerformanceCounters Objects="$(INSTANCE):SQL Statistics" Counters="SQL Compilations/sec" />
 <PerformanceCounters Objects="$(INSTANCE):SQL Statistics" Counters="SQL Re-Compilations/sec" />
 <PerformanceCounters Objects="$(INSTANCE):SQL Statistics" Counters="SQL Attention rate" />
 <PerformanceCounters Objects="$(INSTANCE):SQL Statistics" Counters="Auto-Param Attempts/sec" />
 <PerformanceCounters Objects="$(INSTANCE):SQL Statistics" Counters="Failed Auto-Params/sec" />
 <PerformanceCounters Objects="$(INSTANCE):Plan Cache" Counters="Cache Hit Ratio" Instances="_Total" />
 <PerformanceCounters Objects="$(INSTANCE):Plan Cache" Counters="Cache Hit Ratio" Instances="Object Plans" />
 <PerformanceCounters Objects="$(INSTANCE):Plan Cache" Counters="Cache Hit Ratio" Instances="SQL Plans" />
 <PerformanceCounters Objects="$(INSTANCE):Plan Cache" Counters="Cache Hit Ratio" Instances="Temporary Tables &amp; Table Variables" />
 <PerformanceCounters Objects="$(INSTANCE):Transactions" Counters="Free Space in tempdb (KB)"/>
 <PerformanceCounters Objects="$(INSTANCE):Workload Group Stats" Counters="Active requests" Instances="*"/>
 <PerformanceCounters Objects="$(INSTANCE):Workload Group Stats" Counters="Blocked tasks" Instances="*"/>
 <PerformanceCounters Objects="$(INSTANCE):Workload Group Stats" Counters="CPU usage %" Instances="*"/>
</ns:PerformanceCountersCollector>');

-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection item detils (syscollector_collection_items) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14707;
SET @collection_item_name = FORMATMESSAGE(@name_id);
SET @collection_item_old_name = 'Server Activity - Performance Counters';
SET @frequency = 60;  

IF ISNULL (@collection_item_name, '') = '' 
BEGIN
    SET @collection_item_name = @collection_item_old_name;
END;

SET @collection_item_id = NULL;
SELECT @collection_item_id = collection_item_id
FROM syscollector_collection_items_internal 
WHERE collection_set_id = @collection_set_id 
    AND (name = @collection_item_name OR name = @collection_item_old_name);

IF (@collection_item_id IS NOT NULL)
BEGIN
    RAISERROR ('Updating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_update_collection_item 
        @collection_item_id = @collection_item_id, 
        @new_name = @collection_item_name, 
        @frequency = @frequency, 
        @parameters = @parameters;
END
ELSE
BEGIN
    RAISERROR ('Creating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_create_collection_item
        @collection_set_id = @collection_set_id,
        @collector_type_uid = N'294605DD-21DE-40B2-B20F-F3E170EA1EC3',
        @name = @collection_item_name,
        @parameters = @parameters,
        @frequency = @frequency, 
        @collection_item_id = @collection_item_id output;
END;

-- for localization of collection item name
UPDATE syscollector_collection_items_internal
SET name_id = @name_id
WHERE collection_item_id = @collection_item_id;

-- Turn the is_system flag on so users can't change the definition of this collection set 
UPDATE syscollector_collection_sets
SET is_system = 1
WHERE collection_set_id = @collection_set_id
GO


------------------------------------------------
-- System collection set: Query Statistics
------------------------------------------------
DECLARE @collection_set_name NVARCHAR(128);
DECLARE @description NVARCHAR(4000);
DECLARE @collection_set_id int;
DECLARE @collection_set_uid uniqueidentifier;
DECLARE @collection_mode smallint;
DECLARE @schedule_name sysname;
DECLARE @days_until_expiration smallint;
DECLARE @name_id int;
DECLARE @description_id int;

-- The GUID below identifies this collection set and is used to locate the data collected by this collection set. 
-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection set detils (syscollector_collection_sets) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14709;
SET @description_id = 14708;
SET @collection_set_uid = N'2DC02BD6-E230-4C05-8516-4E8C0EF21F95';
SET @collection_mode = 0; -- Cached
SET @schedule_name = N'CollectorSchedule_Every_15min';
SET @days_until_expiration = 14;

SET @description = FORMATMESSAGE(@description_id);
SET @collection_set_name = ISNULL (FORMATMESSAGE(@name_id), 'Query Statistics');

IF EXISTS (SELECT * FROM dbo.syscollector_collection_sets_internal WHERE collection_set_uid = @collection_set_uid)
BEGIN
    RAISERROR ('Updating system Collection Set "%s"...', 0, 1, @collection_set_name) WITH NOWAIT;
    -- We are updating an existing collection set -- get its ID 
    SELECT @collection_set_id = collection_set_id
    FROM syscollector_collection_sets 
    WHERE collection_set_uid = @collection_set_uid;
    
    -- Temporarily clear the is_system flag so that we can modify the collection set definition
    UPDATE syscollector_collection_sets
    SET is_system = 0
    WHERE collection_set_id = @collection_set_id
    
    -- Don't override the current expiration period or schedule settings, since the user may have customized these
    EXEC dbo.sp_syscollector_update_collection_set
        @collection_set_id = @collection_set_id, 
        @new_name = @collection_set_name, 
        @schedule_name = @schedule_name, -- avoid mismatch in uid if schedule was deleted and recreated
        @collection_mode = @collection_mode, 
        @logging_level = 0,
        @description = @description;
END
ELSE
BEGIN    
    RAISERROR ('Creating system Collection Set "%s"...', 0, 1, @collection_set_name) WITH NOWAIT;
    EXEC dbo.sp_syscollector_create_collection_set
        @collection_set_uid = @collection_set_uid,
        @name = @collection_set_name, 
        @schedule_name = @schedule_name,
        @collection_mode = @collection_mode, 
        @days_until_expiration = @days_until_expiration, 
        @description = @description, 
        @logging_level = 0,
        @collection_set_id = @collection_set_id OUTPUT;
END

-- for localization of collection set name and description
UPDATE syscollector_collection_sets_internal
SET name_id = @name_id, description_id = @description_id
WHERE collection_set_uid = @collection_set_uid;

-- Add collection items
DECLARE @collection_item_name NVARCHAR(128);
DECLARE @collection_item_old_name NVARCHAR(128);
DECLARE @collection_item_id int;
DECLARE @frequency int;
DECLARE @parameters xml;

SELECT @parameters = convert(xml, N'<ns:QueryActivityCollector xmlns:ns="DataCollectorType">
<Databases IncludeSystemDatabases="true" />
</ns:QueryActivityCollector>');

-- Item 1 - Query activity collection type
-- The name and description retrived via FORMATMESSAGE will not be localized for the initial mkmastr build of msdb, 
-- but that is OK; the public interface to collection item detils (syscollector_collection_items) is a view that will 
-- dynamically pull the correct localized strings via its own FORMATMESSAGE calls.  We just need a unique string to 
-- store in the table.
SET @name_id = 14710;
SET @collection_item_name = FORMATMESSAGE(@name_id);
SET @collection_item_old_name = 'Query Statistics - Query Activity';
SET @frequency = 10;  

IF ISNULL (@collection_item_name, '') = '' 
BEGIN
    SET @collection_item_name = @collection_item_old_name;
END;

SET @collection_item_id = NULL;
SELECT @collection_item_id = collection_item_id
FROM syscollector_collection_items_internal 
WHERE collection_set_id = @collection_set_id 
    AND (name = @collection_item_name OR name = @collection_item_old_name);

IF (@collection_item_id IS NOT NULL)
BEGIN
    RAISERROR ('Updating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_update_collection_item 
        @collection_item_id = @collection_item_id, 
        @new_name = @collection_item_name, 
        @frequency = @frequency, 
        @parameters = @parameters;
END
ELSE
BEGIN
    RAISERROR ('Creating Collection Item "%s"...', 0, 1, @collection_item_name);
    EXEC dbo.sp_syscollector_create_collection_item
        @collection_set_id = @collection_set_id,
        @collector_type_uid = N'14AF3C12-38E6-4155-BD29-F33E7966BA23',
        @name = @collection_item_name,
        @parameters = @parameters,
        @frequency = @frequency, 
        @collection_item_id = @collection_item_id output;
END;

-- for localization of collection item name
UPDATE syscollector_collection_items_internal
SET name_id = @name_id
WHERE collection_item_id = @collection_item_id;

-- Turn the is_system flag on so users can't change the definition of this collection set 
UPDATE syscollector_collection_sets
SET is_system = 1
WHERE collection_set_id = @collection_set_id
GO

-- End of installation of out-of-the-box collector components
-- Restore agent xp settings to original state
DECLARE @advopt_old_value int
DECLARE @comp_old_value int
SELECT @advopt_old_value = advopt_old_value FROM #advopt_old_value
SELECT @comp_old_value = comp_old_value FROM #comp_old_value

EXECUTE #sp_restore_component_state 'Agent XPs', @advopt_old_value, @comp_old_value

DROP TABLE #advopt_old_value
DROP TABLE #comp_old_value
---------------------------------------------------------------
-- Data Collector: Security: Permissions
---------------------------------------------------------------

PRINT ''
PRINT 'Granting permissions to data collector roles...'

GRANT SELECT  ON [dbo].[syscollector_config_store]                                TO [dc_operator], [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_enable_collector]                        TO [dc_operator]
GRANT EXECUTE ON [dbo].[sp_syscollector_disable_collector]                        TO [dc_operator]
GRANT EXECUTE ON [dbo].[sp_syscollector_set_warehouse_instance_name]            TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_set_warehouse_database_name]            TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_set_cache_window]                        TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_set_cache_directory]                    TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_get_warehouse_connection_string]        TO [dc_proxy]
GRANT EXECUTE ON [dbo].[fn_syscollector_highest_incompatible_mdw_version]		TO [dc_admin], [dc_proxy]

GRANT SELECT  ON [dbo].[syscollector_collector_types]                            TO [dc_operator], [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_create_collector_type]                    TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_delete_collector_type]                    TO [dc_admin]

GRANT SELECT  ON [dbo].[syscollector_collection_sets]                            TO [dc_operator], [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_cleanup_collector]                       TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_create_collection_set]                    TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_delete_collection_set]                    TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_update_collection_set]                    TO [dc_operator]
GRANT EXECUTE ON [dbo].[sp_syscollector_start_collection_set]                    TO [dc_operator]
GRANT EXECUTE ON [dbo].[sp_syscollector_stop_collection_set]                    TO [dc_operator]
GRANT EXECUTE ON [dbo].[sp_syscollector_upload_collection_set]                    TO [dc_operator]
GRANT EXECUTE ON [dbo].[sp_syscollector_run_collection_set]                        TO [dc_operator]

GRANT SELECT  ON [dbo].[syscollector_collection_items]                            TO [dc_operator], [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_create_collection_item]                    TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_delete_collection_item]                    TO [dc_admin]
GRANT EXECUTE ON [dbo].[sp_syscollector_update_collection_item]                    TO [dc_operator]

GRANT SELECT  ON [dbo].[syscollector_execution_log]                                TO [dc_operator]
GRANT SELECT  ON [dbo].[syscollector_execution_log_full]                        TO [dc_operator]
GRANT SELECT  ON [dbo].[syscollector_execution_stats]                            TO [dc_operator]

GRANT EXECUTE ON [dbo].[fn_syscollector_find_collection_set_root]                TO [dc_operator]
GRANT SELECT  ON [dbo].[fn_syscollector_get_execution_log_tree]                    TO [dc_operator]
GRANT SELECT  ON [dbo].[fn_syscollector_get_execution_stats]                    TO [dc_operator]
GRANT SELECT  ON [dbo].[fn_syscollector_get_execution_details]                    TO [dc_operator]
GRANT EXECUTE ON [dbo].[sp_syscollector_delete_execution_log_tree]                TO [dc_operator]

GRANT EXECUTE ON [dbo].[sp_syscollector_event_oncollectionbegin]                TO [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_event_oncollectionend]                    TO [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_event_onpackagebegin]                    TO [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_event_onpackageend]                        TO [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_event_onpackageupdate]                    TO [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_event_onerror]                            TO [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_event_onstatsupdate]                    TO [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_snapshot_dm_exec_requests]              TO [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_snapshot_dm_exec_query_stats]           TO [dc_proxy]

GRANT EXECUTE ON [dbo].[sp_syscollector_create_tsql_query_collector]            TO [dc_operator], [dc_proxy]
GRANT EXECUTE ON [dbo].[sp_syscollector_get_tsql_query_collector_package_ids]   TO [dc_operator], [dc_proxy]

GRANT EXECUTE ON [dbo].[sp_verify_subsystems]                                   TO [dc_operator]
---------------------------------------------------------------
-- Relational storage for DMF objects
---------------------------------------------------------------

CREATE TABLE #objects_to_drop(
    type nvarchar(128),
    name sysname
)

-- List of shared registered server objects to be deleted from msdb. Note: order is important!
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_verify_shared_server_type]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_update_shared_registered_server]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_rename_shared_registered_server]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_update_shared_server_group]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_rename_shared_server_group]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_move_shared_registered_server]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_delete_shared_registered_server]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_move_shared_server_group]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_delete_shared_server_group]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_add_shared_registered_server]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_sysmanagement_add_shared_server_group]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',              '[dbo].[sysmanagement_shared_registered_servers]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',              '[dbo].[sysmanagement_shared_server_groups]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',         '[dbo].[sysmanagement_delete_shared_server_group_trigger]')

-- List of policy management objects to be deleted from msdb. Note: order is important!
INSERT INTO #objects_to_drop VALUES ('TRIGGER',           '[dbo].[syspolicy_insert_target_set_level_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',           '[dbo].[syspolicy_update_target_set_level_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',           '[dbo].[syspolicy_insert_target_set_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',           '[dbo].[syspolicy_delete_target_set_trigger]' )
INSERT INTO #objects_to_drop VALUES ('FUNCTION',        '[dbo].[syspolicy_fn_filter_complete]' )
INSERT INTO #objects_to_drop VALUES ('FUNCTION',        '[dbo].[syspolicy_fn_eventing_filter]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_add_target_set_condition_reference]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_update_target_set_condition_reference]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',             '[dbo].[syspolicy_target_set_condition_references]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_add_target_set_level]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_update_target_set_level]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',             '[dbo].[syspolicy_target_set_levels]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_verify_object_set_identifiers]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_verify_object_set_references]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_add_target_set]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_delete_target_set]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_update_target_set]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_add_object_set]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',          '[dbo].[sp_syspolicy_delete_object_set]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',             '[dbo].[syspolicy_target_sets]' )

INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_events_reader]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_dispatch_event]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_delete_policy_execution_history]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_log_policy_execution_detail]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_log_policy_execution_end]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_log_policy_execution_start]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',      		'[dbo].[syspolicy_policy_execution_history_details]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',      		'[dbo].[syspolicy_policy_execution_history]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',    		'[dbo].[syspolicy_update_system_health_state]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',      		'[dbo].[syspolicy_system_health_state]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_delete_policy_category_subscription]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_update_policy_category_subscription]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_add_policy_category_subscription]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',      		'[dbo].[syspolicy_policy_category_subscriptions]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_delete_policy]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_update_policy]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_rename_policy]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_verify_policy_identifiers]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_add_policy]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_rename_policy_category]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_update_policy_category]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_delete_policy_category]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_add_policy_category]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_verify_policy_category_identifiers]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',      		'[dbo].[syspolicy_policies]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_create_job]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_delete_job_delete_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_update_policy_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_insert_policy_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_update_job_update_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_insert_job_create_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_instead_delete_policy_trigger]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',      		'[dbo].[syspolicy_policy_categories]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_rename_condition]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_delete_condition]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_update_condition]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_verify_condition_identifiers]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE', 		'[dbo].[sp_syspolicy_add_condition]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',      		'[dbo].[syspolicy_conditions]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',    		'[dbo].[syspolicy_insert_condition_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',    		'[dbo].[syspolicy_update_condition_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_for_update_condition_trigger]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',   		'[dbo].[syspolicy_after_update_condition_trigger]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',     		'[dbo].[syspolicy_object_sets]' )
INSERT INTO #objects_to_drop VALUES ('FUNCTION',  		'[dbo].[syspolicy_fn_get_type_name]' )
INSERT INTO #objects_to_drop VALUES ('INLINE FUNCTION',  		'[dbo].[syspolicy_fn_get_bad_filters]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  		'[dbo].[sp_syspolicy_check_membership]' )
INSERT INTO #objects_to_drop VALUES ('FUNCTION',  		'[dbo].[fn_syspolicy_get_ps_command]' )
INSERT INTO #objects_to_drop VALUES ('VIEW',            '[dbo].[syspolicy_configuration]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_configure]' )
INSERT INTO #objects_to_drop VALUES ('FUNCTION',  	    '[dbo].[fn_syspolicy_is_automation_enabled]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_set_config_enabled]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_repair_policy_automation]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_purge_history]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_set_config_history_retention]' )
INSERT INTO #objects_to_drop VALUES ('TRIGGER',  	    '[dbo].[syspolicy_validate_events]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_create_purge_job]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_purge_health_state]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_set_log_on_success]' )
INSERT INTO #objects_to_drop VALUES ('PROCEDURE',  	    '[dbo].[sp_syspolicy_mark_system]' )

GO

DECLARE @object_name sysname
DECLARE @object_type nvarchar(128)

DECLARE object_to_drop_cursor CURSOR LOCAL FOR SELECT type, name FROM #objects_to_drop

OPEN object_to_drop_cursor
FETCH object_to_drop_cursor INTO @object_type, @object_name

WHILE @@FETCH_STATUS = 0 
BEGIN
    DECLARE @stmt nvarchar(128)
    DECLARE @object_kind nvarchar(2)

    SET @object_kind = 
        CASE @object_type
            WHEN 'VIEW'       THEN 'V'
            WHEN 'PROCEDURE'  THEN 'P'
            WHEN 'TRIGGER'    THEN 'TR'
            WHEN 'FUNCTION'    THEN 'FN'
            WHEN 'INLINE FUNCTION'    THEN 'IF'
            WHEN 'TABLE TYPE'    THEN 'TT'
            ELSE NULL
        END

    IF @object_kind IS NULL
    BEGIN
        DECLARE @errtxt nvarchar(max)
        SET @errtxt = 'Incorrect object type "' + @object_type + '" for object "' + @object_name + '", check "INSERT INTO #objects_to_drop..." statement'
        RAISERROR(@errtxt, 20, 127) WITH LOG
    END

    IF (OBJECT_ID(@object_name, @object_kind) IS NULL)
        PRINT 'Object "' + @object_name + N'" does not exist, will not drop'
    ELSE
    BEGIN
        IF( @object_type = 'INLINE FUNCTION')
            SET @object_type = 'FUNCTION'
        SET @stmt = N'drop ' + @object_type + N' ' + @object_name
        PRINT 'Executing "' + @stmt + N'"'
        EXECUTE(@stmt)
    END
    FETCH object_to_drop_cursor INTO @object_type, @object_name
END

CLOSE object_to_drop_cursor
DEALLOCATE object_to_drop_cursor
DROP TABLE #objects_to_drop

PRINT 'Done dropping all DMF and Shared Registered Server procedures'
GO


--------------------------------------------------------------
-- This section contains shared registered servers information
--------------------------------------------------------------

IF NOT EXISTS (SELECT * FROM sys.tables where name ='sysmanagement_shared_server_groups_internal')
BEGIN
	PRINT 'Creating table [msdb].[dbo].[sysmanagement_shared_server_groups_internal]...'
	CREATE TABLE [msdb].[dbo].[sysmanagement_shared_server_groups_internal]
	(
		server_group_id INT NOT NULL IDENTITY PRIMARY KEY NONCLUSTERED,
		name sysname NOT NULL,
		description NVARCHAR (2048) NOT NULL,
		-- You can only have a registered server of the same type within a group of the same type
		-- So the group needs to have knowledge of its type
		server_type INT NOT NULL,
		-- Explicitly allow NULLs for this column, so we are independent of server configuration
		parent_id INT NULL,
		-- this flag indicates whether the group is a system builtin group
		is_system_object BIT DEFAULT 0,
		-- Make sure each name is unique per parent
		CONSTRAINT [UQ_sysmanagement_unique_group_name_per_parent] UNIQUE(parent_id, name)
	);

    CREATE CLUSTERED    INDEX [IX_sysmanagement_shared_server_groups_clustParentGroupID] ON
                [dbo].[sysmanagement_shared_server_groups_internal] (parent_id);
    CREATE NONCLUSTERED INDEX [IX_sysmanagement_shared_server_groups_name] ON
                [dbo].[sysmanagement_shared_server_groups_internal] (name)

    -- populate with the builtin server groups
    -- Note: server_type_id values must match the ServerType enumeration
    INSERT INTO [msdb].[dbo].[sysmanagement_shared_server_groups_internal] (name, description, server_type, parent_id, is_system_object)
    VALUES (N'DatabaseEngineServerGroup', N'Builtin group that contains the DatabaseEngine servers', 0, null, 1);

    INSERT INTO [msdb].[dbo].[sysmanagement_shared_server_groups_internal] (name, description, server_type, parent_id, is_system_object)
    VALUES (N'AnalysisServicesServerGroup', N'Builtin group that contains the AnalysisServices servers', 1, null, 1);

    INSERT INTO [msdb].[dbo].[sysmanagement_shared_server_groups_internal] (name, description, server_type, parent_id, is_system_object)
    VALUES (N'ReportingServicesServerGroup', N'Builtin group that contains the ReportingServices servers', 2, null, 1);

    INSERT INTO [msdb].[dbo].[sysmanagement_shared_server_groups_internal] (name, description, server_type, parent_id, is_system_object)
    VALUES (N'IntegrationServicesServerGroup', N'Builtin group that contains the IntegrationServices servers', 3, null, 1);

    INSERT INTO [msdb].[dbo].[sysmanagement_shared_server_groups_internal] (name, description, server_type, parent_id, is_system_object)
    VALUES (N'SqlServerCompactEditionServerGroup', N'Builtin group that contains the SqlServerCompactEdition servers', 4, null, 1);

END
ELSE
BEGIN
	UPDATE 
		[msdb].[dbo].[sysmanagement_shared_server_groups_internal]
	SET 
		name = N'SqlServerCompactEditionServerGroup',
		description = N'Builtin group that contains the SqlServerCompactEdition servers'
	WHERE
		name = N'SqlServerEverywhereServerGroup' and
		server_type = 4 and
		is_system_object = 1;
END

GO
PRINT 'Creating trigger [sysmanagement_delete_shared_server_group_trigger]...'
GO
CREATE TRIGGER [sysmanagement_delete_shared_server_group_trigger] on [msdb].[dbo].[sysmanagement_shared_server_groups_internal] 
FOR DELETE
AS
BEGIN
    -- system server groups should not be deleted
    IF EXISTS (SELECT * FROM deleted where is_system_object = 1)
    BEGIN
        RAISERROR (35008, 1, 1)
        ROLLBACK TRANSACTION
    END
END
GO

IF NOT EXISTS ( SELECT * FROM sys.tables WHERE name = 'sysmanagement_shared_registered_servers_internal')
BEGIN
    PRINT 'Creating table [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]...'
    CREATE TABLE [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]
    (
        server_id INT NOT NULL IDENTITY PRIMARY KEY NONCLUSTERED,
        server_group_id INT FOREIGN KEY REFERENCES [msdb].[dbo].[sysmanagement_shared_server_groups_internal] (server_group_id) ON DELETE CASCADE,
        name sysname NOT NULL,
        server_name sysname NOT NULL,
        description NVARCHAR(2048) NOT NULL,
        -- While the server group has the knowledge of the server type,
        -- you also need the Server Type here, because you can have a root registered server with no parent group
        server_type INT NOT NULL,
        -- Make sure each registered name is unique in each group
        CONSTRAINT [UQ_sysmanagement_unique_server_name_per_group] UNIQUE(server_group_id, name)
    )

    CREATE CLUSTERED    INDEX [IX_sysmanagement_shared_registered_servers_clustGroupID] ON
                [dbo].[sysmanagement_shared_registered_servers_internal] (server_group_id);
    CREATE NONCLUSTERED INDEX [IX_sysmanagement_shared_registered_servers_name] ON
                [dbo].[sysmanagement_shared_registered_servers_internal] (name)
END
GO

PRINT 'Creating view [dbo].[sysmanagement_shared_server_groups]...'
GO
CREATE VIEW [dbo].[sysmanagement_shared_server_groups]
AS
(
    SELECT server_group_id, name, description, server_type, parent_id, is_system_object,
    (select COUNT(*) from [msdb].[dbo].[sysmanagement_shared_server_groups_internal] sgChild where sgChild.parent_id = sg.server_group_id) as num_server_group_children,
    (select COUNT(*) from [msdb].[dbo].[sysmanagement_shared_registered_servers_internal] rsChild where rsChild.server_group_id = sg.server_group_id) as num_registered_server_children
    FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] sg
)
GO

PRINT 'Creating view [dbo].[sysmanagement_shared_registered_servers]...'
GO
CREATE VIEW [dbo].[sysmanagement_shared_registered_servers]
AS
(
    SELECT server_id, server_group_id, name, server_name, description, server_type
    FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]
)
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_verify_shared_server_type]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_verify_shared_server_type]
    @server_type INT
AS
BEGIN
    IF (@server_type IS NULL)
    BEGIN
        RAISERROR (35009, -1, -1)
        RETURN(1)
    END
    
    -- 0 --> DatabaseEngineServerGroup, 1 --> AnalysisServicesServerGroup, 2 --> ReportingServicesServerGroup, 3 --> IntegrationServicesServerGroup, 4 --> SqlServerCompactEditionServerGroup
    IF (@server_type < 0 OR @server_type > 4)
    BEGIN
        RAISERROR (35010, -1, -1, @server_type)
        RETURN (1)
    END
    
    RETURN (0)
END
GO


PRINT 'Creating procedure [dbo].[sp_sysmanagement_add_shared_server_group]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_add_shared_server_group]
    @name sysname,
    @description NVARCHAR (2048) = N'',
    @parent_id INT,
    @server_type INT,
    @server_group_id INT OUTPUT
AS
BEGIN
    DECLARE @retval INT

    EXECUTE @retval = sp_sysmanagement_verify_shared_server_type @server_type

    IF (@retval <> 0)
        RETURN(1) -- Failure
    
    -- user created server groups should have a valid parent 
    IF( (@parent_id IS NULL) OR 
        (@parent_id NOT IN (SELECT sg.server_group_id FROM msdb.dbo.sysmanagement_shared_server_groups_internal sg)))
    BEGIN
        RAISERROR (35001, -1, -1)
        RETURN (1)
    END

    IF EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal]  sg 
                WHERE @parent_id = sg.server_group_id AND @server_type <> sg.server_type)
    BEGIN
        RAISERROR (35002, -1, -1)
        RETURN (1)
    END    
    
    INSERT INTO [msdb].[dbo].[sysmanagement_shared_server_groups_internal]
        (name, description, parent_id, server_type)
    VALUES
        (
        @name, 
        @description, 
        @parent_id, 
        @server_type
        )

    SELECT @server_group_id = SCOPE_IDENTITY()
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_add_shared_registered_server]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_add_shared_registered_server]
    @name sysname,
    @server_group_id INT,
    @server_name sysname,
    @description NVARCHAR(2048) = N'',
    @server_type INT,
    @server_id INT OUTPUT
AS
BEGIN
    DECLARE @retval INT

    EXECUTE @retval = sp_sysmanagement_verify_shared_server_type @server_type

    IF (@retval <> 0)
        RETURN(1) -- Failure
    
    IF( (@server_group_id IS NULL) OR 
        (@server_group_id NOT IN (SELECT sg.server_group_id FROM msdb.dbo.sysmanagement_shared_server_groups_internal sg)))
    BEGIN
        RAISERROR (35001, -1, -1)
        RETURN (1)
    END

    IF EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal]  sg 
                WHERE @server_group_id = sg.server_group_id AND @server_type <> sg.server_type)
    BEGIN
        RAISERROR (35002, -1, -1)
        RETURN (1)
    END    

    IF (@server_name IS NULL)
    BEGIN
        RAISERROR(14618, -1, 1, '@server_name')
        RETURN(1)   
    END

    set @server_name = LTRIM(@server_name)
    set @server_name = RTRIM(@server_name)

    -- Disallow relative names
    IF ('.' = @server_name) OR
        (1 = CHARINDEX(N'.\', @server_name)) OR
        (1 = CHARINDEX(N'LOCALHOST\', UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS))) OR
        (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = 'LOCALHOST') OR
        (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = '(LOCAL)')
    BEGIN
        RAISERROR (35011, -1, -1)
        RETURN (1)
    END

    IF (UPPER(@@SERVERNAME collate SQL_Latin1_General_CP1_CS_AS) = UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS))
    BEGIN
        RAISERROR (35012, -1, -1)
        RETURN (1)
    END

    INSERT INTO [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]
        (server_group_id, name, server_name, description, server_type)
    VALUES
        (@server_group_id, @name, @server_name, @description, @server_type)
        
    SELECT @server_id = SCOPE_IDENTITY()
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_delete_shared_server_group]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_delete_shared_server_group]
    @server_group_id INT
AS
BEGIN
    IF NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @server_group_id)
    BEGIN
        RAISERROR (35004, -1, -1)
        RETURN(1)
    END;

    WITH ChildGroups (parent_id, server_group_id, name, server_type, server_level)
    AS
    (
        -- Anchor
        SELECT g.parent_id, g.server_group_id, g.name, g.server_type, 0 AS server_level
        FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] AS g
        WHERE g.server_group_id = @server_group_id
        UNION ALL
        -- Recursive definition
        SELECT r.parent_id, r.server_group_id, r.name, r.server_type, server_level + 1
        FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] AS r
        INNER JOIN ChildGroups AS children ON r.parent_id = children.server_group_id
    )
    -- Execute CTE to delete the hierarchy of server groups
    DELETE FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal]
        FROM ChildGroups children
        JOIN [msdb].[dbo].[sysmanagement_shared_server_groups_internal] ServerGroups
            ON children.server_group_id = ServerGroups.server_group_id
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_delete_shared_registered_server]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_delete_shared_registered_server]
    @server_id INT
AS
BEGIN
    IF NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal] WHERE server_id = @server_id)
    BEGIN
        RAISERROR (35007, -1, -1)
        RETURN(1)
    END
    
    DELETE FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]
    WHERE
            server_id = @server_id
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_move_shared_server_group]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_move_shared_server_group]
    @server_group_id INT,
    @new_parent_id INT
AS
BEGIN
    IF (@new_parent_id IS NULL) OR 
        NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @new_parent_id)
    BEGIN
        RAISERROR (35001, -1, -1)
        RETURN(1)
    END

    IF (@new_parent_id IS NOT NULL)
        AND ((SELECT server_type FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @new_parent_id)
                <>
             (SELECT server_type FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @server_group_id))
    BEGIN
        RAISERROR (35002, -1, -1)
        RETURN(1)
    END

    DECLARE @DeletedGroups TABLE
    (
        server_group_id int
    );

    -- Check if the destination group you're moving to isn't already a descendant of the current group
    WITH ChildGroups (parent_id, server_group_id, server_level)
    AS
    (
        -- Anchor
        SELECT g.parent_id, g.server_group_id, 0 AS server_level
        FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] AS g
        WHERE g.server_group_id = @server_group_id
        UNION ALL
        -- Recursive definition
        SELECT r.parent_id, r.server_group_id, server_level + 1
        FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] AS r
        INNER JOIN ChildGroups AS children ON r.parent_id = children.server_group_id
    )
    -- Execute CTE
    INSERT INTO @DeletedGroups
    SELECT server_group_id FROM ChildGroups

    IF (SELECT COUNT(*) FROM @DeletedGroups WHERE server_group_id = @new_parent_id) > 0
    BEGIN
        RAISERROR (35003, -1, -1)
        RETURN(1)
    END
    
    UPDATE [msdb].[dbo].[sysmanagement_shared_server_groups_internal]
        SET parent_id = @new_parent_id
    WHERE
        server_group_id = @server_group_id
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_move_shared_registered_server]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_move_shared_registered_server]
    @server_id INT,
    @new_parent_id INT
AS
BEGIN
    IF (@server_id IS NULL)
    BEGIN
        RAISERROR (35006, -1, -1)
        RETURN(1)
    END
    
    IF NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal] WHERE server_id = @server_id)
    BEGIN
        RAISERROR (35007, -1, -1)
        RETURN(1)
    END
    
    IF (@new_parent_id IS NULL) OR 
        NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @new_parent_id)
    BEGIN
        RAISERROR (35001, -1, -1)
        RETURN(1)
    END

    IF (@new_parent_id IS NOT NULL)
        AND ((SELECT server_type FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @new_parent_id)
                <>
             (SELECT server_type FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal] WHERE server_id = @server_id))
    BEGIN
        RAISERROR (35002, -1, -1)
        RETURN(1)
    END
    
    UPDATE [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]
        SET server_group_id = @new_parent_id
    WHERE
        server_id = @server_id
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_update_shared_server_group]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_update_shared_server_group]
    @server_group_id INT,
    @description NVARCHAR (2048) = NULL
AS
BEGIN
    IF (@server_group_id IS NULL)
    BEGIN
        RAISERROR (35005, -1, -1)
        RETURN(1)
    END

    IF NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @server_group_id)
    BEGIN
        RAISERROR (35004, -1, -1)
        RETURN(1)
    END

    UPDATE [msdb].[dbo].[sysmanagement_shared_server_groups_internal]
        SET description = ISNULL(@description, description)
    WHERE
        server_group_id = @server_group_id
    
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_update_shared_registered_server]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_update_shared_registered_server]
    @server_id INT,
    @server_name sysname = NULL,
    @description NVARCHAR(2048) = NULL
AS
BEGIN
    IF (@server_id IS NULL)
    BEGIN
        RAISERROR (35006, -1, -1)
        RETURN(1)
    END
    
    IF NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal] WHERE server_id = @server_id)
    BEGIN
        RAISERROR (35007, -1, -1)
        RETURN(1)
    END

    IF (@server_name IS NULL)
    BEGIN
        SET @server_name = (select server_name FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal] WHERE server_id = @server_id)
    END

    set @server_name = LTRIM(@server_name)
    set @server_name = RTRIM(@server_name)

    -- Disallow relative names
    IF ('.' = @server_name) OR
        (1 = CHARINDEX(N'.\', @server_name)) OR
        (1 = CHARINDEX(N'LOCALHOST\', UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS))) OR
        (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = 'LOCALHOST') OR
        (UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS) = '(LOCAL)')
    BEGIN
        RAISERROR (35011, -1, -1)
        RETURN (1)
    END

    IF (UPPER(@@SERVERNAME collate SQL_Latin1_General_CP1_CS_AS) = UPPER(@server_name collate SQL_Latin1_General_CP1_CS_AS))
    BEGIN
        RAISERROR (35012, -1, -1)
        RETURN (1)
    END

    UPDATE [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]
        SET server_name = ISNULL(@server_name, server_name),
            description = ISNULL(@description, description)
    WHERE
        server_id = @server_id
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_sysmanagement_rename_shared_server_group]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_rename_shared_server_group]
    @server_group_id INT,
    @new_name sysname
AS
BEGIN
    IF (@server_group_id IS NULL)
    BEGIN
        RAISERROR (35006, -1, -1)
        RETURN(1)
    END
    
    IF NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_server_groups_internal] WHERE server_group_id = @server_group_id)
    BEGIN
        RAISERROR (35007, -1, -1)
        RETURN(1)
    END
    
    IF (@new_name IS NULL or LEN(@new_name) = 0)
    BEGIN
      RAISERROR(21263, -1, -1, '@new_name')
      RETURN(1) -- Failure
    END

    UPDATE [msdb].[dbo].[sysmanagement_shared_server_groups_internal]
        SET name = @new_name
    WHERE
        server_group_id = @server_group_id

    RETURN (0)
END
GO


PRINT 'Creating procedure [dbo].[sp_sysmanagement_rename_shared_registered_server]...'
GO
CREATE PROCEDURE [dbo].[sp_sysmanagement_rename_shared_registered_server]
    @server_id INT,
    @new_name sysname
AS
BEGIN
    IF (@server_id IS NULL)
    BEGIN
        RAISERROR (35006, -1, -1)
        RETURN(1)
    END
    
    IF NOT EXISTS (SELECT * FROM [msdb].[dbo].[sysmanagement_shared_registered_servers_internal] WHERE server_id = @server_id)
    BEGIN
        RAISERROR (35007, -1, -1)
        RETURN(1)
    END
    
    IF (@new_name IS NULL or LEN(@new_name) = 0)
    BEGIN
      RAISERROR(21263, -1, -1, '@new_name')
      RETURN(1) -- Failure
    END

    UPDATE [msdb].[dbo].[sysmanagement_shared_registered_servers_internal]
        SET name = @new_name
    WHERE
        server_id = @server_id

    RETURN (0)
END
GO


-----------------------------------------------------------
-- This section contains facet information
-----------------------------------------------------------
IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_management_facets')
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_management_facets]...';
    CREATE TABLE [dbo].[syspolicy_management_facets] (
        management_facet_id int NOT NULL IDENTITY PRIMARY KEY,
        name nvarchar(MAX) NOT NULL,    -- this is the name of the management facet
        execution_mode int NOT NULL
    );

END

GO

-- Create a temp table for the facets that are supposed to ship out of the box
-- Later the script will use the temp table to add any new facets to the syspolicy_management_facets table
declare @temp_syspolicy_management_facets TABLE(
    name nvarchar(MAX) NOT NULL,    -- this is the name of the management facet
    execution_mode int NOT NULL);
                
-- populate the temp table with facets shipping out of the box
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ApplicationRole', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('AsymmetricKey', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Audit', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('AvailabilityDatabase', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('AvailabilityGroup', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('AvailabilityReplica', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('DatabaseReplicaState', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('BackupDevice', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('BrokerPriority', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('BrokerService', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Certificate', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ColumnEncryptionKey', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ColumnEncryptionKeyValue', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ColumnMasterKey', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Computer', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Credential', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('CryptographicProvider', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Database', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('DatabaseAuditSpecification', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('DatabaseDdlTrigger', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('DatabaseRole', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('DataFile', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Default', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('DeployedDac', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Endpoint', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Utility', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('FileGroup', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('FullTextCatalog', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('FullTextIndex', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('FullTextStopList', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IAvailabilityGroupState', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IDatabaseMaintenanceFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IDatabaseOptions', 6);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IDatabasePerformanceFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IDatabaseSecurityFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ILoginOptions', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IMultipartNameFacet', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('INameFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ITableOptions', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IUserOptions', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IViewOptions', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Index', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerAuditFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerConfigurationFacet', 6);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerInformation', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerPerformanceFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerProtocolSettingsFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerSecurityFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerSetupFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerSelectionFacet', 0);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IServerSettings', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ISmartAdminState', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ISurfaceAreaConfigurationForAnalysisServer', 0);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ISurfaceAreaConfigurationForReportingServices', 0);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ISurfaceAreaFacet', 6);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('LinkedServer', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('LogFile', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Login', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('MessageType', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('PartitionFunction', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('PartitionScheme', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Processor', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('PlanGuide', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('RemoteServiceBinding', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ResourceGovernor', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ResourcePool', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Rule', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Schema', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('SearchPropertyList', 6);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Server', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ServerAuditSpecification', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ServerDdlTrigger', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ServerRole', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ServiceContract', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ServiceQueue', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ServiceRoute', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('SmartAdmin', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Statistic', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('StoredProcedure', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('SymmetricKey', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Synonym', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Sequence', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Table', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Trigger', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('User', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('UserDefinedAggregate', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('UserDefinedDataType', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('UserDefinedFunction', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('UserDefinedTableType', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('UserDefinedType', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('View', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('Volume', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('WorkloadGroup', 7);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('XmlSchemaCollection', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('IDataFilePerformanceFacet', 4);
INSERT @temp_syspolicy_management_facets (name, execution_mode) VALUES ('ILogFilePerformanceFacet', 4);

-- Facets can be updated and inserted, however deleting a facet is dangerous because of references from conditions

-- Update the modes on the facets
UPDATE facets
SET facets.execution_mode = tempFacets.execution_mode 
FROM @temp_syspolicy_management_facets tempFacets, [msdb].[dbo].[syspolicy_management_facets] facets
WHERE tempFacets.name = facets.name AND tempFacets.execution_mode <> facets.execution_mode;

-- Now populate the syspolicy_management_facets table with the facets that are missing in the table
INSERT [msdb].[dbo].[syspolicy_management_facets] (name, execution_mode)
    SELECT tempFacets.name, tempFacets.execution_mode
    FROM @temp_syspolicy_management_facets tempFacets
    WHERE tempFacets.name NOT IN (SELECT distinct facets.name FROM [msdb].[dbo].[syspolicy_management_facets] facets)
    


GO

---------------------------------------------------------------
-- Relational storage for policy objects
---------------------------------------------------------------
IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_facet_events')
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_facet_events]...';
    CREATE TABLE [dbo].[syspolicy_facet_events] (
        management_facet_id int NOT NULL REFERENCES [dbo].[syspolicy_management_facets],
        event_name sysname NOT NULL,                -- this is the name of the event
        target_type sysname NOT NULL,                -- type of the target (TABLE, PROCEDURE etc.)
        target_type_alias sysname NOT NULL);        -- this is an alias for the type of the target
                                                    -- it can happen that the same target type 
                                                    -- shows up as different strings in the event

    CREATE CLUSTERED INDEX [IX_facet_events_target_type_alias] ON 
        [dbo].[syspolicy_facet_events] (target_type_alias);
    CREATE UNIQUE INDEX [UX_facet_events] ON 
        [dbo].[syspolicy_facet_events] (management_facet_id, event_name, target_type, target_type_alias);
END
GO
-- The facet events are unique to an installation, thus delete what is there and then insert the new events
-- This technique will work for both a new install and an upgrade.
DELETE FROM [msdb].[dbo].[syspolicy_facet_events]

-- this is a pseudo event, so we're inserting it before the
-- consistency check is in place
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'SAC_ENDPOINT_CHANGE',N'SERVER',N'SERVER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ISurfaceAreaFacet'
 

GO
CREATE TRIGGER [syspolicy_validate_events] on [dbo].[syspolicy_facet_events]
AFTER INSERT, UPDATE
AS
BEGIN
    -- make sure that caller is dbo and all events inserted are real events.
    IF  (USER_ID() != 1) OR
        EXISTS (SELECT event_name FROM inserted 
                    WHERE event_name NOT IN(SELECT type_name from sys.event_notification_event_types))
    BEGIN
        RAISERROR(N'Unknown event name inserted into [dbo].[syspolicy_facet_events]', 1,1)
        ROLLBACK TRANSACTION
    END
END
GO

-- Insert the facet events               
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_APPLICATION_ROLE',N'APPLICATION ROLE',N'APPLICATION ROLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ApplicationRole'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_APPLICATION_ROLE',N'APPLICATION ROLE',N'APPLICATION ROLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ApplicationRole'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_ASYMMETRIC_KEY',N'ASYMMETRICKEY',N'ASYMMETRIC KEY'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'AsymmetricKey'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_ASYMMETRIC_KEY',N'ASYMMETRICKEY',N'ASYMMETRIC KEY'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'AsymmetricKey'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'ASYMMETRICKEY',N'ASYMMETRIC KEY'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'AsymmetricKey'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_ROLE',N'ROLE',N'ROLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'DatabaseRole'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_ROLE',N'ROLE',N'ROLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'DatabaseRole'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'ROLE',N'ROLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'DatabaseRole'

INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_ENDPOINT',N'ENDPOINT',N'ENDPOINT'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Endpoint'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_ENDPOINT',N'ENDPOINT',N'ENDPOINT'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Endpoint'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_SERVER',N'ENDPOINT',N'ENDPOINT'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Endpoint'
   
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_DATABASE',N'DATABASE',N'DATABASE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IDatabaseOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_DATABASE',N'DATABASE',N'DATABASE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IDatabaseOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'DATABASE',N'DATABASE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IDatabaseOptions'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_FUNCTION',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_FUNCTION',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_PROCEDURE',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_PROCEDURE',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_SYNONYM',N'SYNONYM',N'SYNONYM'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_TABLE',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_TABLE',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_TYPE',N'TYPE',N'TYPE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'TYPE',N'TYPE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_VIEW',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_VIEW',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_XML_SCHEMA_COLLECTION',N'XMLSCHEMACOLLECTION',N'XMLSCHEMACOLLECTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_XML_SCHEMA_COLLECTION',N'XMLSCHEMACOLLECTION',N'XMLSCHEMACOLLECTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'XMLSCHEMACOLLECTION',N'XMLSCHEMACOLLECTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'SYNONYM',N'SYNONYM'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'TYPE',N'TYPE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'XMLSCHEMACOLLECTION',N'XMLSCHEMACOLLECTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IMultipartNameFacet'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'AUDIT_SERVER_OPERATION_EVENT',N'SERVER',N'SERVER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IServerConfigurationFacet'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'AUDIT_SERVER_OPERATION_EVENT',N'SERVER',N'SERVER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ISurfaceAreaFacet'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_ENDPOINT',N'SERVER',N'SERVER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ISurfaceAreaFacet'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_ENDPOINT',N'SERVER',N'SERVER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ISurfaceAreaFacet'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'DROP_ENDPOINT',N'SERVER',N'SERVER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ISurfaceAreaFacet'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_LOGIN',N'LOGIN',N'LOGIN'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ILoginOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_LOGIN',N'LOGIN',N'LOGIN'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ILoginOptions'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_RESOURCE_POOL',N'RESOURCEPOOL',N'RESOURCE POOL'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ResourcePool'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_RESOURCE_POOL',N'RESOURCEPOOL',N'RESOURCE POOL'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ResourcePool'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'SCHEMA',N'SCHEMA'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Schema'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_SCHEMA',N'SCHEMA',N'SCHEMA'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Schema'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'SCHEMA',N'SCHEMA'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Schema'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SERVER_ROLE',N'SERVER ROLE',N'SERVER ROLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ServerRole'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_SERVER_ROLE',N'SERVER ROLE',N'SERVER ROLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ServerRole'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_SERVER',N'SERVER ROLE',N'SERVER ROLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ServerRole'
 
 INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SEQUENCE',N'SEQUENCE',N'SEQUENCE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Sequence'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_SEQUENCE',N'SEQUENCE',N'SEQUENCE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Sequence'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'SEQUENCE',N'SEQUENCE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Sequence'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'SEQUENCE',N'SEQUENCE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Sequence'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'SEQUENCE',N'SEQUENCE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'Sequence'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_PROCEDURE',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'StoredProcedure'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_PROCEDURE',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'StoredProcedure'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'StoredProcedure'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'StoredProcedure'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'PROCEDURE',N'PROCEDURE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'StoredProcedure'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_TABLE',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ITableOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_TABLE',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ITableOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ITableOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ITableOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'TABLE',N'TABLE'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ITableOptions'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_USER',N'USER',N'ASYMMETRIC KEY USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_USER',N'USER',N'CERTIFICATE USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_USER',N'USER',N'GROUP USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_USER',N'USER',N'SQL USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_USER',N'USER',N'WINDOWS USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_USER',N'USER',N'ASYMMETRIC KEY USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_USER',N'USER',N'CERTIFICATE USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_USER',N'USER',N'GROUP USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_USER',N'USER',N'SQL USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_USER',N'USER',N'WINDOWS USER'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IUserOptions'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_FUNCTION',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'UserDefinedFunction'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_FUNCTION',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'UserDefinedFunction'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'UserDefinedFunction'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'UserDefinedFunction'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'FUNCTION',N'FUNCTION'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'UserDefinedFunction'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_VIEW',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IViewOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_VIEW',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IViewOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'RENAME',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IViewOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IViewOptions'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SCHEMA',N'VIEW',N'VIEW'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'IViewOptions'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_WORKLOAD_GROUP',N'WORKLOADGROUP',N'WORKLOAD GROUP'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'WorkloadGroup'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_WORKLOAD_GROUP',N'WORKLOADGROUP',N'WORKLOAD GROUP'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'WorkloadGroup'

INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_SEARCH_PROPERTY_LIST',N'SEARCHPROPERTYLIST',N'SEARCH PROPERTY LIST'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'SearchPropertyList'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_SEARCH_PROPERTY_LIST',N'SEARCHPROPERTYLIST',N'SEARCH PROPERTY LIST'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'SearchPropertyList'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_AUTHORIZATION_DATABASE',N'SEARCHPROPERTYLIST',N'SEARCH PROPERTY LIST'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'SearchPropertyList'
 
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_COLUMN_ENCRYPTION_KEY',N'COLUMN_ENCRYPTION_KEY',N'COLUMN_ENCRYPTION_KEY'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ColumnEncryptionKey'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_COLUMN_ENCRYPTION_KEY',N'COLUMN_ENCRYPTION_KEY',N'COLUMN_ENCRYPTION_KEY'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ColumnEncryptionKey'
  
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_COLUMN_ENCRYPTION_KEY',N'COLUMN_ENCRYPTION_KEY',N'COLUMN_ENCRYPTION_KEY'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ColumnEncryptionKeyValue'
INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'ALTER_COLUMN_ENCRYPTION_KEY',N'COLUMN_ENCRYPTION_KEY',N'COLUMN_ENCRYPTION_KEY'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ColumnEncryptionKeyValue'

INSERT [msdb].[dbo].[syspolicy_facet_events] (management_facet_id,event_name,target_type,target_type_alias) 
 SELECT management_facet_id,N'CREATE_COLUMN_MASTER_KEY',N'COLUMN_MASTER_KEY',N'COLUMN_MASTER_KEY'
 FROM [msdb].[dbo].[syspolicy_management_facets]
 WHERE name = 'ColumnMasterKey'
GO
---------------------------------------------------------------
-- Condition object
---------------------------------------------------------------

IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_conditions_internal')
BEGIN
PRINT 'Creating table [dbo].[syspolicy_conditions_internal]...'
CREATE TABLE [dbo].[syspolicy_conditions_internal] (
    condition_id int IDENTITY(1,1),
    name sysname NOT NULL,
    date_created datetime default GETDATE(),
    description nvarchar(max) NOT NULL default (''),
    created_by sysname NOT NULL default SUSER_SNAME(),
    modified_by sysname NULL,
    date_modified datetime NULL,
    facet_id int,
    expression nvarchar(max),
    is_name_condition smallint default (0),
    obj_name sysname NULL,
    is_system bit NOT NULL default (0),
    CONSTRAINT [PK_syspolicy_conditions] PRIMARY KEY CLUSTERED (condition_id ASC),
    CONSTRAINT [UQ_syspolicy_conditions_name] UNIQUE(name)
    );
ALTER TABLE [dbo].[syspolicy_conditions_internal] 
    ADD CONSTRAINT [FK_syspolicy_conditions_internal_facet] FOREIGN KEY(facet_id)
    REFERENCES [dbo].[syspolicy_management_facets] (management_facet_id);
END
GO

PRINT 'Creating view [dbo].[syspolicy_conditions]...'
GO
CREATE VIEW [dbo].[syspolicy_conditions]
AS
    SELECT
        c.condition_id, c.name, c.date_created, c.description, c.created_by, 
        c.modified_by, c.date_modified, c.is_name_condition, mf.name AS facet, c.expression, c.obj_name, c.is_system 
    FROM [dbo].[syspolicy_conditions_internal] c 
    LEFT OUTER JOIN [dbo].[syspolicy_management_facets] mf ON c.facet_id = mf.management_facet_id
GO

CREATE PROCEDURE [dbo].[sp_syspolicy_check_membership]
@role sysname,
@raiserror bit = 1
AS
BEGIN
	-- make sure that the caller is dbo or @role
	IF ( IS_MEMBER(@role) != 1 AND USER_ID() != 1)
	BEGIN
		IF (@raiserror = 1)
		BEGIN
			RAISERROR(15003, -1, -1, @role);
		END
		RETURN 15003;
	END
	
	RETURN 0;
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_add_condition]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_add_condition] 
@name sysname,
@description nvarchar(max) = N'',
@facet nvarchar(max),
@expression nvarchar(max),
@is_name_condition smallint = 0,
@obj_name sysname = NULL,
@condition_id int = NULL OUTPUT 
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT
	DECLARE @facet_id            INT
	DECLARE @null_column	sysname
	
	SET @null_column = NULL

    IF (@name IS NULL OR @name = N'')
        SET @null_column = '@name'
    ELSE IF( @description IS NULL)
        SET @null_column = '@description'
    ELSE IF( @facet IS NULL)
        SET @null_column = '@facet'
    ELSE IF( @expression IS NULL)
        SET @null_column = '@expression'

    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_syspolicy_add_condition')
        RETURN(1)
    END

    IF EXISTS (SELECT * FROM msdb.dbo.syspolicy_conditions WHERE name = @name)
    BEGIN
        RAISERROR(34010, -1, -1, 'Condition', @name)
        RETURN(1)
    END

    SET @facet_id = (SELECT management_facet_id FROM [dbo].[syspolicy_management_facets] WHERE name = @facet)
    IF (@facet_id IS NULL)
    BEGIN
        RAISERROR (34014, -1, -1)
        RETURN(1)
    END

    INSERT INTO msdb.dbo.syspolicy_conditions_internal(name, description,facet_id,expression,is_name_condition,obj_name) 
        VALUES(@name, @description,@facet_id,@expression,@is_name_condition,@obj_name)
    SELECT @retval = @@error
    SET @condition_id = SCOPE_IDENTITY()
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_verify_condition_identifiers]...'
GO
-----------------------------------------------------------
-- This procedure verifies if a condition exists
-- The caller can pass either the condition name or the id
-----------------------------------------------------------
CREATE PROCEDURE [dbo].[sp_syspolicy_verify_condition_identifiers]
@condition_name sysname = NULL OUTPUT, 
@condition_id int = NULL OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

  IF ((@condition_name IS NULL)     AND (@condition_id IS NULL)) OR
     ((@condition_name IS NOT NULL) AND (@condition_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, '@condition_name', '@condition_id')
    RETURN(1) -- Failure
  END

  -- Check id
  IF (@condition_id IS NOT NULL)
  BEGIN
    SELECT @condition_name = name
    FROM msdb.dbo.syspolicy_conditions
    WHERE (condition_id = @condition_id)
    
    -- the view would take care of all the permissions issues.
    IF (@condition_name IS NULL) 
    BEGIN
      DECLARE @condition_id_as_char VARCHAR(36)
      SELECT @condition_id_as_char = CONVERT(VARCHAR(36), @condition_id)
      RAISERROR(14262, -1, -1, '@condition_id', @condition_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check name
  IF (@condition_name IS NOT NULL)
  BEGIN
    -- get the corresponding condition_id (if the condition exists)
    SELECT @condition_id = condition_id
    FROM msdb.dbo.syspolicy_conditions
    WHERE (name = @condition_name)
    
    -- the view would take care of all the permissions issues.
    IF (@condition_id IS NULL) 
    BEGIN
      RAISERROR(14262, -1, -1, '@condition_name', @condition_name)
      RETURN(1) -- Failure
    END
  END

  RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_update_condition]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_update_condition] 
@name sysname = NULL,
@condition_id int = NULL,
@facet nvarchar(max) = NULL,
@expression nvarchar(max) = NULL,
@description nvarchar(max) = NULL,
@is_name_condition smallint = NULL,
@obj_name sysname = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT
	DECLARE @facet_id            INT

    EXEC @retval = msdb.dbo.sp_syspolicy_verify_condition_identifiers @name, @condition_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    IF (@facet IS NOT NULL)
    BEGIN
        SET @facet_id = (SELECT management_facet_id FROM [dbo].[syspolicy_management_facets] WHERE name = @facet)
        IF (@facet_id IS NULL)
        BEGIN
            RAISERROR (34014, -1, -1)
            RETURN(1)
        END
    END

    UPDATE msdb.[dbo].[syspolicy_conditions_internal] 
    SET
        description = ISNULL(@description, description),
        facet_id = ISNULL(@facet_id, facet_id),
        expression = ISNULL(@expression, expression),
        is_name_condition = ISNULL(@is_name_condition, is_name_condition),
        obj_name = ISNULL(@obj_name, obj_name)
    WHERE condition_id = @condition_id
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_delete_condition]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_delete_condition] 
@name sysname = NULL,
@condition_id int = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_condition_identifiers @name, @condition_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    IF EXISTS (SELECT * FROM msdb.dbo.syspolicy_policies WHERE condition_id = @condition_id)
    BEGIN
        RAISERROR(34012,-1,-1,'Condition','Policy')
        RETURN (1)
    END

    DELETE msdb.dbo.syspolicy_conditions_internal
    WHERE condition_id = @condition_id
    
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_rename_condition]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_rename_condition] 
@name sysname = NULL,
@condition_id int = NULL,
@new_name sysname = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    IF (@new_name IS NULL or LEN(@new_name) = 0)
    BEGIN
      RAISERROR(21263, -1, -1, '@new_name')
      RETURN(1) -- Failure
    END

    DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_condition_identifiers @name, @condition_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    UPDATE msdb.[dbo].[syspolicy_conditions_internal] 
    SET name = @new_name
    WHERE condition_id = @condition_id

    SELECT @retval = @@error
    RETURN(@retval)
END
GO

---------------------------------------------------------------
-- table for Policy category
---------------------------------------------------------------
IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_policy_categories_internal')
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_policy_categories_internal]...';
    CREATE TABLE [dbo].[syspolicy_policy_categories_internal] (
        policy_category_id int IDENTITY(1,1),
        name sysname,
        mandate_database_subscriptions bit default 1 NOT NULL,
        CONSTRAINT [PK_syspolicy_policy_categories] PRIMARY KEY CLUSTERED (policy_category_id ASC),
        CONSTRAINT [UQ_syspolicy_policy_categories_name] UNIQUE(name)
        );
END
GO

PRINT 'Creating view [dbo].[syspolicy_policy_categories]...'
GO
CREATE VIEW [dbo].[syspolicy_policy_categories]
AS
    SELECT     
        policy_category_id,
        name,
        mandate_database_subscriptions
    FROM [dbo].[syspolicy_policy_categories_internal]
GO

---------------------------------------------------------------
-- ObjectSet object
---------------------------------------------------------------
IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_object_sets_internal')
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_object_sets_internal]...'
CREATE TABLE [dbo].[syspolicy_object_sets_internal] (
    object_set_id int NOT NULL IDENTITY(1,1),
    object_set_name sysname NOT NULL,
    facet_id int,
    is_system bit NOT NULL default (0),
    CONSTRAINT [PK_syspolicy_object_sets] PRIMARY KEY CLUSTERED (object_set_id),
    CONSTRAINT [UQ_syspolicy_object_sets_name] UNIQUE(object_set_name)
)

ALTER TABLE [dbo].[syspolicy_object_sets_internal]
    ADD CONSTRAINT [FK_syspolicy_object_sets_syspolicy_management_facets] FOREIGN KEY(facet_id)
    REFERENCES [dbo].[syspolicy_management_facets] (management_facet_id)
    ON DELETE CASCADE
END
GO

PRINT 'Creating view [dbo].[syspolicy_object_sets]...'
GO
CREATE VIEW [dbo].[syspolicy_object_sets]
AS
    SELECT     
        os.object_set_id,
        os.object_set_name,
        os.facet_id,
        facet.name as facet_name,
        os.is_system
    FROM [dbo].[syspolicy_object_sets_internal] AS os INNER JOIN [dbo].[syspolicy_management_facets] AS facet
    ON os.facet_id = facet.management_facet_id
GO

-----------------------------------------------------------
-- This procedure verifies if a object set definition exists
-- The caller can pass either the name or the id
-----------------------------------------------------------
PRINT 'Creating procedure [dbo].[sp_syspolicy_verify_object_set_identifiers]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_verify_object_set_identifiers]
@name sysname = NULL OUTPUT, 
@object_set_id int = NULL OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

  IF ((@name IS NULL)     AND (@object_set_id IS NULL)) OR
     ((@name IS NOT NULL) AND (@object_set_id IS NOT NULL))
  BEGIN
    -- TODO: Verify the error message is appropriate for object sets as well and not specific to policies
    RAISERROR(14524, -1, -1, '@name', '@object_set_id')
    RETURN(1) -- Failure
  END

  -- Check id
  IF (@object_set_id IS NOT NULL)
  BEGIN
    SELECT @name = object_set_name
    FROM msdb.dbo.syspolicy_object_sets
    WHERE (object_set_id = @object_set_id)
    
    -- the view would take care of all the permissions issues.
    IF (@name IS NULL) 
    BEGIN
        -- TODO: Where did 36 come from? Is this the total lenght of characters for an int?
      DECLARE @object_set_id_as_char VARCHAR(36)
      SELECT @object_set_id_as_char = CONVERT(VARCHAR(36), @object_set_id)
      RAISERROR(14262, -1, -1, '@object_set_id', @object_set_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check name
  IF (@name IS NOT NULL)
  BEGIN
    -- get the corresponding object_set_id (if the object_set exists)
    SELECT @object_set_id = object_set_id
    FROM msdb.dbo.syspolicy_object_sets
    WHERE (object_set_name = @name)
    
    -- the view would take care of all the permissions issues.
    IF (@object_set_id IS NULL) 
    BEGIN
    -- TODO: Verify the error message is appropriate for object sets as well and not specific to policies
      RAISERROR(14262, -1, -1, '@name', @name)
      RETURN(1) -- Failure
    END
  END

  RETURN (0)
END
GO

-----------------------------------------------------------
-- This procedure verifies if an object set is refernced (cannot be deleted)
-----------------------------------------------------------
PRINT 'Creating procedure [dbo].[sp_syspolicy_verify_object_set_references]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_verify_object_set_references]
@object_set_id int,
@is_referenced int OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	SELECT @is_referenced = count(*) FROM dbo.syspolicy_policies WHERE object_set_id = @object_set_id
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_add_object_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_add_object_set]
@object_set_name sysname,
@facet nvarchar (max),
@object_set_id int OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval             INT
	
	DECLARE @facet_id           INT
	DECLARE @null_column		sysname
	
	IF( @facet IS NULL)
		SET @null_column = '@facet'
		
	IF @null_column IS NOT NULL
	BEGIN
		RAISERROR(14043, -1, -1, @null_column, 'sp_syspolicy_add_object_set')
		RETURN(1)
	END

    SET @facet_id = (SELECT management_facet_id FROM [dbo].[syspolicy_management_facets] WHERE name = @facet)
        
    IF (@facet_id IS NULL)
    BEGIN
        RAISERROR (34014, -1, -1)
        RETURN(1)
    END

    INSERT INTO msdb.[dbo].[syspolicy_object_sets_internal]
                                        (object_set_name,
                                        facet_id)
    VALUES                            
                                        (@object_set_name,
                                        @facet_id)

    SELECT @retval = @@error
    SET @object_set_id = SCOPE_IDENTITY()
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_delete_object_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_delete_object_set]
@object_set_name sysname = NULL,
@object_set_id int = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_object_set_identifiers @object_set_name, @object_set_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    DELETE msdb.[dbo].[syspolicy_object_sets_internal] 
        WHERE object_set_id = @object_set_id

    RETURN (0)
END
GO


---------------------------------------------------------------
-- PolicyDefinition object
---------------------------------------------------------------

IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_policies_internal')
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_policies_internal]...';
CREATE TABLE [dbo].[syspolicy_policies_internal] (
	policy_id int IDENTITY(1,1),
	name sysname NOT NULL,
	condition_id int NOT NULL,
	root_condition_id int NULL,
	date_created datetime  NOT NULL default GETDATE(),
	execution_mode int NOT NULL default (0),
	policy_category_id int NULL,
	schedule_uid uniqueidentifier NULL,
	description nvarchar(max) NOT NULL default (''),
	help_text nvarchar(4000) NOT NULL default (''),
	help_link nvarchar(2083) NOT NULL default (''),
	object_set_id INT NULL,
	is_enabled bit default 0 NOT NULL,
	job_id uniqueidentifier NULL,
	created_by sysname NOT NULL default SUSER_SNAME(),
	modified_by sysname NULL,
	date_modified datetime NULL,
	is_system bit NOT NULL default (0),
	CONSTRAINT [PK_syspolicy_policies] PRIMARY KEY CLUSTERED (policy_id ASC),
	CONSTRAINT [UQ_syspolicy_policies_name] UNIQUE(name)
	);
ALTER TABLE [dbo].[syspolicy_policies_internal] 
    ADD CONSTRAINT [FK_syspolicy_policies_syspolicy_conditions] FOREIGN KEY(condition_id)
    REFERENCES [dbo].[syspolicy_conditions_internal] (condition_id);

    ALTER TABLE [dbo].[syspolicy_policies_internal] 
        ADD CONSTRAINT [FK_syspolicy_policies_syspolicy_policy_categories] FOREIGN KEY(policy_category_id)
        REFERENCES [dbo].[syspolicy_policy_categories_internal] (policy_category_id);


ALTER TABLE [dbo].[syspolicy_policies_internal] 
    ADD CONSTRAINT [FK_syspolicy_policies_syspolicy_root_conditions] FOREIGN KEY(root_condition_id)
    REFERENCES [dbo].[syspolicy_conditions_internal] (condition_id);


    ALTER TABLE [dbo].[syspolicy_policies_internal] 
        ADD CONSTRAINT [FK_syspolicy_policies_sysjobs] FOREIGN KEY(job_id)
        REFERENCES [dbo].[sysjobs] (job_id);

ALTER TABLE [dbo].[syspolicy_policies_internal]
    ADD CONSTRAINT [FK_syspolicy_policies_syspolicy_object_sets] FOREIGN KEY(object_set_id)
    REFERENCES [dbo].[syspolicy_object_sets_internal] (object_set_id);
    
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_create_job]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_create_job] 
@schedule_uid uniqueidentifier,
@is_enabled bit = 0,
@jobID uniqueidentifier OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @job_name sysname

	-- create unique job name
	SET @job_name = N'syspolicy_check_schedule_' + LEFT(CONVERT(nvarchar(100), @schedule_uid), 100) 
	WHILE (EXISTS (SELECT * FROM msdb..sysjobs WHERE name = @job_name))
	BEGIN
		SET @job_name = N'syspolicy_check_schedule_' + LEFT(CONVERT(nvarchar(91), @schedule_uid), 91) + '_' + RIGHT(STR(FLOOR(RAND() * 100000000)),8) 
	END

	EXEC  msdb.dbo.sp_add_job @job_name=@job_name, 
			@enabled=@is_enabled, 
			@notify_level_eventlog=0, 
			@notify_level_email=2, 
			@notify_level_netsend=2, 
			@notify_level_page=2, 
			@delete_level=0, 
			@category_id=0, -- [Uncategorized (Local)]
			@job_id = @jobID OUTPUT

	EXEC msdb.dbo.sp_add_jobserver @job_name=@job_name, @server_name = @@servername

    EXEC msdb.dbo.sp_add_jobstep 
            @job_id=@jobID, 
			@step_name=N'Verify that automation is enabled.', 
		    @step_id=1, 
		    @cmdexec_success_code=0, 
		    @on_fail_action=1, 
		    @on_fail_step_id=0, 
		    @retry_attempts=0, 
		    @retry_interval=0, 
		    @os_run_priority=0, 
		    @subsystem=N'TSQL', 
		    @command=N'IF (msdb.dbo.fn_syspolicy_is_automation_enabled() != 1)
        BEGIN
            RAISERROR(34022, 16, 1)
        END', 
		    @database_name=N'master', 
		    @flags=0

	DECLARE @command nvarchar(max)
	SET @command = [dbo].[fn_syspolicy_get_ps_command] (@schedule_uid)

	EXEC msdb.dbo.sp_add_jobstep 
            @job_id=@jobID, 
			@step_name=N'Evaluate policies.', 
			@step_id=2, 
			@cmdexec_success_code=0, 
			@on_success_action=1, 
			@on_fail_action=2, 
			@retry_attempts=0, 
			@retry_interval=0, 
			@os_run_priority=0, 
			@subsystem=N'PowerShell', 
			@command=@command, 
			@flags=0

    EXEC msdb.dbo.sp_update_jobstep 
            @job_id = @jobID, 
            @step_id = 1, 
            @on_success_action=4, 
            @on_success_step_id=2 

	DECLARE @schedule_id int
	SELECT @schedule_id = schedule_id from msdb.dbo.sysschedules where schedule_uid = @schedule_uid

	EXEC msdb.dbo.sp_attach_schedule @job_name = @job_name, @schedule_id = @schedule_id
END
GO

PRINT 'Creating function [dbo].[fn_syspolicy_get_ps_command] ...'
GO

CREATE FUNCTION [dbo].[fn_syspolicy_get_ps_command] (@schedule_uid uniqueidentifier)
RETURNS nvarchar(max)
AS
BEGIN
	
	DECLARE @schedule_uid_string nvarchar(max);
	SET @schedule_uid_string = CONVERT(nvarchar(36), @schedule_uid);
	
	-- translate to PSPath root name, for default instances 
	-- we need to add \default as instance name
	DECLARE @root_name nvarchar(100);
	SET @root_name = @@SERVERNAME
	IF( 0 = CHARINDEX('\', @@SERVERNAME))
		SET @root_name = @root_name + N'\default';
	
	DECLARE @command nvarchar(max);
	SET @command = N'dir SQLSERVER:\SQLPolicy\' + @root_name + 
				N'\Policies | where { $_.ScheduleUid -eq "' + @schedule_uid_string + 
				N'" } |  where { $_.Enabled -eq 1} | where {$_.AutomatedPolicyEvaluationMode -eq 4} | Invoke-PolicyEvaluation -AdHocPolicyEvaluationMode 2 -TargetServerName ' + @@SERVERNAME
				
	RETURN @command
END
GO

PRINT 'Creating trigger [dbo].[syspolicy_insert_job_create_trigger] on [dbo].[syspolicy_policies_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_insert_job_create_trigger] on [dbo].[syspolicy_policies_internal]
FOR INSERT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

	-- protect against non-scheduled automation jobs
	-- that have expressions that execute script
	DECLARE @row_count int

    SELECT @row_count = count(*) 
        FROM syspolicy_conditions c  
        INNER JOIN inserted i ON (i.condition_id = c.condition_id OR i.root_condition_id = c.condition_id)
        WHERE    i.is_enabled != 0 AND
                i.execution_mode != 4 AND
                (1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteSql"]' ) OR
                1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteWql"]' ) ) 
        OPTION (FORCE ORDER);

    SELECT @row_count = @row_count + count(*)
        FROM dbo.syspolicy_target_set_levels l 
        INNER JOIN dbo.syspolicy_target_sets s ON s.target_set_id = l.target_set_id
        INNER JOIN syspolicy_conditions c on c.condition_id = l.condition_id
        INNER JOIN syspolicy_object_sets_internal os ON os.object_set_id = s.object_set_id
        INNER JOIN inserted i ON os.object_set_id = i.object_set_id
        WHERE    i.is_enabled != 0 AND
                i.execution_mode != 4 AND
                (1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteSql"]' ) OR
                1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteWql"]' ) )
        OPTION (FORCE ORDER);

    IF (@row_count > 0)
    BEGIN
        RAISERROR(34017, -1, -1);
        ROLLBACK TRANSACTION;
    END

	DECLARE @jobID uniqueidentifier
	DECLARE @schedule_uid uniqueidentifier
	DECLARE @is_enabled bit

	-- verify that values in inserted.schedule_uid are valid
	IF EXISTS (
		SELECT * FROM inserted i 
		WHERE i.schedule_uid NOT IN 
				(SELECT schedule_uid FROM msdb.dbo.sysschedules ) AND 
		((i.execution_mode & 4) = 4))
	BEGIN
		ROLLBACK -- Failure
		RAISERROR (14365, -1, -1)
		RETURN
	END

	-- find all schedules referenced by the inserted policies for which 
	-- there is no agent job that executes the policies
	DECLARE schedule_cursor CURSOR LOCAL FOR
	    SELECT DISTINCT i.schedule_uid
	    FROM inserted i
	    WHERE 
		    ((i.execution_mode & 4) = 4) AND
	        NOT EXISTS (SELECT * 
	                        FROM msdb.dbo.syspolicy_policies p 
	                        WHERE 
	                            p.policy_id NOT IN (SELECT policy_id FROM inserted) AND
	                            p.schedule_uid = i.schedule_uid AND 
	                            ((p.execution_mode & 4) = 4) )
	
	-- iterate through the cursor and create a job for every schedule		
	OPEN schedule_cursor
	FETCH schedule_cursor INTO @schedule_uid
	WHILE @@FETCH_STATUS = 0
	BEGIN
		-- figure out if the job is enabled or not
		SELECT @is_enabled = COUNT(*) 
		FROM inserted i 
		WHERE i.schedule_uid = @schedule_uid AND i.is_enabled = 1
		
		-- explicitly nullify jobID, 
		-- (if we need to create more than 1 job, it will not be null and sp_add_job will think we're getting job from MSX)
		SET @jobID = NULL
		
		-- create the job that is going to execute the schedule
		EXEC [msdb].[dbo].[sp_syspolicy_create_job] @schedule_uid, @is_enabled, @jobID OUTPUT

		-- update the job_id back into the policies table
		UPDATE p SET p.job_id = @jobID 
			FROM msdb.dbo.syspolicy_policies_internal p
			INNER JOIN inserted i ON p.policy_id = i.policy_id
		WHERE 
			i.schedule_uid = @schedule_uid AND
			(i.execution_mode & 4) = 4

		FETCH schedule_cursor INTO @schedule_uid
	END
	
	CLOSE schedule_cursor
	DEALLOCATE schedule_cursor
	
	-- in case we haven't created the job we still need to update 
	-- the policies with their jobID
	UPDATE p
		SET p.job_id = ( SELECT TOP 1 p2.job_id 
						FROM msdb.dbo.syspolicy_policies p2 
						WHERE 
							p2.schedule_uid = p.schedule_uid AND 
							p2.job_id IS NOT NULL)
		FROM msdb.dbo.syspolicy_policies_internal p
		INNER JOIN inserted i ON p.policy_id = i.policy_id
		WHERE 
			((p.execution_mode & 4) = 4) AND
			p.job_id IS NULL
	
	-- See what jobs we need to enable.
	-- This can happen because we might create a new policy that 
	-- is enabled and there is already a job for it, but the existing
	-- job is disabled
	DECLARE jobs_to_enable CURSOR LOCAL FOR
		SELECT DISTINCT j.job_id
		FROM dbo.sysjobs_view j 
		JOIN msdb.dbo.syspolicy_policies p ON p.job_id = j.job_id
		JOIN inserted i ON p.policy_id = i.policy_id
		WHERE 
			((i.execution_mode & 4) = 4) AND
			j.enabled = 0 AND
			EXISTS ( SELECT * from msdb.dbo.syspolicy_policies p2 
						WHERE p2.job_id = j.job_id AND p2.is_enabled = 1)
	
	OPEN jobs_to_enable
	FETCH jobs_to_enable INTO @jobID
	WHILE @@FETCH_STATUS = 0
	BEGIN
		EXEC msdb.dbo.sp_update_job @job_id = @jobID, @enabled = 1
		
		FETCH jobs_to_enable INTO @jobID
	END
	CLOSE jobs_to_enable
	DEALLOCATE jobs_to_enable
	
	-- enable events infrastructure
	IF EXISTS ( SELECT * FROM inserted i WHERE ((i.execution_mode & 1) = 1))
	BEGIN
		EXEC sys.sp_syspolicy_update_ddl_trigger 
	END

	IF EXISTS (SELECT * FROM inserted i WHERE ((i.execution_mode & 2) = 2))
	BEGIN
		EXEC sys.sp_syspolicy_update_event_notification 
	END

	-- update owner information
	UPDATE msdb.dbo.syspolicy_policies_internal
	SET created_by = original_login(),
		date_created = getdate (),
		date_modified = NULL,
		modified_by = NULL
	FROM inserted i,
	   msdb.dbo.syspolicy_policies_internal policies
	WHERE i.policy_id = policies.policy_id
	
END -- create trigger
GO

PRINT 'Creating trigger [dbo].[syspolicy_update_job_update_trigger] on [dbo].[syspolicy_policies_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_update_job_update_trigger] on [dbo].[syspolicy_policies_internal]
FOR UPDATE
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

	-- This is to prevent indirect entrance of the trigger
	IF (TRIGGER_NESTLEVEL() > 1) RETURN

	-- verify that values in inserted.schedule_uid are valid
	IF EXISTS (
		SELECT * FROM inserted i 
		WHERE i.schedule_uid NOT IN 
				(SELECT schedule_uid FROM msdb.dbo.sysschedules ) AND 
		((i.execution_mode & 4) = 4))
	BEGIN
		ROLLBACK -- Failure
		RAISERROR (14365, -1, -1)
		RETURN
	END

	-- update eventing infrastructure
	IF(UPDATE(execution_mode) OR UPDATE(is_enabled))
	BEGIN
		IF  EXISTS (SELECT * 
					FROM inserted i
					INNER JOIN deleted d ON i.policy_id = d.policy_id
					WHERE 
						(((i.execution_mode & 1) = 1) OR ((d.execution_mode & 1) = 1)) AND 
						(i.is_enabled != d.is_enabled OR i.execution_mode != d.execution_mode))
		BEGIN
			EXEC sys.sp_syspolicy_update_ddl_trigger 
		END

		IF  EXISTS (SELECT * 
					FROM inserted i
					INNER JOIN deleted d ON i.policy_id = d.policy_id
					WHERE 
						(((i.execution_mode & 2) = 2) OR ((d.execution_mode & 2) = 2)) AND 
						(i.is_enabled != d.is_enabled OR i.execution_mode != d.execution_mode))
		BEGIN
			EXEC sys.sp_syspolicy_update_event_notification 
		END
	END

	DECLARE @jobID uniqueidentifier
	DECLARE @is_enabled bit
	DECLARE @schedule_uid uniqueidentifier

    -- set the job_id to NULL for all policies whose schedule_uid has changed
    -- so that we can create a job if needed
    UPDATE p
        SET p.job_id = NULL
        FROM msdb.dbo.syspolicy_policies p
        INNER JOIN inserted i ON p.policy_id = i.policy_id
        INNER JOIN deleted d ON d.policy_id = p.policy_id
        WHERE i.schedule_uid != d.schedule_uid

	-- find all schedules referenced by the inserted policies for which 
	-- there is no agent job that executes the policies
	DECLARE schedule_cursor CURSOR LOCAL FOR
	    SELECT DISTINCT i.schedule_uid
	    FROM inserted i
	    WHERE 
		    ((i.execution_mode & 4) = 4) AND
	        NOT EXISTS (SELECT * 
	                        FROM msdb.dbo.syspolicy_policies p 
	                        WHERE 
	                            p.schedule_uid = i.schedule_uid AND 
	                            ((p.execution_mode & 4) = 4) AND
	                            p.job_id IS NOT NULL)
	                    
	-- iterate through the cursor and create a job for every schedule		
	OPEN schedule_cursor
	FETCH schedule_cursor INTO @schedule_uid
	WHILE @@FETCH_STATUS = 0
	BEGIN
		-- figure out if the job is enabled or not
		SELECT @is_enabled = COUNT(*) 
		FROM inserted i 
		WHERE i.schedule_uid = @schedule_uid AND i.is_enabled = 1
		
		-- create the job that is going to execute the schedule
		EXEC [msdb].[dbo].[sp_syspolicy_create_job] @schedule_uid, @is_enabled, @jobID OUTPUT

		-- update the job_id back into the policies table
		UPDATE p SET p.job_id = @jobID 
			FROM msdb.dbo.syspolicy_policies_internal p
			INNER JOIN inserted i ON p.policy_id = i.policy_id
		WHERE 
			i.schedule_uid = @schedule_uid AND
			(i.execution_mode & 4) = 4
	
		FETCH schedule_cursor INTO @schedule_uid
	END
	
	CLOSE schedule_cursor
	DEALLOCATE schedule_cursor
	
	-- in case we haven't created the job we still need to update 
	-- the policies with their jobID
	UPDATE p 
		SET p.job_id = ( SELECT TOP 1 p2.job_id 
						FROM msdb.dbo.syspolicy_policies p2 
						WHERE 
							p2.schedule_uid = p.schedule_uid AND
							p2.job_id IS NOT NULL)
		FROM msdb.dbo.syspolicy_policies p
		INNER JOIN inserted i ON p.policy_id = i.policy_id
		WHERE 
			((p.execution_mode & 4) = 4) AND
			p.job_id IS NULL
	
	-- if the execution_mode has changed then we need to clear the job references
	UPDATE p
		SET p.job_id = NULL
		FROM msdb.dbo.syspolicy_policies_internal p
		INNER JOIN inserted i ON p.policy_id = i.policy_id 
		INNER JOIN deleted d ON p.policy_id = d.policy_id
		WHERE 
			((i.execution_mode & 4) != 4) AND
			((d.execution_mode & 4) = 4) AND
			p.job_id IS NOT NULL
	
	-- See what jobs we need to enable.
	-- This can happen because we might create a new policy that 
	-- is enabled and there is already a job for it, but the existing
	-- job is disabled
	DECLARE jobs_to_enable CURSOR LOCAL FOR
		SELECT DISTINCT j.job_id
		FROM dbo.sysjobs_view j 
		JOIN msdb.dbo.syspolicy_policies p ON p.job_id = j.job_id
		JOIN inserted i ON p.policy_id = i.policy_id
		WHERE 
			((i.execution_mode & 4) = 4) AND
			j.enabled = 0 AND
			EXISTS ( SELECT * from msdb.dbo.syspolicy_policies p2 
						WHERE p2.job_id = j.job_id AND p2.is_enabled = 1)
	
	OPEN jobs_to_enable
	FETCH jobs_to_enable INTO @jobID
	WHILE @@FETCH_STATUS = 0
	BEGIN
		EXEC msdb.dbo.sp_update_job @job_id = @jobID, @enabled = 1
		
		FETCH jobs_to_enable INTO @jobID
	END
	CLOSE jobs_to_enable
	DEALLOCATE jobs_to_enable

	-- Find out what jobs have to be deleted because the policy's schedule 
	-- has changed
	IF (UPDATE(schedule_uid))
	BEGIN
		DECLARE deleted_cursor CURSOR LOCAL FOR 
			SELECT DISTINCT d.job_id
			FROM deleted d
			WHERE 
				((d.execution_mode & 4) = 4) AND
				d.job_id NOT IN (SELECT job_id FROM msdb.dbo.syspolicy_policies p 
								WHERE 
								((p.execution_mode & 4) = 4))

		OPEN deleted_cursor
		FETCH deleted_cursor INTO @jobID
		
		WHILE (@@FETCH_STATUS=0)
		BEGIN
			-- delete the job(s), but do not delete the shared schedule
			IF (@jobID IS NOT NULL)
				EXEC msdb.dbo.sp_delete_job @job_id = @jobID, @delete_unused_schedule = 0

			FETCH deleted_cursor INTO @jobID
		END -- while (@@FETCH_STATUS=0)

		CLOSE deleted_cursor
		DEALLOCATE deleted_cursor
	END	-- UPDATE(schedule_uid)


	-- See what jobs we need to disable.
	-- This can happen because we do not need to delete the job, but
	-- all policies that reference it are disabled.
	DECLARE jobs_to_disable CURSOR LOCAL FOR
		SELECT DISTINCT j.job_id
		FROM dbo.sysjobs_view j 
		JOIN msdb.dbo.syspolicy_policies p ON p.job_id = j.job_id
		WHERE 
			j.enabled = 1 AND
			NOT EXISTS ( SELECT * from msdb.dbo.syspolicy_policies p2 
						WHERE p2.job_id = j.job_id AND p2.is_enabled = 1 AND ((p2.execution_mode & 4) = 4))
	
	OPEN jobs_to_disable
	FETCH jobs_to_disable INTO @jobID
	WHILE @@FETCH_STATUS = 0
	BEGIN
		EXEC msdb.dbo.sp_update_job @job_id = @jobID, @enabled = 0
		
		FETCH jobs_to_disable INTO @jobID
	END
	CLOSE jobs_to_disable
	DEALLOCATE jobs_to_disable

    UPDATE msdb.dbo.syspolicy_policies_internal
    SET modified_by = original_login(),
        date_modified = GETDATE()
    FROM inserted i,
       msdb.dbo.syspolicy_policies_internal policies
    WHERE i.policy_id = policies.policy_id
END -- update trigger
GO

PRINT 'Creating trigger [dbo].[syspolicy_insert_policy_trigger] on [dbo].[syspolicy_policies_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_insert_policy_trigger] on [dbo].[syspolicy_policies_internal]
FOR INSERT
AS
BEGIN
    DECLARE @object_set_id int, @name sysname

	SELECT TOP 1 @object_set_id = i.object_set_id, @name = i.name 
	FROM inserted i 
	WHERE 1 < (SELECT count(*) FROM syspolicy_policies p WHERE p.object_set_id = i.object_set_id)

	IF @@ROWCOUNT > 0
	BEGIN
				DECLARE @os_name sysname, @policy_name sysname
				SELECT TOP 1 @os_name = os.object_set_name, @policy_name = p.name
				 FROM syspolicy_object_sets os 
					INNER JOIN syspolicy_policies p ON (os.object_set_id = p.object_set_id)
				WHERE os.object_set_id = @object_set_id AND p.name <> @name
				
				RAISERROR(34013, -1, -1, 'ObjectSet', @os_name, @policy_name) 
				ROLLBACK TRANSACTION
	END
END
GO

PRINT 'Creating trigger [dbo].[syspolicy_update_policy_trigger] on [dbo].[syspolicy_policies_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_update_policy_trigger] on [dbo].[syspolicy_policies_internal]
FOR UPDATE
AS
BEGIN
    -- Protect against non-scheduled enabled policies that have scripts in their conditions
    IF( UPDATE(is_enabled) )
    BEGIN
        DECLARE @row_count int

        SELECT @row_count = count(*) 
            FROM syspolicy_conditions c  
            INNER JOIN inserted i ON (i.condition_id = c.condition_id OR i.root_condition_id = c.condition_id)
            WHERE    i.is_enabled != 0 AND
                    i.execution_mode != 4 AND
                    (1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteSql"]' ) OR
                    1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteWql"]' ) ) 
            OPTION (FORCE ORDER)

        SELECT @row_count = @row_count + count(*)
            FROM dbo.syspolicy_target_set_levels l 
            INNER JOIN dbo.syspolicy_target_sets s ON s.target_set_id = l.target_set_id
            INNER JOIN syspolicy_conditions c on c.condition_id = l.condition_id
            INNER JOIN syspolicy_object_sets_internal os on os.object_set_id = s.object_set_id
            INNER JOIN inserted i ON os.object_set_id = i.object_set_id
            WHERE    i.is_enabled != 0 AND
                    i.execution_mode != 4 AND
                    (1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteSql"]' ) OR
                    1 = CONVERT(xml, c.expression).exist('//FunctionType/text()[.="ExecuteWql"]' ) )
            OPTION (FORCE ORDER)

        IF (@row_count > 0)
        BEGIN
            RAISERROR(34017, -1, -1) 
            ROLLBACK TRANSACTION
        END
    END

	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END
	
	-- This is to prevent indirect execution of the trigger
	IF (TRIGGER_NESTLEVEL() > 1) RETURN

    IF( UPDATE(condition_id) )
    BEGIN
        -- delete all health state records for active policies whose 
        -- condition has changed
        DELETE FROM [dbo].[syspolicy_system_health_state_internal] 
            FROM [dbo].[syspolicy_system_health_state_internal] phs
            INNER JOIN inserted i ON phs.policy_id = i.policy_id
            INNER JOIN deleted d ON phs.policy_id = d.policy_id
            WHERE d.condition_id != i.condition_id AND i.is_enabled = 1
    END

    IF( UPDATE(object_set_id) )
    BEGIN
        DECLARE @object_set_id int, @numref int, @new_object_set_id int, @name sysname
        
        DECLARE os_cursor CURSOR LOCAL FOR 
        SELECT i.object_set_id, d.object_set_id, i.name
        FROM inserted i INNER JOIN deleted d ON (i.policy_id = d.policy_id) 
        WHERE (d.object_set_id IS NOT NULL AND i.object_set_id IS NULL)
			OR (i.object_set_id IS NOT NULL AND d.object_set_id IS NULL)
			OR (d.object_set_id != i.object_set_id)

        OPEN os_cursor
        FETCH os_cursor INTO @new_object_set_id, @object_set_id, @name
    
        WHILE @@FETCH_STATUS = 0 
        BEGIN
			IF (@object_set_id IS NOT NULL)
			BEGIN
				EXEC sp_syspolicy_verify_object_set_references @object_set_id, @numref OUTPUT
				IF (@numref = 0)
					EXEC sp_syspolicy_delete_object_set @object_set_id=@object_set_id
            END
            
			IF (@new_object_set_id IS NOT NULL)
			BEGIN
				EXEC sp_syspolicy_verify_object_set_references @new_object_set_id, @numref OUTPUT
				IF (@numref > 1)
				BEGIN
					DECLARE @os_name sysname, @policy_name sysname
					SELECT TOP 1 @os_name = os.object_set_name, @policy_name = p.name
					 FROM syspolicy_object_sets os 
						INNER JOIN syspolicy_policies p ON (os.object_set_id = p.object_set_id)
					WHERE os.object_set_id = @object_set_id AND p.name <> @name
					
					RAISERROR(34013, -1, -1, 'ObjectSet', @os_name, @policy_name) 
					ROLLBACK TRANSACTION
				END
            END
                
            FETCH os_cursor INTO @new_object_set_id, @object_set_id, @name
        END
            
        CLOSE os_cursor
        DEALLOCATE os_cursor    

    END

    IF( UPDATE(is_enabled) )
    BEGIN
        -- delete all health state records for policies that 
        -- have been disabled
        DELETE FROM [dbo].[syspolicy_system_health_state_internal] 
            FROM [dbo].[syspolicy_system_health_state_internal] phs
            INNER JOIN inserted i ON phs.policy_id = i.policy_id
            INNER JOIN deleted d ON phs.policy_id = d.policy_id
            WHERE d.is_enabled = 1 AND i.is_enabled = 0
    END

END
GO

PRINT 'Creating trigger [dbo].[syspolicy_delete_job_delete_trigger] on [dbo].[syspolicy_policies_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_delete_job_delete_trigger] on [dbo].[syspolicy_policies_internal]
FOR DELETE
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

	DECLARE @jobID uniqueidentifier 

	-- Declare the cursor to iterate over the jobs that are only referenced 
	-- by deleted policies. The jobs that are still referenced by active policies 
	-- should not be deleted.
	DECLARE deleted_cursor CURSOR LOCAL FOR 
		SELECT DISTINCT d.job_id
		FROM deleted d
		WHERE 
			((d.execution_mode & 4) = 4) AND
			NOT EXISTS (SELECT * FROM msdb.dbo.syspolicy_policies p 
							WHERE 
							p.job_id = d.job_id AND 
							((p.execution_mode & 4) = 4) AND
							p.policy_id NOT IN (SELECT d2.policy_id FROM deleted d2))

	OPEN deleted_cursor
	FETCH deleted_cursor INTO @jobID
	
	WHILE (@@FETCH_STATUS=0)
	BEGIN
		-- delete the job(s), but do not delete the shared schedule
		IF (@jobID IS NOT NULL)
			EXEC msdb.dbo.sp_delete_job @job_id = @jobID, @delete_unused_schedule = 0

		FETCH deleted_cursor INTO @jobID
	END -- while (@@FETCH_STATUS=0)

    CLOSE deleted_cursor
    DEALLOCATE deleted_cursor


	-- See what jobs we need to disable.
	-- This can happen because we do not need to delete the job, but
	-- all policies that reference it are disabled.
	DECLARE jobs_to_disable CURSOR LOCAL FOR
		SELECT DISTINCT j.job_id
		FROM dbo.sysjobs_view j 
		JOIN deleted d ON d.job_id = j.job_id
		WHERE 
			j.enabled = 1 AND
			NOT EXISTS ( SELECT * from msdb.dbo.syspolicy_policies p2 
						WHERE p2.job_id = j.job_id AND p2.is_enabled = 1 AND ((p2.execution_mode & 4) = 4))
	
	OPEN jobs_to_disable
	FETCH jobs_to_disable INTO @jobID
	WHILE @@FETCH_STATUS = 0
	BEGIN
		EXEC msdb.dbo.sp_update_job @job_id = @jobID, @enabled = 0
		
		FETCH jobs_to_disable INTO @jobID
	END
	CLOSE jobs_to_disable
	DEALLOCATE jobs_to_disable

	-- update eventing infrastructure
	IF EXISTS ( SELECT * FROM deleted d WHERE ((d.execution_mode & 1) = 1))
	BEGIN
		EXEC sys.sp_syspolicy_update_ddl_trigger 
	END

	IF EXISTS (SELECT * FROM deleted d WHERE ((d.execution_mode & 2) = 2))
	BEGIN
		EXEC sys.sp_syspolicy_update_event_notification 
	END

END
GO

PRINT 'Creating trigger [dbo].[syspolicy_instead_delete_policy_trigger] on [dbo].[syspolicy_policies_internal]'
GO

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- when changing logic in this trigger, please remember to change the logic in #sp_syspolicy_cascade_delete_policy of alwayson.sql
-------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE TRIGGER [dbo].[syspolicy_instead_delete_policy_trigger] on [dbo].[syspolicy_policies_internal]
INSTEAD OF DELETE 
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

	-- This trigger deletes references in given order to protect from deadlocks
	DELETE msdb.dbo.syspolicy_policy_execution_history_internal	WHERE policy_id in (SELECT policy_id FROM deleted)
	DELETE msdb.dbo.syspolicy_system_health_state_internal		WHERE policy_id in (SELECT policy_id FROM deleted)
	DELETE msdb.dbo.syspolicy_policies_internal		WHERE policy_id in (SELECT policy_id FROM deleted)
END
GO

PRINT 'Creating view [dbo].[syspolicy_policies]...'
GO
CREATE VIEW [dbo].[syspolicy_policies]
AS
    SELECT     
        policy_id,
        name,
        condition_id,
        root_condition_id,
        date_created,
        execution_mode,
        policy_category_id,
        schedule_uid,
        description,
        help_text,
        help_link,
        object_set_id,
        is_enabled,
        job_id,
        created_by,
        modified_by,
        date_modified,
        is_system
    FROM [dbo].[syspolicy_policies_internal]
GO

PRINT ''
PRINT 'Creating trigger syspolicy_insert_condition_trigger...'
GO

CREATE TRIGGER dbo.syspolicy_insert_condition_trigger
ON msdb.dbo.syspolicy_conditions_internal
AFTER INSERT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

  UPDATE msdb.dbo.syspolicy_conditions_internal
  SET created_by = original_login()
  FROM inserted i INNER JOIN
       msdb.dbo.syspolicy_conditions_internal conditions
  ON i.condition_id = conditions.condition_id

END
GO


PRINT ''
PRINT 'Creating trigger dbo.syspolicy_for_update_condition_trigger...'
GO

CREATE TRIGGER dbo.syspolicy_for_update_condition_trigger
ON msdb.dbo.syspolicy_conditions_internal
FOR UPDATE
AS
BEGIN
    -- do not allow expression to be changed to a script 
    -- if the policy is enabled
    IF UPDATE(expression)
    BEGIN
        DECLARE @row_count int

        SELECT @row_count = count(*) 
            FROM inserted i 
            INNER JOIN syspolicy_policies p ON (i.condition_id = p.condition_id OR p.root_condition_id = i.condition_id)
            WHERE    p.is_enabled != 0 AND
                    p.execution_mode != 4 AND
                    (1 = CONVERT(xml, i.expression).exist('//FunctionType/text()[.="ExecuteSql"]' ) OR
                    1 = CONVERT(xml, i.expression).exist('//FunctionType/text()[.="ExecuteWql"]' ) ) 
            OPTION (FORCE ORDER)

        SELECT @row_count = @row_count + count(*)
            FROM dbo.syspolicy_target_set_levels l 
            INNER JOIN dbo.syspolicy_target_sets s ON s.target_set_id = l.target_set_id
            INNER JOIN inserted i on i.condition_id = l.condition_id
            INNER JOIN syspolicy_object_sets_internal os ON s.object_set_id = os.object_set_id
            INNER JOIN syspolicy_policies p ON os.object_set_id = p.object_set_id
            WHERE    p.is_enabled != 0 AND
                    p.execution_mode != 4 AND
                    (1 = CONVERT(xml, i.expression).exist('//FunctionType/text()[.="ExecuteSql"]' ) OR
                    1 = CONVERT(xml, i.expression).exist('//FunctionType/text()[.="ExecuteWql"]' ) )
            OPTION (FORCE ORDER)

        IF (@row_count > 0)
        BEGIN
            RAISERROR(34017, -1, -1) 
            ROLLBACK TRANSACTION
        END
    END

	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

	-- This is to prevent indirect entrance of the trigger
	IF (TRIGGER_NESTLEVEL() > 1) 
		RETURN
END
GO

PRINT ''
PRINT 'Creating trigger syspolicy_after_update_condition_trigger...'
GO

CREATE TRIGGER dbo.syspolicy_after_update_condition_trigger
ON msdb.dbo.syspolicy_conditions_internal
AFTER UPDATE
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

	-- This is to prevent indirect entrance of the trigger
	IF (TRIGGER_NESTLEVEL() > 1) 
		RETURN

    UPDATE msdb.dbo.syspolicy_conditions_internal
        SET modified_by = original_login(), date_modified = GETDATE()
        FROM inserted i 
        INNER JOIN msdb.dbo.syspolicy_conditions_internal c ON i.condition_id = c.condition_id

    -- update health state table by deleting all the records for 
    -- policies whose expression has been modified
    IF UPDATE(expression)
    BEGIN
        DELETE FROM dbo.syspolicy_system_health_state_internal 
            FROM dbo.syspolicy_system_health_state_internal phs
            INNER JOIN dbo.syspolicy_policies p ON phs.policy_id = p.policy_id
            INNER JOIN inserted i ON p.condition_id = i.condition_id
            INNER JOIN deleted d ON p.condition_id = d.condition_id
            WHERE d.expression != i.expression 
    END
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_verify_policy_category_identifiers]...'
GO
-----------------------------------------------------------
-- This procedure verifies if a policy category exists
-- The caller can pass either the policy category name or the id
-----------------------------------------------------------
CREATE PROCEDURE [dbo].[sp_syspolicy_verify_policy_category_identifiers]
@policy_category_name sysname = NULL OUTPUT, 
@policy_category_id int = NULL OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

  IF ((@policy_category_name IS NULL)     AND (@policy_category_id IS NULL)) OR
     ((@policy_category_name IS NOT NULL) AND (@policy_category_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, '@policy_category_name', '@policy_category_id')
    RETURN(1) -- Failure
  END

  -- Check id
  IF (@policy_category_id IS NOT NULL)
  BEGIN
    SELECT @policy_category_name = name
    FROM msdb.dbo.syspolicy_policy_categories
    WHERE (policy_category_id = @policy_category_id)
    
    -- the view would take care of all the permissions issues.
    IF (@policy_category_name IS NULL) 
    BEGIN
      DECLARE @policy_category_id_as_char VARCHAR(36)
      SELECT @policy_category_id_as_char = CONVERT(VARCHAR(36), @policy_category_id)
      RAISERROR(14262, -1, -1, '@policy_category_id', @policy_category_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check name
  IF (@policy_category_name IS NOT NULL)
  BEGIN
    -- get the corresponding policy_category_id (if the condition exists)
    SELECT @policy_category_id = policy_category_id
    FROM msdb.dbo.syspolicy_policy_categories
    WHERE (name = @policy_category_name)
    
    -- the view would take care of all the permissions issues.
    IF (@policy_category_id IS NULL) 
    BEGIN
      RAISERROR(14262, -1, -1, '@policy_category_name', @policy_category_name)
      RETURN(1) -- Failure
    END
  END

  RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_add_policy_category]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_add_policy_category]
@name sysname,
@mandate_database_subscriptions bit = 1,
@policy_category_id int OUTPUT 
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval			INT
	DECLARE @null_column	sysname
	
	SET @null_column = NULL
	
	IF(@name IS NULL OR @name = N'')
		SET @null_column = '@name'

    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_syspolicy_add_policy_category')
        RETURN(1)
    END

    IF EXISTS (SELECT * FROM msdb.dbo.syspolicy_policy_categories_internal WHERE name = @name)
    BEGIN
        RAISERROR(34010, -1, -1, 'Policy Category', @name)
        RETURN(1)
    END

    INSERT INTO msdb.dbo.syspolicy_policy_categories_internal(name, mandate_database_subscriptions) VALUES (@name, @mandate_database_subscriptions)
    SELECT @retval = @@error
    SET @policy_category_id = SCOPE_IDENTITY()
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_delete_policy_category]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_delete_policy_category]
@name sysname = NULL,
@policy_category_id int = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_policy_category_identifiers @name, @policy_category_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    IF EXISTS (SELECT * FROM msdb.dbo.syspolicy_policy_category_subscriptions WHERE policy_category_id = @policy_category_id)
    BEGIN
        RAISERROR(34012,-1,-1,'Policy Category','Policy Subscription')
        RETURN (1)
    END


    IF EXISTS (SELECT * FROM msdb.dbo.syspolicy_policies WHERE policy_category_id = @policy_category_id)
    BEGIN
        RAISERROR(34012,-1,-1,'Policy Category','Policy')
        RETURN (1)
    END

    DELETE msdb.dbo.syspolicy_policy_categories_internal
    WHERE policy_category_id = @policy_category_id
    
    SET @retval = @@error
    RETURN @retval
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_rename_policy_category]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_rename_policy_category] 
@name sysname = NULL,
@policy_category_id int = NULL,
@new_name sysname = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    IF (@new_name IS NULL or LEN(@new_name) = 0)
    BEGIN
      RAISERROR(21263, -1, -1, '@new_name')
      RETURN(1) -- Failure
    END

    DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_policy_category_identifiers @name, @policy_category_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    UPDATE msdb.[dbo].[syspolicy_policy_categories_internal ]
    SET name = @new_name
    WHERE policy_category_id = @policy_category_id

    SELECT @retval = @@error
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_update_policy_category]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_update_policy_category] 
@name sysname = NULL,
@policy_category_id int = NULL,
@mandate_database_subscriptions bit = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_policy_category_identifiers @name, @policy_category_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    UPDATE msdb.[dbo].[syspolicy_policy_categories_internal ]
    SET mandate_database_subscriptions = ISNULL(@mandate_database_subscriptions, mandate_database_subscriptions)
    WHERE policy_category_id = @policy_category_id

    SELECT @retval = @@error
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_add_policy]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_add_policy] 
@name sysname,
@condition_id int = NULL,
@condition_name sysname = NULL,
@schedule_uid uniqueidentifier = NULL,
@policy_category sysname = NULL,
@description nvarchar(max) = N'',
@help_text nvarchar(4000) = N'',
@help_link nvarchar(2083) = N'',
@execution_mode int,
@is_enabled bit = 0,
@root_condition_id int = NULL,
@root_condition_name sysname = NULL,
@object_set sysname = NULL,
@policy_id int = NULL OUTPUT
WITH EXECUTE AS OWNER
AS
BEGIN
    DECLARE @retval_check int;
    EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
    IF ( 0!= @retval_check)
    BEGIN
        RETURN @retval_check
    END

    DECLARE @retval         INT
    DECLARE @null_column    sysname
    
    SET @null_column = NULL

    IF (@name IS NULL OR @name = N'')
        SET @null_column = '@name'
    ELSE IF (@execution_mode IS NULL )
        SET @null_column = '@execution_mode'
    ELSE IF( @is_enabled IS NULL)
        SET @null_column = '@is_enabled'
    ELSE IF( @description IS NULL)
        SET @null_column = '@description'
    ELSE IF( @help_text IS NULL)
        SET @null_column = '@help_text'
    ELSE IF( @help_link IS NULL)
        SET @null_column = '@help_link'
    

    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_syspolicy_add_policy')
        RETURN(1)
    END

    IF EXISTS (SELECT * FROM msdb.dbo.syspolicy_policies WHERE name = @name)
    BEGIN
        RAISERROR(34010, -1, -1, 'Policy', @name)
        RETURN(1)
    END

    SET @schedule_uid = ISNULL (@schedule_uid, '{00000000-0000-0000-0000-000000000000}')

    --Check for the execution mode value
    IF (@execution_mode NOT IN (0,1,2,4,5,6))
    BEGIN 
        RAISERROR(34004, -1, -1, @execution_mode)
        RETURN (1)
    END

    IF (@schedule_uid = '{00000000-0000-0000-0000-000000000000}' AND (@execution_mode & 4) = 4)
    BEGIN
        RAISERROR (34011, -1, -1, 'schedule_uid', 4)
        RETURN(1)
    END

    IF (@is_enabled = 1 AND @execution_mode = 0)
    BEGIN
        RAISERROR (34011, -1, -1, 'is_enabled', @execution_mode)
        RETURN(1)
    END

    -- Turn [nullable] empty string parameters into NULLs
    IF @condition_name = '' SELECT @condition_name = NULL
    IF @policy_category = ''   SELECT @policy_category = NULL
    IF @root_condition_name = '' SELECT @root_condition_name = NULL

    -- verify that the condition exists
    EXEC @retval = msdb.dbo.sp_syspolicy_verify_condition_identifiers @condition_name = @condition_name OUTPUT, @condition_id = @condition_id OUTPUT
    IF (@retval <> 0)
        RETURN(1)
        
    -- convert @object_set into id if needed
    DECLARE @object_set_id INT
    DECLARE @object_set_facet_id INT
    IF (@object_set IS NOT NULL)
    BEGIN
        SELECT @object_set_id = object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name = @object_set
        IF @object_set_id IS NULL
        BEGIN
            -- TODO: RAISERROR that specified object set doesn't exist
            RAISERROR(N'specified object set does not exists', -1, -1)
            RETURN(1) -- Failure
        END
        ELSE
        BEGIN
            SELECT @object_set_facet_id = facet_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name = @object_set
            -- Ensure the object set has been created from the same facet that the policy condition has been created
            IF (@object_set_facet_id <> (SELECT facet_id FROM msdb.dbo.syspolicy_conditions_internal WHERE condition_id = @condition_id))
            BEGIN
                -- TODO: RAISEERROR that specified object_set isn't created from the facet that the policy condition has been created from
                RAISERROR(N'specified object set does not match facet the policy condition was created off', -1, -1)
                RETURN(1) -- Failure
            END
        END
    END

    IF (@root_condition_name IS NOT NULL) OR (@root_condition_id IS NOT NULL)
    BEGIN
        -- verify that the root condition exists
        EXEC @retval = msdb.dbo.sp_syspolicy_verify_condition_identifiers @condition_name = @root_condition_name OUTPUT, @condition_id = @root_condition_id OUTPUT
        IF (@retval <> 0)
            RETURN(1)
            
        -- Check execution mode for compatibility with root_condition
        IF (@execution_mode = 1) OR (@execution_mode = 2) -- Enforce or Check on Change
        BEGIN
            RAISERROR (34011, -1, -1, 'root_condition', @execution_mode)
            RETURN(1)
        END

    END

    -- verify schedule
    IF (@schedule_uid != '{00000000-0000-0000-0000-000000000000}')
    BEGIN
        IF NOT EXISTS (SELECT * FROM msdb.dbo.sysschedules WHERE schedule_uid = @schedule_uid)
        BEGIN
            RAISERROR(14365, -1, -1)
            RETURN(1) -- Failure
        END
    END

    -- convert group_name into id if needed
    DECLARE @policy_category_id INT
    IF ( (@policy_category IS NOT NULL) )
    BEGIN 
        IF NOT EXISTS (SELECT * from msdb.dbo.syspolicy_policy_categories WHERE name = @policy_category)
        BEGIN
            RAISERROR(34015, -1, -1,@policy_category)
            RETURN(1) -- Failure
        END
        ELSE
            SELECT @policy_category_id = policy_category_id FROM msdb.dbo.syspolicy_policy_categories WHERE name = @policy_category
    END
    
    INSERT INTO msdb.dbo.syspolicy_policies_internal
                                        (name, 
                                        execution_mode, 
                                        schedule_uid,
                                        policy_category_id,
                                        description,
                                        help_text,
                                        help_link,
                                        condition_id,
                                        root_condition_id,
                                        object_set_id,
                                        is_enabled)
    VALUES                            
                                        (@name, 
                                        @execution_mode, 
                                        @schedule_uid,
                                        @policy_category_id,
                                        @description,
                                        @help_text,
                                        @help_link,
                                        @condition_id,
                                        @root_condition_id,
                                        @object_set_id,
                                        @is_enabled)

    SELECT @retval = @@error
    SET @policy_id = SCOPE_IDENTITY()
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_verify_policy_identifiers]...'
GO
-----------------------------------------------------------
-- This procedure verifies if a policy definition exists
-- The caller can pass either the name or the id
-----------------------------------------------------------
CREATE PROCEDURE [dbo].[sp_syspolicy_verify_policy_identifiers]
@name sysname = NULL OUTPUT, 
@policy_id int = NULL OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

  IF ((@name IS NULL)     AND (@policy_id IS NULL)) OR
     ((@name IS NOT NULL) AND (@policy_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, '@name', '@policy_id')
    RETURN(1) -- Failure
  END

  -- Check id
  IF (@policy_id IS NOT NULL)
  BEGIN
    SELECT @name = name
    FROM msdb.dbo.syspolicy_policies
    WHERE (policy_id = @policy_id)
    
    -- the view would take care of all the permissions issues.
    IF (@name IS NULL) 
    BEGIN
      DECLARE @policy_id_as_char VARCHAR(36)
      SELECT @policy_id_as_char = CONVERT(VARCHAR(36), @policy_id)
      RAISERROR(14262, -1, -1, '@policy_id', @policy_id_as_char)
      RETURN(1) -- Failure
    END
  END
  ELSE
  -- Check name
  IF (@name IS NOT NULL)
  BEGIN
    -- get the corresponding policy_id (if the policy exists)
    SELECT @policy_id = policy_id
    FROM msdb.dbo.syspolicy_policies
    WHERE (name = @name)
    
    -- the view would take care of all the permissions issues.
    IF (@policy_id IS NULL) 
    BEGIN
      RAISERROR(14262, -1, -1, '@name', @name)
      RETURN(1) -- Failure
    END
  END

  RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_rename_policy]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_rename_policy] 
@name sysname = NULL,
@policy_id int = NULL,
@new_name sysname = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    IF (@new_name IS NULL or LEN(@new_name) = 0)
    BEGIN
      RAISERROR(21263, -1, -1, '@new_name')
      RETURN(1) -- Failure
    END

    DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_policy_identifiers @name, @policy_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    UPDATE msdb.[dbo].[syspolicy_policies_internal] 
    SET name = @new_name
    WHERE policy_id = @policy_id

    SELECT @retval = @@error
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_update_policy]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_update_policy] 
@name sysname = NULL,
@policy_id int = NULL,
@condition_id int=NULL,
@condition_name sysname = NULL,
@execution_mode int=NULL,
@policy_category sysname = NULL,
@schedule_uid uniqueidentifier = NULL,
@description nvarchar(max) = NULL,
@help_text nvarchar(4000) = NULL,
@help_link nvarchar(2083) = NULL,
@root_condition_id int = -1,
@root_condition_name sysname = NULL,
@object_set_id int = -1,
@object_set sysname = NULL,
@is_enabled bit = NULL
WITH EXECUTE AS OWNER
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	--Check for the execution mode value
	IF (((@execution_mode IS NOT NULL)) AND (@execution_mode NOT IN (0,1,2,4,5,6)))
	BEGIN 
		RAISERROR(34004, -1, -1, @execution_mode)
		RETURN (1)
	END

    -- Turn [nullable] empty string parameters into NULLs
    IF @name = ''           SELECT @name = NULL
    IF @condition_name = '' SELECT @condition_name = NULL
    IF @root_condition_name = '' 
        BEGIN
        SELECT @root_condition_name = NULL
        IF @root_condition_id = -1
            -- root_condition is being reset
            SELECT @root_condition_id = NULL
        END
    IF @object_set = '' 
        BEGIN
        SELECT @object_set = NULL
        IF @object_set_id = -1
            -- object_set is being reset
            SELECT @object_set_id = NULL
        END

    DECLARE @retval              INT

    EXEC @retval = msdb.dbo.sp_syspolicy_verify_policy_identifiers @name, @policy_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    SELECT  @execution_mode = ISNULL(@execution_mode, execution_mode),
            @is_enabled = ISNULL(@is_enabled, is_enabled) 
        FROM msdb.dbo.syspolicy_policies WHERE policy_id = @policy_id

    IF(@condition_id IS NOT NULL or @condition_name IS NOT NULL)
    BEGIN
        EXEC @retval = msdb.dbo.sp_syspolicy_verify_condition_identifiers @condition_name = @condition_name OUTPUT, @condition_id = @condition_id OUTPUT
        IF (@retval <> 0)
            RETURN (1)
    END

    IF((@root_condition_id IS NOT NULL and @root_condition_id != -1) or @root_condition_name IS NOT NULL)
    BEGIN
        IF (@root_condition_id = -1 and @root_condition_name IS NOT NULL)
            SET @root_condition_id = NULL
        EXEC @retval = msdb.dbo.sp_syspolicy_verify_condition_identifiers @condition_name = @root_condition_name OUTPUT, @condition_id = @root_condition_id OUTPUT
        IF (@retval <> 0)
            RETURN (1)
    END

    SET @schedule_uid = ISNULL (@schedule_uid, '{00000000-0000-0000-0000-000000000000}')

    IF (@schedule_uid = '{00000000-0000-0000-0000-000000000000}' AND (@execution_mode & 4) = 4)
    BEGIN
        RAISERROR (34011, -1, -1, 'schedule_uid', 4)
        RETURN(1)
    END

    IF (@is_enabled = 1 AND @execution_mode = 0)
    BEGIN
        RAISERROR (34011, -1, -1, 'is_enabled', @execution_mode)
        RETURN(1)
    END

    IF (@schedule_uid != '{00000000-0000-0000-0000-000000000000}')
    BEGIN
        -- verify the schedule exists
        IF NOT EXISTS (SELECT schedule_id FROM msdb.dbo.sysschedules WHERE schedule_uid = @schedule_uid)
        BEGIN
            RAISERROR (14365, -1, -1)
            RETURN(1)
        END
    END

    DECLARE @object_set_facet_id INT
    IF ((@object_set_id IS NOT NULL and @object_set_id != -1) or @object_set IS NOT NULL)
    BEGIN
        IF (@object_set_id = -1 and @object_set IS NOT NULL)
            SET @object_set_id = NULL
        EXEC @retval = msdb.dbo.sp_syspolicy_verify_object_set_identifiers @name = @object_set OUTPUT, @object_set_id = @object_set_id OUTPUT
        IF (@retval <> 0)
            RETURN (1)

        SELECT @object_set_facet_id = facet_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name = @object_set
        -- Ensure the object set has been created from the same facet that the policy condition has been created
        IF (@object_set_facet_id <> (SELECT facet_id FROM msdb.dbo.syspolicy_conditions_internal WHERE condition_id = @condition_id))
        BEGIN
            -- TODO: RAISEERROR that specified object_set isn't created from the facet that the policy condition has been created from
            RAISERROR(N'specified object set does not match facet the policy condition was created off', -1, -1)
            RETURN(1) -- Failure
        END
    END

    DECLARE @policy_category_id INT
    SET @policy_category_id = NULL
    BEGIN TRANSACTION 

    DECLARE @old_policy_category_id INT
    SELECT @old_policy_category_id = policy_category_id 
        FROM syspolicy_policies 
        WHERE policy_id = @policy_id 

    IF ( (@policy_category IS NOT NULL and @policy_category != '') )
    BEGIN
        IF NOT EXISTS (SELECT * from syspolicy_policy_categories WHERE name = @policy_category)
        BEGIN
            RAISERROR(34015, -1, -1,@policy_category)
            RETURN(1) -- Failure
        END
        ELSE
            SELECT @policy_category_id = policy_category_id FROM msdb.dbo.syspolicy_policy_categories WHERE name = @policy_category
    END

        -- If the caller gave us an empty string for the
        -- @policy_category, then that means to remove the group.
    DECLARE @new_policy_category_id INT
        SELECT  @new_policy_category_id = @old_policy_category_id
    IF ( (@policy_category = '') )
                SELECT @new_policy_category_id = NULL
    ELSE IF (@policy_category_id IS NOT NULL)
                SELECT @new_policy_category_id = @policy_category_id

    UPDATE msdb.dbo.syspolicy_policies_internal
    SET 
        condition_id = ISNULL(@condition_id, condition_id),
        root_condition_id = CASE @root_condition_id WHEN -1 THEN root_condition_id ELSE @root_condition_id END,
        execution_mode = ISNULL(@execution_mode, execution_mode ),
        schedule_uid = @schedule_uid,
        policy_category_id = @new_policy_category_id, 
        description = ISNULL(@description, description),
        help_text = ISNULL(@help_text, help_text),
        help_link = ISNULL(@help_link, help_link),
        is_enabled = ISNULL(@is_enabled, is_enabled),
        object_set_id = CASE @object_set_id WHEN -1 THEN object_set_id ELSE @object_set_id END
    WHERE policy_id = @policy_id

    COMMIT TRANSACTION
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_delete_policy]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_delete_policy] 
@name sysname = NULL,
@policy_id int = NULL
WITH EXECUTE AS OWNER
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT

    EXEC @retval = sp_syspolicy_verify_policy_identifiers @name, @policy_id OUTPUT
    IF (@retval <> 0)
        RETURN (1)

    DELETE msdb.dbo.syspolicy_policies_internal 
        WHERE policy_id = @policy_id

    RETURN (0)
END
GO


IF NOT EXISTS (SELECT * FROM sys.types where name = 'syspolicy_target_filters_type')
BEGIN
    PRINT 'Creating type [dbo].[syspolicy_target_filters_type]...'
    CREATE TYPE [dbo].[syspolicy_target_filters_type]
    AS
    TABLE (
        target_filter_id int,
        policy_id int,
        type sysname NOT NULL,
        filter nvarchar(max) NOT NULL,
        type_skeleton sysname NOT NULL
        )
END
GO

PRINT 'Creating function [dbo].[syspolicy_fn_get_bad_filters]...'
GO

-- This function returns filters that are not supported
-- It is used to prevent unsupported filters from being
-- created. It will only reject well formed filters, in 
-- other words it will not perform a full syntax check.
CREATE FUNCTION [dbo].[syspolicy_fn_get_bad_filters] (
    @inserted [dbo].[syspolicy_target_filters_type] READONLY
)
RETURNS TABLE
AS
    RETURN 
    (
        SELECT filter FROM @inserted 
        WHERE    
            -- do not accept filters for the next level 
            filter LIKE N'Server/%/%\[@%=%\]%' ESCAPE '\' AND 
            -- take out cases when the property contains the pattern
            filter NOT LIKE 'Server/%\[%\[%\]%\]%' ESCAPE '\'
    )
GO

---------------------------------------------------------------
-- Target Set object
---------------------------------------------------------------

IF OBJECT_ID ('[dbo].[syspolicy_target_sets_internal]') IS NULL
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_target_sets_internal]...'
    CREATE TABLE [dbo].[syspolicy_target_sets_internal] (
        target_set_id int NOT NULL IDENTITY(1,1),
        object_set_id int NOT NULL,
        type_skeleton nvarchar(440) NOT NULL,
        type sysname NOT NULL,
        enabled bit NOT NULL,
        -- TODO: Verify if the primary access method of this table is based on policy_id then perhaps the clustered intdex should be on the policy id?
        CONSTRAINT [PK_syspolicy_target_sets] PRIMARY KEY CLUSTERED (target_set_id),
        )
    ALTER TABLE [dbo].[syspolicy_target_sets_internal] 
        ADD CONSTRAINT [FK_syspolicy_target_sets_syspolicy_object_sets] FOREIGN KEY(object_set_id)
        REFERENCES [dbo].[syspolicy_object_sets_internal] (object_set_id)
        ON DELETE CASCADE
    
    CREATE UNIQUE INDEX [UX_syspolicy_target_sets] ON [dbo].[syspolicy_target_sets_internal](object_set_id, type_skeleton)
END
GO

PRINT 'Creating view [dbo].[syspolicy_target_sets]...'
GO
CREATE VIEW [dbo].[syspolicy_target_sets]
AS
    SELECT     
        target_set_id,
        object_set_id,
        type_skeleton,
        type,
        enabled
    FROM [dbo].[syspolicy_target_sets_internal]
GO

IF OBJECT_ID ('[dbo].[syspolicy_target_set_levels_internal]') IS NULL
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_target_set_levels_internal]...'
    CREATE TABLE [dbo].[syspolicy_target_set_levels_internal] (
        target_set_level_id int NOT NULL IDENTITY(1,1),
        target_set_id int NOT NULL,
        type_skeleton nvarchar(440) NOT NULL,
        condition_id int NULL,
        level_name sysname NOT NULL,
        CONSTRAINT [PK_syspolicy_target_set_levels_internal] PRIMARY KEY CLUSTERED (target_set_level_id),
        )
    ALTER TABLE [dbo].[syspolicy_target_set_levels_internal] 
        ADD CONSTRAINT [FK_syspolicy_levels_target_sets] FOREIGN KEY(target_set_id)
        REFERENCES [dbo].[syspolicy_target_sets_internal] (target_set_id)
        ON DELETE CASCADE
    ALTER TABLE [dbo].[syspolicy_target_set_levels_internal] 
        ADD CONSTRAINT [FK_syspolicy_levels_conditions] FOREIGN KEY(condition_id)
        REFERENCES [dbo].[syspolicy_conditions_internal] (condition_id)
    
    CREATE UNIQUE INDEX [UX_syspolicy_levels] ON [dbo].[syspolicy_target_sets_internal](target_set_id, type_skeleton)
END
GO

PRINT 'Creating view [dbo].[syspolicy_target_set_levels]...'
GO
CREATE VIEW [dbo].[syspolicy_target_set_levels]
AS
    SELECT     
        target_set_level_id,
        target_set_id,
        type_skeleton,
        condition_id,
        level_name
    FROM [dbo].[syspolicy_target_set_levels_internal]
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_add_target_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_add_target_set] 
@object_set_id int = NULL,
@object_set_name sysname = NULL,
@type_skeleton nvarchar(max),
@type sysname,
@enabled bit,
@target_set_id int OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT

    EXEC @retval = dbo.sp_syspolicy_verify_object_set_identifiers @name = @object_set_name OUTPUT, @object_set_id = @object_set_id OUTPUT
    if( @retval <> 0)
        RETURN(1)
    
    INSERT INTO msdb.[dbo].[syspolicy_target_sets_internal]
                                        (object_set_id,
                                        type_skeleton,
                                        type,
                                        enabled)
    VALUES                            
                                        (@object_set_id, 
                                        @type_skeleton,
                                        @type,
                                        @enabled)

    SELECT @retval = @@error
    SET @target_set_id = SCOPE_IDENTITY()
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_update_target_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_update_target_set]
@target_set_id int,
@enabled bit
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	UPDATE [msdb].[dbo].[syspolicy_target_sets_internal]
	SET
		enabled = @enabled
	WHERE
		target_set_id = @target_set_id
	
	IF (@@ROWCOUNT = 0)
	BEGIN
		DECLARE @target_set_id_as_char VARCHAR(36)
		SELECT @target_set_id_as_char = CONVERT(VARCHAR(36), @target_set_id)
		RAISERROR(14262, -1, -1, '@target_set_id', @target_set_id_as_char)
		RETURN (1)
	END

    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_delete_target_set]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_delete_target_set] 
@target_set_id int
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DELETE msdb.[dbo].[syspolicy_target_sets_internal] 
		WHERE target_set_id = @target_set_id
	
	IF (@@ROWCOUNT = 0)
	BEGIN
		DECLARE @target_set_id_as_char VARCHAR(36)
		SELECT @target_set_id_as_char = CONVERT(VARCHAR(36), @target_set_id)
		RAISERROR(14262, -1, -1, '@target_set_id', @target_set_id_as_char)
		RETURN (1)
	END

    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_add_target_set_level]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_add_target_set_level] 
@target_set_id int,
@type_skeleton nvarchar(max),
@condition_id int = NULL,
@condition_name sysname = NULL,
@level_name sysname,
@target_set_level_id int OUTPUT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval              INT

    IF NOT EXISTS (SELECT * FROM syspolicy_target_sets WHERE target_set_id = @target_set_id)
            RETURN (1)

    IF (@condition_name = '')
        SET @condition_name = NULL

    IF(@condition_id IS NOT NULL or @condition_name IS NOT NULL)
    BEGIN
        EXEC @retval = msdb.dbo.sp_syspolicy_verify_condition_identifiers @condition_name = @condition_name OUTPUT, @condition_id = @condition_id OUTPUT
        IF (@retval <> 0)
            RETURN (1)
    END

    
    INSERT INTO msdb.[dbo].[syspolicy_target_set_levels_internal]
                                        (target_set_id,
                                        type_skeleton,
                                        condition_id,
                                        level_name)
    VALUES                            
                                        (@target_set_id,
                                        @type_skeleton,
                                        @condition_id,
                                        @level_name)

    SELECT @retval = @@error
    SET @target_set_level_id = SCOPE_IDENTITY()
    RETURN(@retval)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_update_target_set_level]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_update_target_set_level] 
@target_set_id int,
@type_skeleton nvarchar(max),
@condition_id int = NULL,
@condition_name sysname = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @retval int

    IF @condition_name = '' SET @condition_name = NULL

    IF(@condition_id IS NOT NULL or @condition_name IS NOT NULL)
    BEGIN
        EXEC @retval = msdb.dbo.sp_syspolicy_verify_condition_identifiers @condition_name = @condition_name OUTPUT, @condition_id = @condition_id OUTPUT
        IF (@retval <> 0)
            RETURN (1)
    END

    UPDATE msdb.[dbo].[syspolicy_target_set_levels_internal] 
        SET condition_id = @condition_id
        WHERE target_set_id = @target_set_id AND type_skeleton = @type_skeleton
    
    IF (@@ROWCOUNT = 0)
    BEGIN
        DECLARE @id nvarchar(max)
        SET @id = '@target_set_id='+LTRIM(STR(@target_set_id))+' @type_skeleton='''+@type_skeleton+''''
        RAISERROR(14262, -1, -1, 'Target Set Level', @id)
        RETURN (1)
    END

    RETURN (0)
END
GO

PRINT 'Creating function [dbo].[syspolicy_fn_eventing_filter]'
GO

CREATE FUNCTION [dbo].[syspolicy_fn_eventing_filter] (@target_set_id INT)
RETURNS INT
AS
BEGIN
    DECLARE @cnt int, @level sysname, @condition_id int, @ret int

    SELECT @cnt = count(*) FROM msdb.dbo.syspolicy_target_set_levels 
        WHERE target_set_id = @target_set_id AND condition_id IS NOT NULL
    IF @cnt = 0 
        RETURN 1
    ELSE IF @cnt > 1
        RETURN 0
    ELSE
        BEGIN
        SELECT @level = level_name, @condition_id = condition_id FROM msdb.dbo.syspolicy_target_set_levels 
            WHERE target_set_id = @target_set_id AND condition_id IS NOT NULL
        IF @level != 'Database'
            RETURN 0

		IF @condition_id IS NOT NULL
			BEGIN
			IF EXISTS (SELECT * FROM msdb.dbo.syspolicy_conditions  
				WHERE condition_id = @condition_id AND   
				(1 = CONVERT(xml, expression).exist('//FunctionType/text()[.="ExecuteSql"]') OR
				1 = CONVERT(xml, expression).exist('//FunctionType/text()[.="ExecuteWql"]') ) )
				RETURN 0
			END

        SELECT @ret = is_name_condition 
        FROM msdb.dbo.syspolicy_conditions    
        WHERE condition_id = @condition_id
        END

    RETURN @ret
END
GO

PRINT 'Creating function [dbo].[syspolicy_fn_filter_complete]'
GO

CREATE FUNCTION [dbo].[syspolicy_fn_filter_complete] (@target_set_id INT)
RETURNS INT
AS
BEGIN
    DECLARE @target_set_skeleton nvarchar(max), @skeleton nvarchar(max), @level sysname, @dummy nvarchar(max), @ret int, 
            @i int, @p int
    
    SELECT @target_set_skeleton = type_skeleton, @i=0, @p=CHARINDEX('/',type_skeleton)
        FROM msdb.dbo.syspolicy_target_sets 
        WHERE target_set_id = @target_set_id

    IF @@ROWCOUNT != 1 RETURN 0
    
    IF @target_set_skeleton = 'Server' RETURN 1    

    -- Count the number of levels in the skeleton past the root
    WHILE (@p <> 0)
        BEGIN
            SET @i = @i + 1
        SET @p = CHARINDEX('/', @target_set_skeleton, @p + 1)
        END

    -- Compare the number of levels in the skeleton with those in TSL
    IF (@i = (SELECT COUNT(*) FROM msdb.dbo.syspolicy_target_set_levels 
             WHERE target_set_id = @target_set_id))
        RETURN 1

    RETURN 0
END
GO

PRINT 'Creating trigger [dbo].[syspolicy_insert_target_set_level_trigger] on [dbo].[syspolicy_target_set_levels_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_insert_target_set_level_trigger] 
    ON [dbo].[syspolicy_target_set_levels_internal]
FOR INSERT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

    DECLARE @update_notifications INT
    DECLARE @update_ddl_trigger   INT

    SET @update_notifications = 0
    SET @update_ddl_trigger = 0

	DECLARE @level sysname
	SET @level = NULL
	
	-- Don't allow setting non-db levels for runtime policies
    SELECT TOP 1 @level = i.level_name
		FROM inserted i
		JOIN dbo.syspolicy_target_sets s ON (i.target_set_id = s.target_set_id)
		JOIN msdb.dbo.syspolicy_object_sets os ON s.object_set_id = os.object_set_id
		JOIN msdb.dbo.syspolicy_policies p ON (os.object_set_id = p.object_set_id)    
		WHERE 1 = dbo.syspolicy_fn_filter_complete (i.target_set_id) AND
			((p.execution_mode & 3) > 0 AND 0 = dbo.syspolicy_fn_eventing_filter (i.target_set_id))
	IF @level IS NOT NULL
    BEGIN
        RAISERROR(34016, -1, -1, @level) 
        ROLLBACK TRANSACTION
    END


    SELECT @update_notifications = SUM (p.execution_mode & 2), @update_ddl_trigger = SUM (p.execution_mode & 1)
        FROM inserted i
        JOIN dbo.syspolicy_target_sets s ON (i.target_set_id = s.target_set_id)
        JOIN msdb.dbo.syspolicy_object_sets_internal os ON (s.object_set_id = os.object_set_id)
        JOIN msdb.dbo.syspolicy_policies p ON (os.object_set_id = p.object_set_id)    
        WHERE 1 = dbo.syspolicy_fn_filter_complete (i.target_set_id) AND
            ((p.execution_mode & 3) > 0 AND p.is_enabled = 1 AND 1 = dbo.syspolicy_fn_eventing_filter (i.target_set_id))

    IF (@update_ddl_trigger > 0)
        EXEC sys.sp_syspolicy_update_ddl_trigger 

    IF    (@update_notifications > 0)    
        EXEC sys.sp_syspolicy_update_event_notification 
END
GO

PRINT 'Creating trigger [dbo].[syspolicy_update_target_set_level_trigger] on [dbo].[syspolicy_target_set_levels_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_update_target_set_level_trigger] ON [dbo].[syspolicy_target_set_levels_internal]
FOR UPDATE
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

    DECLARE @update_notifications INT
    DECLARE @update_ddl_trigger   INT

    SET @update_notifications = 0
    SET @update_ddl_trigger = 0

    IF UPDATE(condition_id)
    BEGIN
		DECLARE @level sysname
		SET @level = NULL
		
		-- Don't allow setting non-db levels for runtime policies
        SELECT TOP 1 @level = i.level_name
			FROM inserted i
			JOIN dbo.syspolicy_target_sets s ON (i.target_set_id = s.target_set_id)
			JOIN msdb.dbo.syspolicy_object_sets os ON s.object_set_id = os.object_set_id
			JOIN msdb.dbo.syspolicy_policies p ON (os.object_set_id = p.object_set_id)    
			WHERE 1 = dbo.syspolicy_fn_filter_complete (i.target_set_id) AND
				((p.execution_mode & 3) > 0 AND 0 = dbo.syspolicy_fn_eventing_filter (i.target_set_id))
		IF @level IS NOT NULL
        BEGIN
            RAISERROR(34016, -1, -1, @level) 
            ROLLBACK TRANSACTION
        END
    END

    SELECT @update_notifications = SUM (p.execution_mode & 2), @update_ddl_trigger = SUM (p.execution_mode & 1)
        FROM inserted i
        JOIN dbo.syspolicy_target_sets s ON (i.target_set_id = s.target_set_id)
        JOIN msdb.dbo.syspolicy_object_sets os ON s.object_set_id = os.object_set_id
        JOIN msdb.dbo.syspolicy_policies p ON (os.object_set_id = p.object_set_id)    
        WHERE 1 = dbo.syspolicy_fn_filter_complete (i.target_set_id) AND
            ((p.execution_mode & 3) > 0 AND p.is_enabled = 1 AND 1 = dbo.syspolicy_fn_eventing_filter (i.target_set_id))

    IF (@update_ddl_trigger > 0)
        EXEC sys.sp_syspolicy_update_ddl_trigger 

    IF    (@update_notifications > 0)    
        EXEC sys.sp_syspolicy_update_event_notification 

END
GO


PRINT 'Creating trigger [dbo].[syspolicy_insert_target_set_trigger] on [dbo].[syspolicy_target_sets_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_insert_target_set_trigger] 
    ON [dbo].[syspolicy_target_sets_internal]
FOR INSERT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

    DECLARE @update_notifications INT
    DECLARE @update_ddl_trigger   INT

    SET @update_notifications = 0
    SET @update_ddl_trigger = 0

    -- Only need to check Server TargetSets, as they don't have levels
    SELECT @update_notifications = SUM (p.execution_mode & 2), @update_ddl_trigger = SUM (p.execution_mode & 1)
        FROM inserted i
        JOIN msdb.dbo.syspolicy_object_sets_internal os ON i.object_set_id = os.object_set_id
        JOIN msdb.dbo.syspolicy_policies p ON (os.object_set_id = p.object_set_id)    
        WHERE i.type = 'SERVER' AND ((p.execution_mode & 3) > 0 AND p.is_enabled = 1)

    IF (@update_ddl_trigger > 0)
        EXEC sys.sp_syspolicy_update_ddl_trigger 

    IF    (@update_notifications > 0)    
        EXEC sys.sp_syspolicy_update_event_notification 
END
GO

PRINT 'Creating trigger [dbo].[syspolicy_delete_target_set_trigger] on [dbo].[syspolicy_target_sets_internal]'
GO

CREATE TRIGGER [dbo].[syspolicy_delete_target_set_trigger] ON [dbo].[syspolicy_target_sets_internal]
FOR DELETE
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

    DECLARE @update_notifications INT
    DECLARE @update_ddl_trigger   INT

    SET @update_notifications = 0
    SET @update_ddl_trigger = 0

    -- If this is cascade delete, there will be no policies to join
    SELECT @update_notifications = SUM (p.execution_mode & 2), @update_ddl_trigger = SUM (p.execution_mode & 1)
        FROM deleted d
        JOIN msdb.dbo.syspolicy_object_sets_internal os ON d.object_set_id = os.object_set_id
        JOIN msdb.dbo.syspolicy_policies p ON (os.object_set_id = p.object_set_id)    
        WHERE ((p.execution_mode & 3) > 0 AND p.is_enabled = 1)

    IF (@update_ddl_trigger > 0)
        EXEC sys.sp_syspolicy_update_ddl_trigger 

    IF    (@update_notifications > 0)    
        EXEC sys.sp_syspolicy_update_event_notification 
END
GO


---------------------------------------------------------------
-- Policy category subscription object
---------------------------------------------------------------

IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_policy_category_subscriptions_internal')
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_policy_category_subscriptions_internal]...';
    CREATE TABLE [dbo].[syspolicy_policy_category_subscriptions_internal] (
        policy_category_subscription_id int IDENTITY(1,1),
        target_type sysname NOT NULL,
        target_object sysname NOT NULL, 
        policy_category_id int NOT NULL,
        CONSTRAINT [PK_syspolicy_policy_category_subscriptions] PRIMARY KEY CLUSTERED (policy_category_subscription_id ASC)
        );

    CREATE UNIQUE INDEX [UX_syspolicy_policy_category_subscriptions] ON [dbo].[syspolicy_policy_category_subscriptions_internal](policy_category_id, target_object, target_type)

    ALTER TABLE [dbo].[syspolicy_policy_category_subscriptions_internal] 
        ADD CONSTRAINT [FK_syspolicy_policy_category_subscriptions_syspolicy_policy_categories] FOREIGN KEY(policy_category_id)
        REFERENCES [dbo].[syspolicy_policy_categories_internal] (policy_category_id)
        ON DELETE CASCADE;
END
GO

PRINT 'Creating view [dbo].[syspolicy_policy_category_subscriptions]...'
GO
CREATE VIEW [dbo].[syspolicy_policy_category_subscriptions]
AS
    SELECT     
        policy_category_subscription_id,
        target_type,
        target_object,
        policy_category_id
    FROM [dbo].[syspolicy_policy_category_subscriptions_internal]
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_add_policy_category_subscription]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_add_policy_category_subscription] 
    @target_type sysname,
    @target_object sysname,
    @policy_category sysname,
    @policy_category_subscription_id int = NULL OUTPUT
WITH EXECUTE AS OWNER
AS
BEGIN
    DECLARE @retval int    

 IF(@target_type IS NOT NULL)
	BEGIN
		IF(@target_type <> 'DATABASE')
		BEGIN
			RAISERROR(34018,-1,-1,@target_type);
			RETURN(1)
		END
	END


 IF(NOT EXISTS(SELECT * FROM sys.databases WHERE name=@target_object))
	BEGIN
		RAISERROR(34019,-1,-1,@target_object);
		RETURN(1)
	END

    -- convert category_name into id if needed
    DECLARE @policy_category_id INT
    BEGIN TRANSACTION 
    IF ( (@policy_category IS NOT NULL AND @policy_category != '') )
    BEGIN 
        IF NOT EXISTS (SELECT * from syspolicy_policy_categories WHERE name = @policy_category)
        BEGIN
            INSERT INTO syspolicy_policy_categories_internal(name) VALUES (@policy_category)
            SELECT @policy_category_id = SCOPE_IDENTITY()
        END
        ELSE
            SELECT @policy_category_id = policy_category_id FROM syspolicy_policy_categories WHERE name = @policy_category
    END
    
    INSERT INTO msdb.[dbo].[syspolicy_policy_category_subscriptions_internal]
                                            (target_type, 
                                            target_object,
                                            policy_category_id)
        VALUES                            
                                            (@target_type, 
                                            @target_object, 
                                            @policy_category_id)

    SELECT @retval = @@error
    SET @policy_category_subscription_id = SCOPE_IDENTITY()

    COMMIT TRANSACTION
    RETURN(@retval)

END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_update_policy_category_subscription]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_update_policy_category_subscription] 
@policy_category_subscription_id int,
@target_type sysname = NULL,
@target_object sysname = NULL,
@policy_category sysname = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	-- Turn [nullable] empty string parameters into NULLs
	IF @target_type = ''      SELECT @target_type = NULL
	IF @target_object = ''    SELECT @target_object = NULL
	IF @policy_category = ''  SELECT @policy_category = NULL

    IF(@target_type IS NOT NULL)
	BEGIN
		IF(LOWER(@target_type) <> 'database')
		BEGIN
			RAISERROR(34018,-1,-1,@target_type);
			RETURN(1)
		END
	END
	
	IF(@target_object IS NOT NULL AND NOT EXISTS(SELECT * FROM sys.databases WHERE name=@target_object))
	BEGIN
		RAISERROR(34019,-1,-1,@target_object);
		RETURN(1)
	END

    DECLARE @policy_category_id INT
    SET @policy_category_id = NULL
    BEGIN TRANSACTION 

    DECLARE @old_policy_category_id INT
    SET @old_policy_category_id = NULL

    IF ( (@policy_category IS NOT NULL) )
    BEGIN 
        IF NOT EXISTS (SELECT * from syspolicy_policy_categories WHERE name = @policy_category)
        BEGIN
            -- add a new policy category
            INSERT INTO syspolicy_policy_categories_internal(name) VALUES (@policy_category)
            SELECT @policy_category_id = SCOPE_IDENTITY()

            SELECT @old_policy_category_id = policy_category_id 
                FROM syspolicy_policy_category_subscriptions 
                WHERE policy_category_subscription_id = @policy_category_subscription_id 
        END
        ELSE
            SELECT @policy_category_id = policy_category_id FROM syspolicy_policy_categories WHERE name = @policy_category
    END
    
    DECLARE @group_usage_count INT
    SELECT @group_usage_count = COUNT(*) 
        FROM syspolicy_policy_category_subscriptions  
        WHERE policy_category_id = @old_policy_category_id

    SELECT @group_usage_count = @group_usage_count + COUNT(*) 
        FROM syspolicy_policies  
        WHERE policy_category_id = @old_policy_category_id

    UPDATE msdb.[dbo].[syspolicy_policy_category_subscriptions_internal] 
        SET
            target_type            = ISNULL(@target_type, target_type),
            target_object       = ISNULL(@target_object, target_object),
            policy_category_id        = ISNULL(@policy_category_id, policy_category_id)
        WHERE policy_category_subscription_id = @policy_category_subscription_id

    IF (@@ROWCOUNT = 0)
    BEGIN
        DECLARE @policy_category_subscription_id_as_char VARCHAR(36)
        SELECT @policy_category_subscription_id_as_char = CONVERT(VARCHAR(36), @policy_category_subscription_id)
        RAISERROR(14262, -1, -1, '@policy_category_subscription_id', @policy_category_subscription_id_as_char)
        ROLLBACK TRANSACTION
        RETURN(1) -- Failure
    END

    -- delete the old entry if it was used only by this policy
    DELETE syspolicy_policy_categories_internal WHERE 
        policy_category_id = @old_policy_category_id
        AND 1 = @group_usage_count

    COMMIT TRANSACTION
    RETURN (0)
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_delete_policy_category_subscription]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_delete_policy_category_subscription] 
@policy_category_subscription_id int
WITH EXECUTE AS OWNER
AS
BEGIN
    DECLARE @old_policy_category_id INT
    SELECT @old_policy_category_id = policy_category_id 
        FROM dbo.syspolicy_policy_category_subscriptions 
        WHERE policy_category_subscription_id = @policy_category_subscription_id

    DECLARE @group_usage_count INT
    SELECT @group_usage_count = COUNT(name) 
        FROM syspolicy_policies pd 
        WHERE pd.policy_category_id = @old_policy_category_id

    DECLARE @subscription_group_usage_count INT
    SELECT @subscription_group_usage_count = COUNT(*) 
        FROM syspolicy_policy_category_subscriptions  
        WHERE policy_category_id = @old_policy_category_id

    SELECT @group_usage_count = @group_usage_count + @subscription_group_usage_count

    DELETE msdb.dbo.syspolicy_policy_category_subscriptions_internal 
        WHERE policy_category_subscription_id = @policy_category_subscription_id

    IF (@@ROWCOUNT = 0)
    BEGIN
        DECLARE @policy_category_subscription_id_as_char VARCHAR(36)
        SELECT @policy_category_subscription_id_as_char = CONVERT(VARCHAR(36), @policy_category_subscription_id)
        RAISERROR(14262, -1, -1, '@policy_category_subscription_id', @policy_category_subscription_id_as_char)
        RETURN(1) -- Failure
    END

    RETURN (0)
END
GO

GO
IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_system_health_state_internal')
BEGIN
	PRINT 'Creating table [dbo].[syspolicy_system_health_state_internal]...'
	CREATE TABLE [dbo].[syspolicy_system_health_state_internal](
			health_state_id bigint IDENTITY PRIMARY KEY CLUSTERED,
			policy_id int NOT NULL REFERENCES [dbo].[syspolicy_policies_internal],
			last_run_date datetime NOT NULL, 
			target_query_expression_with_id nvarchar(400) NOT NULL, 
			target_query_expression nvarchar(max) NOT NULL, 
			result bit NOT NULL);
	CREATE INDEX IX_syspolicy_system_health_state_internal_policy_id ON 
		[dbo].[syspolicy_system_health_state_internal](policy_id);
	CREATE INDEX IX_syspolicy_system_health_state_internal_target_query_expression_with_id ON
		[dbo].[syspolicy_system_health_state_internal](target_query_expression_with_id);
END
GO

CREATE VIEW [dbo].[syspolicy_system_health_state]
AS
    SELECT 
        health_state_id,
        policy_id,
        last_run_date,
        target_query_expression_with_id,
        target_query_expression,
        result
    FROM [dbo].[syspolicy_system_health_state_internal]
GO

IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_policy_execution_history_internal')
BEGIN
	PRINT 'Creating table [dbo].[syspolicy_policy_execution_history_internal]...';
	CREATE TABLE syspolicy_policy_execution_history_internal (
		history_id bigint IDENTITY PRIMARY KEY CLUSTERED,
		policy_id int NOT NULL REFERENCES syspolicy_policies_internal,
		start_date datetime NOT NULL DEFAULT (GETDATE()),
		end_date datetime NULL,
		result bit NOT NULL DEFAULT (0),
		is_full_run bit NOT NULL DEFAULT (1),
		exception_message nvarchar(max) NULL,
		exception nvarchar(max) NULL
	);
	CREATE INDEX IX_syspolicy_policy_execution_history_internal_end_date_policy_id 
		ON [dbo].[syspolicy_policy_execution_history_internal](policy_id, end_date);
	CREATE INDEX IX_syspolicy_policy_execution_history_internal_policy_id 
		ON [dbo].[syspolicy_policy_execution_history_internal](policy_id);
END

PRINT 'Creating trigger [syspolicy_update_system_health_state]...'
GO
CREATE TRIGGER [syspolicy_update_system_health_state] ON [dbo].[syspolicy_policy_execution_history_internal]
AFTER UPDATE
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

    -- if the entire policy has been checked delete all entries 
    -- regarding that policy
    DELETE FROM [dbo].[syspolicy_system_health_state_internal] 
        WHERE policy_id in (SELECT policy_id FROM inserted WHERE is_full_run = 1)

    -- Note: in the queries below new records are added only for
    -- policies that are enabled for automation

    -- if the policy is evaluated against a single target
    -- delete the old entry
    DELETE FROM [dbo].[syspolicy_system_health_state_internal] 
        WHERE policy_id in 
            (SELECT i.policy_id FROM inserted i WHERE i.is_full_run = 0) AND
                target_query_expression_with_id in (SELECT target_query_expression_with_id 
                                                    FROM [dbo].[syspolicy_policy_execution_history_details_internal] d 
                                                    INNER JOIN inserted i2 ON i2.history_id = d.history_id 
                                                    WHERE i2.is_full_run = 0)


    -- insert the detail rows, but only for failures
    -- this is done both for the full runs and for the partial runs
    -- we will not insert anything if this is a ghost record, i.e. 
    -- target_query_expression_with_id is null 
    -- this will happen when we log prevent policies
    INSERT INTO [dbo].[syspolicy_system_health_state_internal] 
        (policy_id, last_run_date, target_query_expression_with_id, target_query_expression, result)
    SELECT i.policy_id, d.execution_date, d.target_query_expression_with_id, d.target_query_expression, d.result
        FROM inserted i 
        INNER JOIN [dbo].[syspolicy_policy_execution_history_details_internal] d on i.history_id = d.history_id
        INNER JOIN [dbo].[syspolicy_policies] p on i.policy_id = p.policy_id
        WHERE d.result = 0 AND p.is_enabled = 1 AND d.target_query_expression_with_id != N''
        
    -- delete all the success detail rows with no expression
    -- these are rows inserted so that we can update the system health state
    -- make an exception if the global switch says we should keep those records
    IF( 0 = (SELECT ISNULL(convert(bit, current_value), 0) FROM msdb.dbo.syspolicy_configuration WHERE name = 'LogOnSuccess'))
    BEGIN
        DELETE FROM d
        FROM [dbo].[syspolicy_policy_execution_history_details_internal] d
        INNER JOIN inserted i ON i.history_id = d.history_id
            WHERE d.result_detail = N''
    END
END
GO

CREATE VIEW [dbo].[syspolicy_policy_execution_history]
AS
    SELECT 
        history_id,
        policy_id,
        start_date,
        end_date,
        result,
        exception_message,
        exception
    FROM [dbo].[syspolicy_policy_execution_history_internal]
GO

IF NOT EXISTS (SELECT * FROM sys.tables where name = 'syspolicy_policy_execution_history_details_internal')
BEGIN
	PRINT 'Creating table [dbo].[syspolicy_policy_execution_history_details_internal]...';
    CREATE TABLE syspolicy_policy_execution_history_details_internal 
    (
          detail_id bigint IDENTITY,
          history_id bigint NOT NULL REFERENCES syspolicy_policy_execution_history_internal ON DELETE CASCADE,
          target_query_expression nvarchar(4000) NOT NULL,
          target_query_expression_with_id nvarchar(4000) NOT NULL,
          execution_date datetime NOT NULL DEFAULT (GETDATE()),
          result bit NOT NULL,
          result_detail nvarchar(max) NULL, 
          exception_message nvarchar(max) NULL,
          exception nvarchar(max) NULL,
          CONSTRAINT [PK_syspolicy_policy_execution_history_details_id] PRIMARY KEY CLUSTERED(history_id, detail_id)
    )
END
GO

CREATE VIEW [dbo].[syspolicy_policy_execution_history_details]
AS
    SELECT 
        detail_id,
        history_id,
        target_query_expression,
        execution_date,
        result,
        result_detail,
        exception_message,
        exception
    FROM [dbo].[syspolicy_policy_execution_history_details_internal]
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_log_policy_execution_start]...'
GO
CREATE PROC [dbo].[sp_syspolicy_log_policy_execution_start] 
    @policy_id int,
    @is_full_run bit,
    @history_id bigint OUTPUT 
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole', 0
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END
    DECLARE @ret int

    SET @history_id = 0

    EXEC @ret = dbo.sp_syspolicy_verify_policy_identifiers NULL, @policy_id
    IF @ret <> 0 RETURN -1

    INSERT syspolicy_policy_execution_history_internal (policy_id, is_full_run) VALUES (@policy_id, @is_full_run) 
    SET @history_id = SCOPE_IDENTITY ()
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_log_policy_execution_end]...'
GO
CREATE PROC [dbo].[sp_syspolicy_log_policy_execution_end] 
    @history_id bigint, 
    @result bit,
    @exception_message nvarchar(max) = NULL,
    @exception nvarchar(max) = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole', 0
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    UPDATE syspolicy_policy_execution_history_internal 
      SET result = @result,
          end_date = GETDATE(),
          exception_message = @exception_message,
          exception = @exception
      WHERE history_id = @history_id
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_log_policy_execution_detail]...'
GO
CREATE PROC [dbo].[sp_syspolicy_log_policy_execution_detail] 
 @history_id bigint, 
 @target_query_expression nvarchar(4000), 
 @target_query_expression_with_id nvarchar(4000), 
 @result bit, 
 @result_detail nvarchar(max),
 @exception_message nvarchar(max) = NULL,
 @exception nvarchar(max) = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole', 0
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END
    BEGIN TRANSACTION
    DECLARE @is_valid_entry INT
    -- take an update lock on this table first to prevent deadlock
    SELECT @is_valid_entry = count(*) FROM syspolicy_policy_execution_history_internal
        WITH (UPDLOCK) 
        WHERE history_id = @history_id

    INSERT syspolicy_policy_execution_history_details_internal (
                                history_id, 
                                target_query_expression, 
                                target_query_expression_with_id, 
                                result, 
                                result_detail,
                                exception_message,
                                exception) 
                        VALUES (
                                @history_id, 
                                @target_query_expression, 
                                @target_query_expression_with_id, 
                                @result, 
                                @result_detail,
                                @exception_message,
                                @exception) 
    IF( @@TRANCOUNT > 0)
        COMMIT
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_delete_policy_execution_history]...'
GO
CREATE PROC [dbo].[sp_syspolicy_delete_policy_execution_history] 
 @policy_id int,
 @oldest_date datetime
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    IF @oldest_date IS NULL
        BEGIN
        IF (@policy_id IS NULL)
            DELETE syspolicy_policy_execution_history_internal
        ELSE
            DELETE syspolicy_policy_execution_history_internal WHERE policy_id = @policy_id
        END
    ELSE
        BEGIN
        IF (@policy_id IS NULL)
            DELETE syspolicy_policy_execution_history_internal WHERE start_date < @oldest_date
        ELSE
            DELETE syspolicy_policy_execution_history_internal WHERE policy_id = @policy_id AND start_date < @oldest_date
        END
END
GO


PRINT 'Creating procedure [dbo].[sp_syspolicy_mark_system]...'
GO
CREATE PROC dbo.sp_syspolicy_mark_system @type sysname, @name sysname=NULL, @object_id int=NULL, @marker bit=NULL 
AS
BEGIN
	-- If @marker IS NULL simple return the state

    DECLARE @retval_check int;
    EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
    IF ( 0!= @retval_check)
    BEGIN
        RETURN @retval_check
    END

	DECLARE @retval int
	
	IF (UPPER(@type  collate SQL_Latin1_General_CP1_CS_AS) = N'POLICY')
	BEGIN
	    EXEC @retval = sp_syspolicy_verify_policy_identifiers @name, @object_id OUTPUT
		IF (@retval <> 0)
			RETURN (1)

		IF @marker IS NULL
		BEGIN
			SELECT policy_id, name, is_system FROM syspolicy_policies_internal WHERE policy_id = @object_id
		END
		ELSE
		BEGIN
			UPDATE msdb.dbo.syspolicy_policies_internal
			SET is_system = @marker 
			WHERE policy_id = @object_id
		END
	END
	ELSE IF (UPPER(@type collate SQL_Latin1_General_CP1_CS_AS) = N'CONDITION')
	BEGIN
	    EXEC @retval = sp_syspolicy_verify_condition_identifiers @name, @object_id OUTPUT
		IF (@retval <> 0)
			RETURN (1)

		IF @marker IS NULL
		BEGIN
			SELECT condition_id, name, is_system FROM syspolicy_conditions_internal WHERE condition_id = @object_id
		END
		ELSE
		BEGIN
			UPDATE msdb.dbo.syspolicy_conditions_internal
			SET is_system = @marker 
			WHERE condition_id = @object_id
		END
	END
	ELSE IF (UPPER(@type collate SQL_Latin1_General_CP1_CS_AS) = N'OBJECTSET')
	BEGIN
	    EXEC @retval = sp_syspolicy_verify_object_set_identifiers @name, @object_id OUTPUT
		IF (@retval <> 0)
			RETURN (1)

		IF @marker IS NULL
		BEGIN
			SELECT object_set_id, object_set_name, is_system FROM syspolicy_object_sets_internal WHERE object_set_id = @object_id
		END
		ELSE
		BEGIN
			UPDATE msdb.dbo.syspolicy_object_sets_internal
			SET is_system = @marker 
			WHERE object_set_id = @object_id
		END
	END
    ELSE
    BEGIN
		RAISERROR(14262, -1, -1, '@type', @type)
		RETURN(1) -- Failure
	END
	
    SELECT @retval = @@error
    RETURN(@retval)
END
GO 




-----------------------------------------------------------
-- event processing
-----------------------------------------------------------

PRINT 'Creating function [dbo].[syspolicy_fn_get_type_name]...'
GO
CREATE FUNCTION [dbo].[syspolicy_fn_get_type_name](@event_type_name sysname)
RETURNS sysname
AS
BEGIN
    RETURN 
    (CASE LOWER(@event_type_name)
        WHEN 'procedure' THEN 'StoredProcedure'
        WHEN 'function' THEN 'UserDefinedFunction'
        WHEN 'type' THEN 'UserDefinedType'
        WHEN 'sql user' THEN 'User'
        WHEN 'certificate user' THEN 'User'
        WHEN 'asymmetric key user' THEN 'User'
        WHEN 'windows user' THEN 'User'
        WHEN 'group user' THEN 'User'
        WHEN 'application role' THEN 'ApplicationRole'
        ELSE UPPER(SUBSTRING(@event_type_name, 1,1)) + LOWER(SUBSTRING(@event_type_name, 2,LEN(@event_type_name)))
    END)
END
GO

IF OBJECT_ID ('[dbo].[syspolicy_execution_internal]') IS NULL
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_execution_internal]...'
    CREATE TABLE [dbo].[syspolicy_execution_internal] (
        policy_id int,
        synchronous bit,
        event_data xml)
END
GO

IF OBJECT_ID ('[dbo].[syspolicy_execution_trigger]') IS NOT NULL
BEGIN
    DROP TRIGGER [dbo].[syspolicy_execution_trigger] 
END
GO
CREATE TRIGGER [dbo].[syspolicy_execution_trigger] ON [dbo].[syspolicy_execution_internal]
INSTEAD OF INSERT
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN;
	END

	IF NOT EXISTS (SELECT * FROM inserted)
		RETURN

    DECLARE @policy_id int
    DECLARE @synchronous bit
    DECLARE @event_data xml
    DECLARE affected_policies CURSOR LOCAL FOR 
        SELECT policy_id, synchronous, event_data FROM inserted
    
    OPEN affected_policies
    FETCH NEXT FROM affected_policies INTO @policy_id, @synchronous, @event_data
    DECLARE @err int
    SET @err = 0
    WHILE (@@FETCH_STATUS = 0 AND (@synchronous = 0 OR @err = 0)) 
    BEGIN
        DECLARE @pol_name sysname
        SELECT @pol_name = p.name
            FROM dbo.syspolicy_policies p 
            WHERE p.policy_id = @policy_id

        IF (@synchronous = 0)
        BEGIN
            -- trace what policy is processing this event
            DECLARE @msg nvarchar(1000)
            SET @msg = N'Policy ''' + @pol_name + ''' was activated by an event.'
            RAISERROR(@msg, 1, 1) with log
        END

        -- execute the policy
        EXEC @err = msdb.sys.sp_syspolicy_execute_policy @policy_name =@pol_name, @event_data = @event_data, @synchronous = @synchronous
    
        -- move to the next policy if we're checking the policy
        -- or if we are in enforce mode and we haven't failed
        IF( @synchronous = 0 OR @err = 0)
            FETCH NEXT FROM affected_policies INTO @policy_id, @synchronous, @event_data
    END

    CLOSE affected_policies
    DEALLOCATE affected_policies

END
GO
    

PRINT 'Creating procedure [dbo].[sp_syspolicy_dispatch_event]...'
GO
-- These settings are necessary to read XML.
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
SET ARITHABORT ON
SET CONCAT_NULL_YIELDS_NULL ON
SET NUMERIC_ROUNDABORT OFF
SET QUOTED_IDENTIFIER ON
GO

-- procedure that processes an event and decides 
-- what binding should handle it
CREATE PROCEDURE [dbo].[sp_syspolicy_dispatch_event]  @event_data xml, @synchronous bit
AS
BEGIN
	-- disable these as the caller may not have SHOWPLAN permission
	SET STATISTICS XML OFF
	SET STATISTICS PROFILE OFF

	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	IF ( @synchronous = 0)
		PRINT CONVERT(nvarchar(max), @event_data)
	DECLARE @event_type sysname
	DECLARE @object_type sysname
	DECLARE @database sysname
	DECLARE @mode int
	DECLARE @filter_expression nvarchar(4000)
	DECLARE @filter_expression_skeleton nvarchar(4000)

    SET @mode = (case @synchronous when 1 then 1 else 2 end)

    -- These settings are necessary to read XML.
    SET ANSI_NULLS ON
    SET ANSI_PADDING ON
    SET ANSI_WARNINGS ON
    SET ARITHABORT ON
    SET CONCAT_NULL_YIELDS_NULL ON
    SET NUMERIC_ROUNDABORT OFF
    SET QUOTED_IDENTIFIER ON

    SET NOCOUNT ON 

    SELECT 
        @event_type = T.c.value('(EventType/text())[1]', 'sysname')
        , @database = T.c.value('(DatabaseName/text())[1]', 'sysname')
        , @object_type = T.c.value('(ObjectType/text())[1]', 'sysname')
    FROM   @event_data.nodes('/EVENT_INSTANCE') T(c)
    
    -- we are going to ignore events that affect subobjects
    IF  (@event_type = N'ALTER_DATABASE' AND 
        1 = @event_data.exist('EVENT_INSTANCE/AlterDatabaseActionList')) OR
        (@event_type = N'ALTER_TABLE' AND 
        1 = @event_data.exist('EVENT_INSTANCE/AlterTableActionList'))
    BEGIN
        RETURN;
    END

    -- convert trace numerical objecttypes to string
    IF (ISNUMERIC(@object_type) = 1)
        select @object_type = name from master.dbo.spt_values where type = 'EOB' and number = @object_type

    -- these events do not have ObjectType and ObjectName
    IF ((@object_type IS NULL) AND @event_type IN ('CREATE_DATABASE', 'DROP_DATABASE', 'ALTER_DATABASE'))
    BEGIN
        SET @object_type = 'DATABASE'
    END

    INSERT msdb.dbo.syspolicy_execution_internal
        SELECT p.policy_id , @synchronous, @event_data
        FROM dbo.syspolicy_policies p  -- give me all the policies
        INNER JOIN dbo.syspolicy_conditions_internal c ON c.condition_id = p.condition_id  -- and their conditions
        INNER JOIN dbo.syspolicy_facet_events fe ON c.facet_id = fe.management_facet_id  -- and the facet events that are affected by the condition
        INNER JOIN dbo.syspolicy_target_sets ts ON ts.object_set_id = p.object_set_id AND ts.type = fe.target_type  -- and the target sets in the object set of the policy, with event types that are affected by the condition
        LEFT JOIN dbo.syspolicy_policy_category_subscriptions pgs ON pgs.policy_category_id = p.policy_category_id -- and the policy category subscriptions, if any
        LEFT JOIN dbo.syspolicy_target_set_levels tsl ON tsl.target_set_id = ts.target_set_id AND tsl.level_name = 'Database' -- and the database target set levels associated with any of the target sets, if any
        LEFT JOIN dbo.syspolicy_conditions_internal lc ON lc.condition_id = tsl.condition_id -- and any conditions on the target set level, if any
        LEFT JOIN dbo.syspolicy_policy_categories cat ON p.policy_category_id = cat.policy_category_id -- and the policy categories, if any
        WHERE fe.event_name=@event_type AND -- event type matches the fired event
            p.is_enabled = 1 AND -- policy is enabled
            fe.target_type_alias = @object_type AND -- target type matches the object in the event
            ts.enabled = 1 AND -- target set is enabled
            -- 1 means Enforce, 2 means CheckOnChange
            (p.execution_mode & @mode) = @mode AND -- policy mode matches the requested mode
            ((p.policy_category_id IS NULL) OR (cat.mandate_database_subscriptions = 1) OR ( ts.type_skeleton NOT LIKE 'Server/Database%') OR (@database IS NOT NULL AND pgs.target_object = @database)) AND
            ((@database IS NULL) OR 
             (@database IS NOT NULL AND 
              (tsl.condition_id IS NULL OR 
               (tsl.condition_id IS NOT NULL AND 
                ((lc.is_name_condition=1 AND @database = lc.obj_name) OR
                 (lc.is_name_condition=2 AND @database LIKE lc.obj_name) OR
                 (lc.is_name_condition=3 AND @database != lc.obj_name) OR
                 (lc.is_name_condition=4 AND @database NOT LIKE lc.obj_name))
               )
              )
             )
            ) 

    -- NOTE: if we haven't subscribed via an Endpoint facet on those events 
    -- we know for sure they will not be processed by the ServerAreaFacet policies 
    -- because syspolicy_facet_events expects @target_type to be SERVER
    -- so the filter will leave them out, and we are going to generate a fake 
    -- event to make those policies run
    IF( @synchronous = 0 AND 
        (@event_type IN ('ALTER_ENDPOINT', 'CREATE_ENDPOINT', 'DROP_ENDPOINT')))
    BEGIN
        DECLARE @fake_event_data xml
        SET @fake_event_data = CONVERT(xml, '<EVENT_INSTANCE><EventType>SAC_ENDPOINT_CHANGE</EventType><ObjectType>21075</ObjectType><ObjectName/><DatabaseName>master</DatabaseName></EVENT_INSTANCE>')
        
        EXEC [dbo].[sp_syspolicy_dispatch_event]  @event_data = @fake_event_data, @synchronous = 0
    END
            
END              
GO 

/* 
 * Asynchronous events collection via event notifications
 */

PRINT N'Dropping service [syspolicy_event_listener]...'
GO
IF EXISTS (SELECT * FROM sys.services WHERE name = N'syspolicy_event_listener')
    DROP SERVICE [syspolicy_event_listener]
GO
PRINT N'Dropping queue [syspolicy_event_queue]...'
GO
IF EXISTS (SELECT * FROM sys.service_queues WHERE name = N'syspolicy_event_queue')
    DROP QUEUE [syspolicy_event_queue]
GO
PRINT N'Creating queue [syspolicy_event_queue]...'
GO
CREATE QUEUE [syspolicy_event_queue]
GO

PRINT N'Creating service [syspolicy_event_listener]...'
GO
CREATE SERVICE [syspolicy_event_listener]
 ON QUEUE  [syspolicy_event_queue]
    ([http://schemas.microsoft.com/SQL/Notifications/PostEventNotification]);
GO

IF EXISTS (SELECT * FROM sys.procedures WHERE name = N'sp_syspolicy_events_reader')
    DROP PROCEDURE [sp_syspolicy_events_reader] 
GO
PRINT N'Creating procedure [sp_syspolicy_events_reader]...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_events_reader] 
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

	DECLARE @dh uniqueidentifier;
	DECLARE @mt sysname;
	DECLARE @body varbinary(max);
	DECLARE @msg nvarchar(max)

    BEGIN TRANSACTION;
    WAITFOR (RECEIVE TOP (1)
        @dh = conversation_handle,
        @mt = message_type_name,
        @body = message_body
        FROM [syspolicy_event_queue]), timeout 5000;
    WHILE (@dh is not null)
    BEGIN
        IF (@mt = N'http://schemas.microsoft.com/SQL/ServiceBroker/Error')
        BEGIN
            -- @body contains the error
            DECLARE @bodyStr nvarchar(max)
            SET @bodyStr = convert(nvarchar(max), @body)
            RAISERROR (34001, 1,1, @bodyStr) with log;
            END CONVERSATION @dh;
        END
        IF (@mt = N'http://schemas.microsoft.com/SQL/ServiceBroker/EndDialog')
        BEGIN
            RAISERROR (34002, 1,1) with log;
            END CONVERSATION @dh;
        END
        IF (@mt = N'http://schemas.microsoft.com/SQL/Notifications/EventNotification')
        BEGIN
            -- process the event
            BEGIN TRY
                EXEC [dbo].[sp_syspolicy_dispatch_event]  @event_data = @body, @synchronous = 0
            END TRY
            BEGIN CATCH
                -- report the error

                DECLARE @errorNumber int
                DECLARE @errorMessage nvarchar(max)
                SET @errorNumber = ERROR_NUMBER()
                SET @errorMessage = ERROR_MESSAGE()

                RAISERROR (34003, 1,1, @errorNumber, @errorMessage ) with log;
            END CATCH
        END
        -- every message is handled in its own transaction
        COMMIT TRANSACTION;
        SELECT @dh = null;
        BEGIN TRANSACTION;
        WAITFOR (RECEIVE TOP (1)
            @dh = conversation_handle,
            @mt = message_type_name,
            @body = message_body
            FROM [syspolicy_event_queue]), TIMEOUT 5000;
    END
    COMMIT;
END
GO

IF OBJECT_ID('[dbo].[syspolicy_configuration_internal]') IS NULL
BEGIN
    PRINT 'Creating table [dbo].[syspolicy_configuration_internal]...'
    CREATE TABLE [dbo].[syspolicy_configuration_internal] (
        name sysname PRIMARY KEY CLUSTERED NOT NULL,
        current_value sql_variant NOT NULL);
        
    INSERT INTO [dbo].[syspolicy_configuration_internal] (name, current_value)
    VALUES (N'Enabled', 1);
    
    INSERT INTO [dbo].[syspolicy_configuration_internal] (name, current_value)
    VALUES (N'HistoryRetentionInDays', 0);
    
    INSERT INTO [dbo].[syspolicy_configuration_internal] (name, current_value)
    VALUES (N'LogOnSuccess', 0);
END
GO


PRINT 'Creating view [dbo].[syspolicy_configuration] ...'
GO
CREATE VIEW [dbo].[syspolicy_configuration]
AS
    SELECT 
        name,
        CASE WHEN name = N'Enabled' and SERVERPROPERTY('EngineEdition') = 4 THEN 0 ELSE current_value END AS current_value
    FROM [dbo].[syspolicy_configuration_internal] 
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_set_config_history_retention] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_set_config_history_retention] 
	@value sql_variant
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    UPDATE [msdb].[dbo].[syspolicy_configuration_internal]
        SET current_value = @value
        WHERE name = N'HistoryRetentionInDays';
    
END
GO
PRINT 'Creating procedure [dbo].[sp_syspolicy_set_log_on_success] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_set_log_on_success] 
	@value sql_variant
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END

    UPDATE [msdb].[dbo].[syspolicy_configuration_internal]
        SET current_value = @value
        WHERE name = N'LogOnSuccess';
    
END
GO
PRINT 'Creating procedure [dbo].[sp_syspolicy_set_config_enabled] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_set_config_enabled] 
	@value sql_variant
AS
BEGIN
	DECLARE @retval_check int;
	
	SET NOCOUNT ON
	
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check
	END
    
    DECLARE @val bit;
    SET @val = CONVERT(bit, @value);
    IF (@val = 1)
    BEGIN
	    DECLARE @engine_edition INT
		SELECT @engine_edition = CAST(SERVERPROPERTY('EngineEdition') AS INT)
		IF @engine_edition = 4 -- All SQL Express types + embedded
		BEGIN
			RAISERROR (34054, 16, 1)			
            RETURN 34054
		END
		
	    UPDATE [msdb].[dbo].[syspolicy_configuration_internal]
	    SET current_value = @value
	    WHERE name = N'Enabled';
    
        -- enable policy automation
        ALTER QUEUE [syspolicy_event_queue] WITH ACTIVATION (STATUS = ON)

        EXEC sys.sp_syspolicy_update_ddl_trigger;
        EXEC sys.sp_syspolicy_update_event_notification;
    END
    ELSE
    BEGIN
	    UPDATE [msdb].[dbo].[syspolicy_configuration_internal]
	    SET current_value = @value
	    WHERE name = N'Enabled';

        -- disable policy automation
        ALTER QUEUE [syspolicy_event_queue] WITH ACTIVATION (STATUS = OFF)

        IF EXISTS (SELECT * FROM sys.server_event_notifications WHERE name = N'syspolicy_event_notification')
	        DROP EVENT NOTIFICATION [syspolicy_event_notification] ON SERVER 

        IF EXISTS (SELECT * FROM sys.server_triggers WHERE name = N'syspolicy_server_trigger')
            DISABLE TRIGGER [syspolicy_server_trigger] ON ALL SERVER 
    END

END
GO
PRINT 'Creating procedure [dbo].[sp_syspolicy_configure] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_configure]
    @name sysname,
    @value sql_variant
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole'
	IF (0 != @retval_check)
	BEGIN
		RETURN @retval_check
	END

    DECLARE @value_type sysname;
    
    IF (@name=N'Enabled')
    BEGIN
        SET @value_type = CONVERT(sysname, SQL_VARIANT_PROPERTY(@value, 'BaseType'));
        IF (@value_type != 'int')
        BEGIN
            RAISERROR (34021, -1, -1, @name, @value_type);
            RETURN 34021;
        END
       
        EXEC msdb.[dbo].[sp_syspolicy_set_config_enabled] @value;
    END
    ELSE 
    IF (@name = N'HistoryRetentionInDays')
    BEGIN
        SET @value_type = CONVERT(sysname, SQL_VARIANT_PROPERTY(@value, 'BaseType'));
        IF (@value_type != 'int')
        BEGIN
            RAISERROR (34021, -1, -1, @name, @value_type);
            RETURN 34021;
        END
        
        EXEC msdb.[dbo].[sp_syspolicy_set_config_history_retention] @value;
    END
    ELSE
    IF (@name=N'LogOnSuccess')
    BEGIN
        SET @value_type = CONVERT(sysname, SQL_VARIANT_PROPERTY(@value, 'BaseType'));
        IF (@value_type != 'int')
        BEGIN
            RAISERROR (34021, -1, -1, @name, @value_type);
            RETURN 34021;
        END
       
        EXEC msdb.[dbo].[sp_syspolicy_set_log_on_success] @value;
    END
    ELSE 
    BEGIN
        RAISERROR(34020, -1, -1, @name);
        RETURN 34020;
    END
    
    RETURN 0;
END
GO
PRINT 'Creating function [dbo].[fn_syspolicy_is_automation_enabled] ...'
GO
CREATE FUNCTION fn_syspolicy_is_automation_enabled()
RETURNS bit
AS
BEGIN
    DECLARE @ret bit;
    SELECT @ret = CONVERT(bit, current_value)
        FROM msdb.dbo.syspolicy_configuration 
        WHERE name = 'Enabled' 

    RETURN @ret;
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_repair_policy_automation] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_repair_policy_automation]
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole';
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check;
	END

    BEGIN TRANSACTION;

    BEGIN TRY
        SET NOCOUNT ON
        
        DECLARE @policies_copy TABLE (
                            policy_id int ,
                            name sysname NOT NULL,
                            condition_id int NOT NULL,
                            root_condition_id int NULL,
                            execution_mode int NOT NULL,
                            policy_category_id int NULL,
                            schedule_uid uniqueidentifier NULL,
                            description nvarchar(max) NOT NULL ,
                            help_text nvarchar(4000) NOT NULL ,
                            help_link nvarchar(2083) NOT NULL ,
                            object_set_id INT NULL,
                            is_enabled bit NOT NULL,
                           	is_system bit NOT NULL);

        INSERT INTO @policies_copy 
            SELECT          policy_id,
                            name, 
                            condition_id, 
                            root_condition_id, 
                            execution_mode, 
                            policy_category_id, 
                            schedule_uid, 
                            description,
                            help_text,
                            help_link,
                            object_set_id,
                            is_enabled,
                            is_system
            FROM msdb.dbo.syspolicy_policies_internal;
        
        DELETE FROM syspolicy_policies_internal;
        
        SET IDENTITY_INSERT msdb.dbo.syspolicy_policies_internal ON
        INSERT INTO msdb.dbo.syspolicy_policies_internal (
                            policy_id,
                            name, 
                            condition_id, 
                            root_condition_id, 
                            execution_mode, 
                            policy_category_id, 
                            schedule_uid, 
                            description,
                            help_text,
                            help_link,
                            object_set_id,
                            is_enabled,
                            is_system)
            SELECT 
                            policy_id,
                            name, 
                            condition_id, 
                            root_condition_id, 
                            execution_mode, 
                            policy_category_id, 
                            schedule_uid, 
                            description,
                            help_text,
                            help_link,
                            object_set_id,
                            is_enabled,
                            is_system
            FROM @policies_copy;
            
        SET IDENTITY_INSERT msdb.dbo.syspolicy_policies_internal OFF;
        
        SET NOCOUNT OFF;
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION;
        RAISERROR (14351, -1, -1);
        RETURN 1;
    END CATCH

    -- commit the transaction we started
    COMMIT TRANSACTION;
    
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_purge_history] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_purge_history] @include_system bit=0
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole';
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check;
	END

    DECLARE @retention_interval_in_days_variant sql_variant
    SET @retention_interval_in_days_variant = (SELECT current_value 
                                        FROM msdb.dbo.syspolicy_configuration
                                        WHERE name = N'HistoryRetentionInDays');
                                        
    DECLARE @retention_interval_in_days int;
    SET @retention_interval_in_days = CONVERT(int, @retention_interval_in_days_variant);
    
    IF( @retention_interval_in_days <= 0)
        RETURN 0;
	
	DECLARE @cutoff_date datetime;
	SET @cutoff_date = DATEADD(day, -@retention_interval_in_days, GETDATE());

    -- delete old policy history records
    BEGIN TRANSACTION
    
    DELETE d 
    FROM msdb.dbo.syspolicy_policy_execution_history_details_internal d
    INNER JOIN msdb.dbo.syspolicy_policy_execution_history_internal h ON d.history_id = h.history_id
    INNER JOIN msdb.dbo.syspolicy_policies p ON h.policy_id = p.policy_id
    WHERE h.end_date < @cutoff_date AND (p.is_system = 0 OR p.is_system = @include_system)
    
    DELETE h
    FROM msdb.dbo.syspolicy_policy_execution_history_internal h
    INNER JOIN msdb.dbo.syspolicy_policies p ON h.policy_id = p.policy_id
    WHERE h.end_date < @cutoff_date AND (p.is_system = 0 OR p.is_system = @include_system)
    
    COMMIT TRANSACTION
    
    -- delete policy subscriptions that refer to the nonexistent databases
    DELETE s
    FROM msdb.dbo.syspolicy_policy_category_subscriptions_internal s
    LEFT OUTER JOIN master.sys.databases d ON s.target_object = d.name
    WHERE s.target_type = 'DATABASE' AND d.database_id IS NULL
    
    RETURN 0;
END
GO
PRINT 'Creating procedure [dbo].[sp_syspolicy_create_purge_job] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_create_purge_job]
AS
BEGIN
DECLARE @retval_check int;
EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole';
IF ( 0!= @retval_check)
BEGIN
	RETURN @retval_check;
END

-- create a policy history retention maintenance job
-- first check if this job already exists 
IF EXISTS (SELECT * 
            FROM msdb.dbo.syspolicy_configuration c
            WHERE c.name = 'PurgeHistoryJobGuid')
BEGIN
    RETURN;
END

BEGIN TRANSACTION;
DECLARE @ReturnCode INT;
SELECT @ReturnCode = 0;
DECLARE @job_name sysname;
-- create unique job name
SET @job_name = N'syspolicy_purge_history';
WHILE (EXISTS (SELECT * FROM msdb..sysjobs WHERE name = @job_name))
BEGIN
	SET @job_name = N'syspolicy_purge_history_' + RIGHT(STR(FLOOR(RAND() * 100000000)),8);
END

DECLARE @sa_account_name sysname
SET @sa_account_name = SUSER_Name(0x1)

DECLARE @jobId BINARY(16);
EXEC @ReturnCode =  msdb.dbo.sp_add_job 
        @job_name=@job_name, 
		@enabled=1, 
		@notify_level_eventlog=0, 
		@notify_level_email=0, 
		@notify_level_netsend=0, 
		@notify_level_page=0, 
		@delete_level=0, 
		@owner_login_name=@sa_account_name, 
		@job_id = @jobId OUTPUT;
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;

EXEC @ReturnCode = msdb.dbo.sp_add_jobstep 
        @job_id=@jobId, 
        @step_name=N'Verify that automation is enabled.', 
		@step_id=1, 
		@cmdexec_success_code=0, 
		@on_success_action=3, 
		@on_success_step_id=0, 
		@on_fail_action=1, 
		@on_fail_step_id=0, 
		@retry_attempts=0, 
		@retry_interval=0, 
		@os_run_priority=0, 
		@subsystem=N'TSQL', 
		@command=N'IF (msdb.dbo.fn_syspolicy_is_automation_enabled() != 1)
        BEGIN
            RAISERROR(34022, 16, 1)
        END', 
		@database_name=N'master', 
		@flags=0;
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

EXEC @ReturnCode = msdb.dbo.sp_add_jobstep 
        @job_id=@jobId, 
        @step_name=N'Purge history.', 
		@step_id=2, 
		@cmdexec_success_code=0, 
		@on_success_action=3, 
		@on_success_step_id=0, 
		@on_fail_action=2, 
		@on_fail_step_id=0, 
		@retry_attempts=0, 
		@retry_interval=0, 
		@os_run_priority=0, 
		@subsystem=N'TSQL', 
		@command=N'EXEC msdb.dbo.sp_syspolicy_purge_history', 
		@database_name=N'master', 
		@flags=0;
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;

DECLARE @command nvarchar(1000);
SET @command = N'if (''$(ESCAPE_SQUOTE(INST))'' -eq ''MSSQLSERVER'') {$a = ''\DEFAULT''} ELSE {$a = ''''};
(Get-Item SQLSERVER:\SQLPolicy\$(ESCAPE_NONE(SRVR))$a).EraseSystemHealthPhantomRecords()'

EXEC @ReturnCode = msdb.dbo.sp_add_jobstep 
        @job_id=@jobId, 
        @step_name=N'Erase Phantom System Health Records.', 
		@step_id=3, 
		@cmdexec_success_code=0, 
		@on_success_action=1, 
		@on_success_step_id=0, 
		@on_fail_action=2, 
		@on_fail_step_id=0, 
		@retry_attempts=0, 
		@retry_interval=0, 
		@os_run_priority=0, 
		@subsystem=N'PowerShell', 
		@command=@command, 
		@database_name=N'master', 
		@flags=0;
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;

EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1;
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;

EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = @@SERVERNAME;
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;

-- run this job every day at 2AM
EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule 
        @job_id=@jobId, 
        @name=N'syspolicy_purge_history_schedule', 
		@enabled=1, 
		@freq_type=4, 
		@freq_interval=1, 
		@freq_subday_type=1, 
		@freq_subday_interval=0, 
		@freq_relative_interval=0, 
		@freq_recurrence_factor=0, 
		@active_start_date=20080101, 
		@active_end_date=99991231, 
		@active_start_time=20000, 
		@active_end_time=235959;
IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;

INSERT INTO [msdb].[dbo].[syspolicy_configuration_internal] (name, current_value)
VALUES (N'PurgeHistoryJobGuid', @jobId);

COMMIT TRANSACTION;
RETURN;

QuitWithRollback:
    IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION;
END
GO

PRINT 'Creating procedure [dbo].[sp_syspolicy_purge_health_state] ...'
GO
CREATE PROCEDURE [dbo].[sp_syspolicy_purge_health_state]
    @target_tree_root_with_id nvarchar(400) = NULL
AS
BEGIN
	DECLARE @retval_check int;
	EXECUTE @retval_check = [dbo].[sp_syspolicy_check_membership] 'PolicyAdministratorRole';
	IF ( 0!= @retval_check)
	BEGIN
		RETURN @retval_check;
	END
	
	IF (@target_tree_root_with_id IS NULL)
	BEGIN
	    DELETE FROM msdb.dbo.syspolicy_system_health_state_internal;
	END
	ELSE
	BEGIN
	    DECLARE @target_mask nvarchar(801);
	    SET @target_mask = @target_tree_root_with_id;
	    -- we need to escape all the characters that can be part of the 
	    -- LIKE pattern
	    SET @target_mask = REPLACE(@target_mask, '[', '\[');
	    SET @target_mask = REPLACE(@target_mask, ']', '\]');
	    SET @target_mask = REPLACE(@target_mask, '_', '\_');
	    SET @target_mask = REPLACE(@target_mask, '%', '\%');
	    SET @target_mask = @target_mask + '%';
	    DELETE FROM msdb.dbo.syspolicy_system_health_state_internal
	        WHERE target_query_expression_with_id LIKE @target_mask ESCAPE '\';
	END
	
	RETURN 0;
END
GO

-----------------------------------------------------------
-- Security for policy objects
-----------------------------------------------------------
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'PolicyAdministratorRole' AND type = 'R'))
BEGIN
    CREATE ROLE [PolicyAdministratorRole]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'PolicyAdministratorRole' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [PolicyAdministratorRole]
        CREATE ROLE [PolicyAdministratorRole]
    END
END
GO
-- Policy administrator is also an agent operator
-- because we need to create jobs automatically
EXECUTE sp_addrolemember @rolename = 'SQLAgentOperatorRole' , 
                   @membername = 'PolicyAdministratorRole' 
GO
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'ServerGroupAdministratorRole' AND type = 'R'))
BEGIN
    CREATE ROLE [ServerGroupAdministratorRole]
END

GO
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'ServerGroupReaderRole' AND type = 'R'))
BEGIN
    CREATE ROLE [ServerGroupReaderRole]
END
GO
EXECUTE sp_addrolemember @rolename = 'ServerGroupReaderRole' , 
                   @membername = 'ServerGroupAdministratorRole' 

GO
-- In case a failure occurs, procedure may have not been dropped, and the create will fail.
-- Execute CREATE OR ALTER instead, to make sure it succeeds.
CREATE OR ALTER PROCEDURE #provision_table @short_name sysname, @role_name sysname, @grant_public_select bit
AS
BEGIN
    DECLARE @stmt nvarchar(max)
    -- revoke table permissions
    SELECT @stmt = N'REVOKE DELETE, INSERT, REFERENCES, SELECT, UPDATE, ALTER, CONTROL, TAKE OWNERSHIP 
        ON [dbo].' + QUOTENAME(@short_name + N'_internal') + ' FROM [public] CASCADE'
    EXEC sp_executesql @stmt

    -- revoke view permissions
    SELECT @stmt = N'REVOKE ALTER, CONTROL, DELETE, INSERT, REFERENCES, SELECT, TAKE OWNERSHIP, UPDATE
        ON [dbo].' + QUOTENAME(@short_name ) + ' FROM [public] CASCADE'
    EXEC sp_executesql @stmt

    -- grant select on view 
    SELECT @stmt = N'GRANT SELECT ON [dbo].' + QUOTENAME(@short_name)+ ' TO ' + QUOTENAME(@role_name)
    EXEC sp_executesql @stmt

    if (@grant_public_select != 0)
    BEGIN
        SELECT @stmt = N'GRANT SELECT ON [dbo].' + QUOTENAME(@short_name)+ ' TO [public]'
        EXEC sp_executesql @stmt
    END
    
END
GO

-- public role can view all policy metadata
EXEC #provision_table N'syspolicy_conditions', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_policies', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_policy_categories', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_object_sets', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_target_sets', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_target_set_levels', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_policy_category_subscriptions', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_system_health_state', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_policy_execution_history', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_policy_execution_history_details', N'PolicyAdministratorRole', 1
EXEC #provision_table N'syspolicy_configuration', N'PolicyAdministratorRole', 1

-- Registered Server information is limited to the ServerGroupReaderRole, with no public access
EXEC #provision_table N'sysmanagement_shared_registered_servers', N'ServerGroupReaderRole', 0
EXEC #provision_table N'sysmanagement_shared_server_groups', N'ServerGroupReaderRole', 0
GO
DROP PROCEDURE #provision_table

GO

-- In case a failure occurs, procedure may have not been dropped, and the create will fail.
-- Execute CREATE OR ALTER instead, to make sure it succeeds.
CREATE OR ALTER PROCEDURE #provision_sp @name sysname, @role_name sysname
AS
BEGIN
    DECLARE @stmt nvarchar(max)
    SELECT @stmt = N'REVOKE ALTER, CONTROL, EXECUTE, TAKE OWNERSHIP, VIEW DEFINITION
    ON [dbo].' + QUOTENAME(@name) + ' FROM [public] CASCADE'
    EXEC sp_executesql @stmt

    SELECT @stmt = N'GRANT EXECUTE ON [dbo].' + QUOTENAME(@name)+ ' TO ' + QUOTENAME(@role_name)
    EXEC sp_executesql @stmt
    
END
GO
EXEC #provision_sp N'sp_syspolicy_add_condition', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_update_condition', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_rename_condition', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_delete_condition', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_add_policy', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_update_policy', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_rename_policy', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_delete_policy', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_add_target_set', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_update_target_set', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_add_target_set_level', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_update_target_set_level', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_add_policy_category_subscription', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_update_policy_category_subscription', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_delete_policy_category_subscription', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_log_policy_execution_start', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_log_policy_execution_end', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_log_policy_execution_detail', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_add_policy_category', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_rename_policy_category', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_update_policy_category', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_delete_policy_category', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_add_object_set', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_delete_object_set', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_verify_object_set_identifiers', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_dispatch_event', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_syspolicy_configure', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_purge_history', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_repair_policy_automation', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_purge_health_state', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_create_purge_job', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_set_log_on_success', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_set_config_enabled', N'PolicyAdministratorRole'
EXEC #provision_sp N'sp_syspolicy_set_config_history_retention', N'PolicyAdministratorRole'

EXEC #provision_sp N'sp_sysmanagement_update_shared_registered_server', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_rename_shared_registered_server', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_update_shared_server_group', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_rename_shared_server_group', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_move_shared_registered_server', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_delete_shared_registered_server', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_move_shared_server_group', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_delete_shared_server_group', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_add_shared_registered_server', N'ServerGroupAdministratorRole'
EXEC #provision_sp N'sp_sysmanagement_add_shared_server_group', N'ServerGroupAdministratorRole'

GO
DROP PROCEDURE #provision_sp
GO
GRANT EXECUTE ON [dbo].[fn_syspolicy_is_automation_enabled] TO PUBLIC
GO 

IF EXISTS (SELECT * from sys.database_principals where name = N'##MS_PolicyEventProcessingLogin##')
    DROP USER [##MS_PolicyEventProcessingLogin##]
GO
use master
GO

-- Do not recreate the login for Managed instance (EngineEdition 8). Recreation of the existing login will be done on physical master
-- (forced during script execution), but replicated master will have the old login, and later access to it will fail, because of SID mismatch.
-- In case the login does not exist, proceed either way (done during mkmastr in case it was dropped).
IF EXISTS (SELECT * from sys.server_principals WHERE name = '##MS_PolicyEventProcessingLogin##') AND SERVERPROPERTY('EngineEdition') <> 8
BEGIN
    IF EXISTS (SELECT * from sys.server_triggers WHERE name = N'syspolicy_server_trigger')
        DROP TRIGGER [syspolicy_server_trigger] ON ALL SERVER
    DROP LOGIN [##MS_PolicyEventProcessingLogin##]
END
go
-- Create the login if it does not exist already.
IF NOT EXISTS (SELECT * from sys.server_principals WHERE name = '##MS_PolicyEventProcessingLogin##')
BEGIN
    -- create event processing login with random password
    DECLARE @newid uniqueidentifier
    SET @newid = NEWID()
    DECLARE @password varchar(255)
    SELECT @password = QUOTENAME(CONVERT(varchar(255), @newid), '''')
    DBCC TRACEON(4606,-1)
    EXECUTE( N'CREATE LOGIN [##MS_PolicyEventProcessingLogin##] WITH PASSWORD=N' + @password + '')
    DBCC TRACEOFF(4606,-1)
END
GO

-- create t-sql execution login with random password
    DECLARE @newid uniqueidentifier
    SET @newid = NEWID()
    DECLARE @password varchar(255)
    SELECT @password = QUOTENAME(CONVERT(varchar(255), @newid), '''')
IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = '##MS_PolicyTsqlExecutionLogin##')
BEGIN
    DBCC TRACEON(4606,-1)
    EXECUTE( N'CREATE LOGIN [##MS_PolicyTsqlExecutionLogin##] WITH PASSWORD=N' + @password + '')
    DBCC TRACEOFF(4606,-1)

    -- this login is used just for impersonation, no one should be able to connect
    ALTER LOGIN [##MS_PolicyTsqlExecutionLogin##] DISABLE

    GRANT VIEW ANY DEFINITION TO [##MS_PolicyTsqlExecutionLogin##]
    GRANT VIEW SERVER STATE TO [##MS_PolicyTsqlExecutionLogin##]
END
ELSE
BEGIN
    ALTER LOGIN [##MS_PolicyTsqlExecutionLogin##] WITH CHECK_POLICY = OFF;
    EXECUTE( N'ALTER LOGIN [##MS_PolicyTsqlExecutionLogin##] WITH PASSWORD=N' + @password + '')
    ALTER LOGIN [##MS_PolicyTsqlExecutionLogin##] WITH CHECK_POLICY = ON;
END
go

-- this login is used just for impersonation, no one should be able to connect
ALTER LOGIN [##MS_PolicyEventProcessingLogin##] DISABLE

go
USE [msdb]
go
CREATE USER [##MS_PolicyEventProcessingLogin##] FROM LOGIN [##MS_PolicyEventProcessingLogin##]
go
EXEC msdb.sys.sp_addrolemember @rolename = 'PolicyAdministratorRole', @membername = '##MS_PolicyEventProcessingLogin##'
go
GRANT EXECUTE ON [sp_syspolicy_events_reader] TO [##MS_PolicyEventProcessingLogin##]
go

IF NOT EXISTS ( SELECT * FROM sys.database_principals WHERE name = '##MS_PolicyTsqlExecutionLogin##')
BEGIN
    CREATE USER [##MS_PolicyTsqlExecutionLogin##] FROM LOGIN [##MS_PolicyTsqlExecutionLogin##]
    EXEC msdb.sys.sp_addrolemember @rolename = 'PolicyAdministratorRole', @membername = '##MS_PolicyTsqlExecutionLogin##'
END
GO

USE [master]
go
IF EXISTS (SELECT * from sys.database_principals where name = N'##MS_PolicyEventProcessingLogin##')
    DROP USER [##MS_PolicyEventProcessingLogin##]
go
CREATE USER [##MS_PolicyEventProcessingLogin##]
go
GRANT EXECUTE ON [sp_syspolicy_execute_policy] TO [##MS_PolicyEventProcessingLogin##]
go

USE [msdb]
go

PRINT N'Hook up the activation procedure to the queue...'
ALTER QUEUE [syspolicy_event_queue]
    WITH ACTIVATION (
        STATUS = ON,
        MAX_QUEUE_READERS = 1,
        PROCEDURE_NAME = [sp_syspolicy_events_reader],
        EXECUTE AS N'##MS_PolicyEventProcessingLogin##');
GO

-- if this script runs on an existing installation (e.g in an upgrade) we need to recreate 
-- the event notifications and the ddl triggers used for policy automation 
exec sys.sp_syspolicy_update_event_notification
exec sys.sp_syspolicy_update_ddl_trigger
GO

USE [msdb]
GO

IF OBJECT_ID('[dbo].[sp_read_settings]', 'P') IS NOT NULL
  DROP PROC [dbo].[sp_read_settings]
GO

CREATE PROCEDURE [dbo].[sp_read_settings]
@name sysname = NULL OUTPUT, 
@setting_id int = NULL OUTPUT
AS
BEGIN
  IF ((@name IS NULL)     AND (@setting_id IS NULL)) OR
     ((@name IS NOT NULL) AND (@setting_id IS NOT NULL))
  BEGIN
    RAISERROR(14524, -1, -1, '@name', '@setting_id')
    RETURN(1) -- Failure
  END

  IF (@setting_id IS NOT NULL)
  BEGIN
    SELECT @name = CASE @setting_id
      WHEN 1 THEN 'ExtendedProtection'
      WHEN 2 THEN 'ForceEncryption'
      WHEN 3 THEN 'AcceptedSPNs'
      ELSE NULL
      END
      
      IF (@name IS NULL) RETURN (2) -- Unknown key
  END
  ELSE
  BEGIN
    IF (@name collate SQL_Latin1_General_CP1_CI_AS) != 'ExtendedProtection'
      AND (@name collate SQL_Latin1_General_CP1_CI_AS) != 'ForceEncryption'
      AND (@name collate SQL_Latin1_General_CP1_CI_AS) != 'AcceptedSPNs'
      RETURN (2) -- Unknown key
  END
  
  DECLARE @hive nvarchar(32), @key nvarchar(256)
  SET @hive=N'HKEY_LOCAL_MACHINE' 
  SET @key=N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\SuperSocketNetLib'
  
  Execute master.sys.xp_instance_regread @hive, @key, @name
  
  RETURN (0)
END
GO

EXEC sp_MS_marksystemobject 'dbo.sp_read_settings'
GO

-------------------------------------------------------------------------------
-- End policy objects
-------------------------------------------------------------------------------

/**************************************************************/
/*                     BEGIN DAC SCRIPTS                     */
/**************************************************************/
-- Note: These should be located before the sysutility objects in instmsdb.sql, 
-- because some of the Utility objects depend on the DAC objects. 

/**********************************************************************/
/* DAC Functions                                                      */
/**********************************************************************/

IF (NOT OBJECT_ID ('[dbo].[fn_sysdac_is_dac_creator]', 'FN') IS NULL) 
BEGIN
    RAISERROR('Dropping Function [dbo].[fn_sysdac_is_dac_creator]', 0, 1) WITH NOWAIT;
    DROP FUNCTION [dbo].[fn_sysdac_is_dac_creator];
END;
GO

IF (NOT OBJECT_ID ('[dbo].[fn_sysdac_is_login_creator]', 'FN') IS NULL) 
BEGIN
    RAISERROR('Dropping Function [dbo].[fn_sysdac_is_login_creator]', 0, 1) WITH NOWAIT;
    DROP FUNCTION [dbo].[fn_sysdac_is_login_creator];
END;
GO

IF (NOT OBJECT_ID ('[dbo].[fn_sysdac_is_currentuser_sa]', 'FN') IS NULL) 
BEGIN
    RAISERROR('Dropping Function [dbo].[fn_sysdac_is_currentuser_sa]', 0, 1) WITH NOWAIT;
    DROP FUNCTION [dbo].[fn_sysdac_is_currentuser_sa];
END;
GO

IF (NOT OBJECT_ID ('[dbo].[fn_sysdac_get_currentusername]', 'FN') IS NULL) 
BEGIN
    RAISERROR('Dropping Function [dbo].[fn_sysdac_get_currentusername]', 0, 1) WITH NOWAIT;
    
    --
    -- Drop the constraints that reference this function
    --
    DECLARE @dac_constraint_name SYSNAME
    DECLARE @sql nvarchar(1000)

    -- Find the Default constraint system name on the DAC tables for created_by column. 
    -- If it's fn_sysdac_get_currentusername, drop this constraint since we are about to drop the function

    -- sysdac_instances_internal
    SELECT @dac_constraint_name=dc.name
    FROM sys.default_constraints dc
    JOIN sys.columns c 
	    ON dc.parent_object_id = c.object_id 
	    AND dc.parent_column_id = c.column_id
    WHERE dc.parent_object_id = object_id('[dbo].[sysdac_instances_internal]', 'U') 
      AND dc.definition ='([dbo].[fn_sysdac_get_currentusername]())' 
      AND c.name='created_by'

    IF @dac_constraint_name IS NOT NULL
    BEGIN
	    SELECT @sql = 'ALTER TABLE [dbo].[sysdac_instances_internal] DROP CONSTRAINT ' + QUOTENAME(@dac_constraint_name)
	    EXEC (@sql)
    END

    -- sysdac_history_internal    
    SELECT @dac_constraint_name=dc.name
    FROM sys.default_constraints dc
    JOIN sys.columns c 
	    ON dc.parent_object_id = c.object_id 
	    AND dc.parent_column_id = c.column_id
    WHERE dc.parent_object_id = object_id('[dbo].[sysdac_history_internal]', 'U') 
      AND dc.definition ='([dbo].[fn_sysdac_get_currentusername]())' 
      AND c.name='created_by'


    IF @dac_constraint_name IS NOT NULL
    BEGIN
	    SELECT @sql = 'ALTER TABLE [dbo].[sysdac_history_internal] DROP CONSTRAINT ' + QUOTENAME(@dac_constraint_name)
	    EXEC (@sql)
    END
	
    --
    -- Drop the function
    --
    DROP FUNCTION [dbo].[fn_sysdac_get_currentusername];

END;
GO

IF (NOT OBJECT_ID ('[dbo].[fn_sysdac_get_username]', 'FN') IS NULL) 
BEGIN
    RAISERROR('Dropping Function [dbo].[fn_sysdac_get_username]', 0, 1) WITH NOWAIT;
    DROP FUNCTION [dbo].[fn_sysdac_get_username];
END;
GO

/*
Scalar Function:  dbo.fn_sysdac_is_dac_creator
Returns a one (1) if the current user is allowed to create DACs

This is generalized in a function for reuse and maintenance
*/
RAISERROR('Creating Function [dbo].[fn_sysdac_is_dac_creator]...', 0, 1) WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysdac_is_dac_creator]()
RETURNS int
BEGIN
    DECLARE @engineEdition int = CAST(SERVERPROPERTY('EngineEdition') AS int);
    DECLARE @isdaccreator int;

    -- Check the engine edition
    IF (@engineEdition = 5)
    BEGIN
        -- Windows Azure SQL Database:
        --   is member of dbmanager or is superuser.

        SET @isdaccreator = COALESCE(IS_MEMBER('dbmanager'), 0) | 
            dbo.fn_sysdac_is_currentuser_sa()

    END ELSE
    BEGIN
        -- Standalone, default:
        --  is member of dbcreator

        /*
        We should only require CREATE ANY DATABASE but the database rename 
        step of creating a DAC requires that we have dbcreator.
    
        If that changes use the code below
    
        -- CREATE ANY DATABASE is what makes somebody a creator
        Set @isdaccreator = HAS_PERMS_BY_NAME(null, null, 'CREATE ANY DATABASE')
        */

        SET @isdaccreator = COALESCE(is_srvrolemember('dbcreator'), 0)
        
    END

    RETURN @isdaccreator;
END
GO

/*
Scalar Function:  dbo.fn_sysdac_is_login_creator
Returns a one (1) if the current user is allowed to create Logins

This is generalized in a function for reuse and maintenance
*/
RAISERROR('Creating Function [dbo].[fn_sysdac_is_login_creator]...', 0, 1) WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysdac_is_login_creator]()
RETURNS int
BEGIN
    DECLARE @engineEdition int = CAST(SERVERPROPERTY('EngineEdition') AS int);
    DECLARE @islogincreator int;

    -- Check the engine edition
    IF (@engineEdition = 5)
    BEGIN
        -- Windows Azure SQL Database:
        --   is member of loginmanager or is superuser.

        SET @islogincreator = COALESCE(IS_MEMBER('loginmanager'), 0) | 
            dbo.fn_sysdac_is_currentuser_sa()

    END ELSE
    BEGIN
        -- Standalone, default:
        --   has ALTER ANY LOGIN permision

        SET @islogincreator = HAS_PERMS_BY_NAME(null, null, 'ALTER ANY LOGIN')
        
    END

    RETURN @islogincreator;
END
GO

/*
Scalar Function:  dbo.fn_sysdac_is_currentuser_sa
Returns a one (1) if the current user is super user

This is generalized in a function for reuse and maintenance
*/
RAISERROR('Creating Function [dbo].[fn_sysdac_is_currentuser_sa]...', 0, 1) WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysdac_is_currentuser_sa]()
RETURNS int
BEGIN
    DECLARE @engineEdition int = CAST(SERVERPROPERTY('EngineEdition') AS int);
    DECLARE @is_sa int;
    
    -- Check the engine edition
    IF (@engineEdition = 5)
    BEGIN
        -- Windows Azure SQL Database:
    --   SID matches with the reserved offset.
        -- NOTE: We should get an inbuilt user function from Azure team instead of us querying based on SID.    
        SET @is_sa = 0
    
        IF((CONVERT(varchar(85), suser_sid(), 2) LIKE '0106000000000164%'))
            SET @is_sa = 1
            
    END ELSE
    BEGIN
        -- Standalone, default:
        --   is member of the serverrole 'sysadmin'
        SET @is_sa = COALESCE(is_srvrolemember('sysadmin'), 0)
    
    END
   
    RETURN @is_sa;
END
GO

/*
Scalar Function:  dbo.fn_sysdac_get_username
Returns the login name of the user with given sid

EXECUTE AS OWNER : This function needs access to sys.sql_logins

This is generalized in a function for reuse and maintenance
*/
RAISERROR('Creating Function [dbo].[fn_sysdac_get_username]...', 0, 1) WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysdac_get_username](@user_sid varbinary(85))
RETURNS sysname
WITH EXECUTE AS OWNER
BEGIN	
    DECLARE @engineEdition int = CAST(SERVERPROPERTY('EngineEdition') AS int);
    DECLARE @current_user_name sysname;

    IF (@engineEdition = 5)
    BEGIN 
        --Windows Azure SQL Database does not have syslogins. All the logins reside in sql_logins
        SELECT @current_user_name = name FROM sys.sql_logins where sid = @user_sid 
    END ELSE
    BEGIN
        --OnPremise engine has both sql and windows logins in syslogins
        SELECT @current_user_name = name FROM sys.syslogins where sid = @user_sid 
    END

    RETURN @current_user_name;
END
GO

/*
Scalar Function:  dbo.fn_sysdac_get_currentusername
Returns the login name of the current user

This is generalized in a function for reuse and maintenance
*/
RAISERROR('Creating Function [dbo].[fn_sysdac_get_currentusername]...', 0, 1) WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysdac_get_currentusername]()
RETURNS sysname
BEGIN
    DECLARE @engineEdition int;
    DECLARE @current_user_name sysname;

    SET @engineEdition = CAST(SERVERPROPERTY('EngineEdition') AS int);

    IF (@engineEdition = 5)
    BEGIN 
        --Windows Azure SQL Database does not have SUSER_SNAME. We need to look in sql_logins to get the user name.
        SELECT @current_user_name = dbo.fn_sysdac_get_username(SUSER_SID()) 
    END ELSE
    BEGIN
        --OnPremise engine has both sql and windows logins - We rely on SUSER_SNAME to find the current user name.
        SELECT @current_user_name = SUSER_SNAME()
    END

    RETURN @current_user_name;
END
GO

/**********************************************************************/
/* DAC TABLES                                                         */
/**********************************************************************/


/*
Table dbo.sysdac_instances_internal
Creates one row per one installed DacInstance
*/
IF (OBJECT_ID(N'dbo.sysdac_instances_internal', 'U') IS NULL)
BEGIN
RAISERROR('Creating table [dbo].[sysdac_instances_internal]...', 0, 1) WITH NOWAIT;
CREATE TABLE [dbo].[sysdac_instances_internal] 
(
    instance_id UniqueIdentifier NOT NULL, --unique DAC instance ID of the DacInstance
    instance_name sysname NOT NULL, --DacInstance name
    type_name sysname NOT NULL,  --DacType name
    type_version nvarchar(64) NOT NULL,--DacType version 
    description nvarchar(4000) NULL default (''),
    type_stream varbinary(max) NOT NULL, --Package Blob
    date_created datetime NOT NULL default GETDATE(),
    created_by sysname NOT NULL default dbo.fn_sysdac_get_currentusername(),
    
    CONSTRAINT [PK_sysdac_instances_internal]
        PRIMARY KEY CLUSTERED (instance_id), 
    
    --instance name is unique
    CONSTRAINT [UQ_sysdac_instances_internal]
        UNIQUE (instance_name),
);
END
GO

/*
Table dbo.sysdac_history_internal
Holds the information of deployment/deletion steps of DAC instances
*/
IF (OBJECT_ID(N'dbo.sysdac_history_internal', 'U') IS NULL)
BEGIN
RAISERROR('Creating table [dbo].[sysdac_history_internal]...', 0, 1) WITH NOWAIT;
CREATE TABLE [dbo].[sysdac_history_internal] 
(
    action_id int NOT NULL, --action identifier for each attempt of deploy/delete/detach/upgrade
    sequence_id int NOT NULL, --step # of deployment/deletion process
    instance_id UniqueIdentifier NOT NULL, --unique transaction ID = DAC package id
    action_type tinyint NOT NULL, --type of the action being performed 
    action_type_name AS CASE action_type
        WHEN 0 THEN 'deploy'
        WHEN 1 THEN 'create'
        WHEN 2 THEN 'rename'
        WHEN 3 THEN 'register'
        WHEN 4 THEN 'create objects'
        WHEN 5 THEN 'detach'
        WHEN 6 THEN 'delete'
        WHEN 7 THEN 'data transfer'
        WHEN 8 THEN 'disable constraints'
        WHEN 9 THEN 'move data'
        WHEN 10 THEN 'enable constraints'
        WHEN 11 THEN 'copy permissions'
        WHEN 12 THEN 'set readonly'
        WHEN 13 THEN 'upgrade'
        WHEN 14 THEN 'unregister'
        WHEN 15 THEN 'update registration'
        WHEN 16 THEN 'set readwrite'
        WHEN 17 THEN 'disconnect users'
        END,        
    dac_object_type tinyint NOT NULL, --type of the object affected 0-dacpac,1-login,2-database
    dac_object_type_name AS CASE dac_object_type
        WHEN 0 THEN 'dacpac'
        WHEN 1 THEN 'login'
        WHEN 2 THEN 'database'
        END,
    action_status tinyint NOT NULL, --pending/success/fail
        action_status_name AS CASE action_status
        WHEN 0 THEN 'not started'
        WHEN 1 THEN 'pending'
        WHEN 2 THEN 'success'
        WHEN 3 THEN 'fail'
        WHEN 4 THEN 'rollback'
        ELSE NULL
        END,
    required bit NULL, --indicates if the current step is needed to be committed for success of the action
    dac_object_name_pretran sysname, --name of the  object before the current step completes
    dac_object_name_posttran sysname, --name of the  object after the current step completes
    sqlscript nvarchar(max) NULL,  --sql script for performing the associated object
    payload varbinary(max) NULL, --payload data associated with an object. Mostly used for Dacpac
    comments varchar(max) NOT NULL, --comments associated with the operation
    error_string nvarchar(max) NULL,  --error while running associated sql
    created_by sysname NOT NULL default dbo.fn_sysdac_get_currentusername(),  --login of the user creating the step
    date_created datetime NOT NULL default GETDATE(), --datetime at which the step is created
    date_modified datetime NOT NULL default GETDATE() --datetime at while the step was last modified
    
    CONSTRAINT [PK_sysdac_history_internal]
    PRIMARY KEY CLUSTERED (action_id, sequence_id),
    
    CONSTRAINT [UQ_sysdac_history_internal]
        UNIQUE (action_id, dac_object_type, action_type, 
                dac_object_name_pretran, dac_object_name_posttran),    
);

CREATE NONCLUSTERED INDEX IX_sysdac_history_internal 
ON sysdac_history_internal(sequence_id, action_status) 
END
GO

--
-- Recreate the constraints that reference fn_sysdac_get_currentusername
--
-- sysdac_instances_internal
DECLARE @dac_constraint_name SYSNAME
DECLARE @sql nvarchar(1000)
DECLARE @tablename varchar(50)
SET @tablename = '[dbo].[sysdac_instances_internal]'
SELECT @dac_constraint_name=dc.name
FROM sys.default_constraints dc
JOIN sys.columns c 
	ON dc.parent_object_id = c.object_id 
	AND dc.parent_column_id = c.column_id
WHERE dc.parent_object_id = object_id(@tablename, 'U') 
  AND c.name='created_by'

IF @dac_constraint_name IS NULL
BEGIN
	SELECT @sql = 'ALTER TABLE ' + @tablename + ' ADD DEFAULT dbo.fn_sysdac_get_currentusername() FOR created_by';
	EXEC (@sql)
END
GO

-- sysdac_history_internal
DECLARE @dac_constraint_name SYSNAME
DECLARE @sql nvarchar(1000)
DECLARE @tablename varchar(50)
SET @tablename = '[dbo].[sysdac_history_internal]'
SELECT @dac_constraint_name=dc.name
FROM sys.default_constraints dc
JOIN sys.columns c 
	ON dc.parent_object_id = c.object_id 
	AND dc.parent_column_id = c.column_id
WHERE dc.parent_object_id = object_id(@tablename, 'U') 
  AND c.name='created_by'
  
IF @dac_constraint_name IS NULL
BEGIN
	SELECT @sql = 'ALTER TABLE ' + @tablename + ' ADD DEFAULT dbo.fn_sysdac_get_currentusername() FOR created_by';
	EXEC (@sql)
END
GO

/**********************************************************************/
/* DAC VIEWS                                                          */
/**********************************************************************/

/* Clean up any existing views */
IF(NOT OBJECT_ID(N'[dbo].[sysdac_instances]', 'V') IS NULL)
BEGIN
    RAISERROR ('Dropping view dbo.sysdac_instances', 0, 1) WITH NOWAIT;
    DROP VIEW dbo.sysdac_instances;
END;
GO

/*
View :  dbo.sysdac_instances
Returns one row for each package installed in the system. The list is filtered
based on the callers permisions.
sysadmin: sees all information for all instances
dbo's: sees all information for dacs in databases they own, limited information for all other instances
public: sees limited information for all packges
*/
RAISERROR('Creating View [dbo].[sysdac_instances]...', 0, 1) WITH NOWAIT;
GO
CREATE VIEW [dbo].[sysdac_instances]
AS
    SELECT
        -- this must be locked down because we use instance_id visability as a security gate
        case 
            when (dbo.fn_sysdac_is_currentuser_sa() = 1) then dac_instances.instance_id
            when sd.owner_sid = SUSER_SID() then dac_instances.instance_id
            else NULL
        end as instance_id,
        dac_instances.instance_name,
        dac_instances.type_name,
        dac_instances.type_version,
        dac_instances.description,
        case 
            when (dbo.fn_sysdac_is_currentuser_sa() = 1) then dac_instances.type_stream
            when sd.owner_sid = SUSER_SID() then dac_instances.type_stream
            else NULL
        end as type_stream,
        dac_instances.date_created,
        dac_instances.created_by,
        dac_instances.instance_name as database_name
    FROM sysdac_instances_internal dac_instances
    LEFT JOIN sys.databases sd
		ON dac_instances.instance_name = sd.name
GO

/**********************************************************************/
/* DAC Stored Procedures                                              */
/**********************************************************************/

/*Drop existing Stored Procedures*/
IF (NOT OBJECT_ID ('dbo.sp_sysdac_add_history_entry', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_add_history_entry', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_add_history_entry; 
END;
GO

IF (NOT OBJECT_ID ('dbo.sp_sysdac_add_instance', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_add_instance', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_add_instance; 
END;
GO

IF (NOT OBJECT_ID ('dbo.sp_sysdac_update_instance', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_update_instance', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_update_instance; 
END;
GO

IF (NOT OBJECT_ID ('dbo.sp_sysdac_upgrade_instance', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_upgrade_instance', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_upgrade_instance; 
END;
GO

IF (NOT OBJECT_ID ('dbo.sp_sysdac_update_history_entry', 'P') IS NULL) 
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_update_history_entry', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_update_history_entry; 
END;
GO

IF (NOT OBJECT_ID ('dbo.sp_sysdac_delete_instance', 'P') IS NULL) 
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_delete_instance', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_delete_instance; 
END;
GO

IF (NOT OBJECT_ID ('dbo.sp_sysdac_delete_history', 'P') IS NULL) 
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_delete_history', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_delete_history; 
END;
GO

IF (NOT OBJECT_ID ('dbo.sp_sysdac_rename_database', 'P') IS NULL) 
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_rename_database', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_rename_database; 
END;
GO

IF (NOT OBJECT_ID ('dbo.sp_sysdac_drop_database', 'P') IS NULL) 
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_drop_database', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_drop_database; 
END;
GO

IF (NOT OBJECT_ID ('dbo.sp_sysdac_drop_login', 'P') IS NULL) 
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_drop_login', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_drop_login; 
END;
GO

IF (NOT OBJECT_ID ('dbo.sp_sysdac_ensure_dac_creator', 'P') IS NULL) 
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_ensure_dac_creator', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_ensure_dac_creator; 
END;
GO

IF (NOT OBJECT_ID ('dbo.sp_sysdac_rollback_all_pending_objects', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_rollback_all_pending_objects', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_rollback_all_pending_objects; 
END;
GO

IF (NOT OBJECT_ID ('dbo.sp_sysdac_rollback_pending_object', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_rollback_pending_object', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_rollback_pending_object; 
END;
GO

IF (NOT OBJECT_ID ('dbo.sp_sysdac_setreadonly_database', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_setreadonly_database', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_setreadonly_database; 
END;
GO

IF (NOT OBJECT_ID ('dbo.sp_sysdac_resolve_pending_entry', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_resolve_pending_entry', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_resolve_pending_entry; 
END;
GO

IF (NOT OBJECT_ID ('dbo.sp_sysdac_rollback_committed_step', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping Stored Procedure sp_sysdac_rollback_committed_step', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysdac_rollback_committed_step; 
END;
GO

/*
Procedure [sp_sysdac_ensure_dac_creator]
This sproc checks the caller is a dac creator and raise's an error if not
*/
RAISERROR('Creating procedure [dbo].[sp_sysdac_ensure_dac_creator]...', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE dbo.sp_sysdac_ensure_dac_creator
as
BEGIN
    -- only users that can create a dac can add parts
    IF (dbo.fn_sysdac_is_dac_creator() != 1)
    BEGIN
        RAISERROR(36010, -1, -1);
        RETURN(1); -- failure
    END
END
go

/*
Procedure [sp_sysdac_add_instance]
This proc creates a new DacInstance in dbo.sysdac_instances_internal table
 
Parameters: 
    @type_name - Name of the Dac type
    @type_version - Version of the Dac type
    @instance_name - Name of the Dac Instance
    @instance_id - Guid identification of a Dac instance
    @description - Dac type description
    @type_stream - package content of the dac type

*/
RAISERROR('Creating procedure [dbo].[sp_sysdac_add_instance]...', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysdac_add_instance]  
    @type_name sysname,
    @instance_id UniqueIdentifier = NULL,            
    @instance_name sysname,
    @type_version NVARCHAR(64) = NULL,
    @description nvarchar(4000) = N'',
    @type_stream varbinary(max)
AS  
SET NOCOUNT ON;
BEGIN  
    DECLARE @retval INT  

    DECLARE @null_column sysname    
    SET @null_column = NULL

    IF (@type_name IS NULL OR @type_name = N'')
        SET @null_column = '@type_name'
    ELSE IF (@instance_name IS NULL OR @instance_name = N'')
        SET @null_column = '@instance_name'
    ELSE IF (@instance_id IS NULL )
        SET @null_column = '@instance_id'
    ELSE IF( @type_version = N'')
        SET @null_column = '@type_version'
    ELSE IF( @type_stream IS NULL)
        SET @null_column = '@type_stream'
      

    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_sysdac_add_instance')
        RETURN(1)
    END

    -- only users that can create a dac can add instances
    if (dbo.fn_sysdac_is_dac_creator() != 1)
    BEGIN
        RAISERROR(36010, -1, -1);
        RETURN(1); -- failure
    END
    
    --instance_name is unique
    IF EXISTS (SELECT * FROM dbo.sysdac_instances_internal WHERE instance_name = @instance_name) 
    BEGIN
        RAISERROR(36001, -1, -1, 'DacInstance', @instance_name)
        RETURN(1)
    END

    --Ensure that the database being referred exists
    IF NOT EXISTS (SELECT * from sys.sysdatabases WHERE name = @instance_name)
    BEGIN
        RAISERROR(36005, -1, -1, @instance_name)
        RETURN(1)
    END
  
    INSERT INTO [dbo].[sysdac_instances_internal]
        (instance_id, type_name, instance_name, type_version, description, type_stream)
    VALUES
        (@instance_id, @type_name, @instance_name, @type_version, @description, @type_stream)

    SELECT @retval = @@error
    RETURN(@retval)
END
GO

/*
Procedure [sp_sysdac_update_instance]
This proc updates an existing DacInstance in dbo.sysdac_instances_internal table

Parameters: 
    @type_name - Name of the Dac type. If NULL, then type_name will not be updated.
    @type_version - Version of the Dac type
    @instance_name - Name of the Dac Instance. If NULL, then instance_name will not be updated.
    @instance_id - Guid identification of a Dac instance
    @description - Dac type description
    @type_stream - package content of the dac type

*/
RAISERROR('Creating procedure [dbo].[sp_sysdac_update_instance]...', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysdac_update_instance]  
    @type_name sysname,
    @instance_id UniqueIdentifier = NULL,            
    @instance_name sysname,
    @type_version NVARCHAR(64) = NULL,
    @description nvarchar(4000) = N'',
    @type_stream varbinary(max)
AS  
SET NOCOUNT ON;
BEGIN  
    DECLARE @retval INT  

    DECLARE @null_column sysname    
    SET @null_column = NULL

   IF (@type_name = N'')
        SET @null_column = '@type_name'
    ELSE IF (@instance_name = N'')
        SET @null_column = '@instance_name'
    ELSE IF (@instance_id IS NULL )
        SET @null_column = '@instance_id'
    ELSE IF( @type_version = N'')
        SET @null_column = '@type_version'
    ELSE IF( @type_stream IS NULL)
        SET @null_column = '@type_stream'
      
    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_sysdac_add_instance')
        RETURN(1)
    END

    -- Ensure that the package being referred to exists by using the package view. We only continue if we can see 
    -- the specified package. The package will only be visible if we are the associated dbo or sysadmin and it exists
    IF NOT EXISTS (SELECT * from dbo.sysdac_instances WHERE instance_id = @instance_id)
    BEGIN
        RAISERROR(36004, -1, -1)
        RETURN(1)
    END
    
    UPDATE sysdac_instances_internal SET
        type_name = ISNULL(@type_name, type_name),
        instance_name = ISNULL(@instance_name, instance_name),
		type_version = @type_version,
		description = @description, 
		type_stream = @type_stream
    WHERE instance_id = @instance_id 
    
    SELECT @retval = @@error
    RETURN(@retval)
END
GO

/*
Procedure [sp_sysdac_add_history_entry]
This proc creates a new row in dbo.sysdac_history_internal table

Parameters: 
    @sequence_id --step # of deployment/deletion process
    @instance_id --unique transaction ID = DAC package id
    @part_name --name of the dacpart corresponding to this part
    @action_type --type of the action being performed 0-deploy 1-detach 2-delete
    @dac_object_type --type of the object affected 0-dacpac,1-login,2-database
    @required -- indicates if the steps is needed(value of 1) to be committed for the success of the action 
    @dac_object_name_pretran --name of the  object before the current step completes
    @dac_object_name_posttran --name of the  object after the current step completes
    @sqlscript --sql script for performing the associated object
    @payload --payload data associated with an object. Mostly used for Dacpac
    @comments --comments associated with the history
    @error_string --error while running associated sql
    @action_id --action identifier for each attempt of deploy/delete/detach/upgrade
*/
RAISERROR('Creating procedure [dbo].[sp_sysdac_add_history_entry]...', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysdac_add_history_entry]  
    @sequence_id int,
    @instance_id UniqueIdentifier = NULL,
    @action_type tinyint = NULL,
    @action_status tinyint = NULL,
    @dac_object_type tinyint = NULL,
    @required bit = NULL,
    @dac_object_name_pretran sysname = N'',
    @dac_object_name_posttran sysname = N'',
    @sqlscript nvarchar(max) = N'',
    @payload varbinary(max) = NULL,
    @comments varchar(max) = N'',
    @error_string nvarchar(max) = N'',
    @action_id int = NULL OUTPUT
AS  
SET NOCOUNT ON;
BEGIN  
    DECLARE @retval INT  

    DECLARE @null_column sysname    
    SET @null_column = NULL

    IF (@instance_id IS NULL)
        SET @null_column = '@instance_id'
    ELSE IF (@action_type IS NULL)
        SET @null_column = '@action_type'
    ELSE IF (@action_status IS NULL)
        SET @null_column = '@action_status'
    ELSE IF (@dac_object_type IS NULL)
        SET @null_column = '@dac_object_type'
    ELSE IF (@required IS NULL)
        SET @null_column = '@required'

    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_sysdac_add_history_entry')
        RETURN(1)
    END

    -- comments is optional. make sure it is non-null
    IF (@comments IS NULL)
    BEGIN
        SET @comments = N''
    END

    --- Ensure the user is either a db_creator or that the package being referred is visible via the package view. 
    --- For non-dbcreators, the package will only be visible if we are the associated dbo or sysadmin and the instance row exists
    IF ((dbo.fn_sysdac_is_dac_creator() != 1) AND
         (NOT EXISTS (SELECT * from dbo.sysdac_instances WHERE instance_id = @instance_id)))
    BEGIN
        RAISERROR(36004, -1, -1)
        RETURN(1)
    END
    
    BEGIN TRAN

    --If the action_id value is not set by the user, this is a new entry and the proc
    --should calculate the next value which is one more than the current max
    IF (@action_id IS NULL)
    BEGIN
        SET @action_id = (
            SELECT ISNULL(MAX(action_id) + 1, 0) 
            FROM dbo.sysdac_history_internal WITH (UPDLOCK, HOLDLOCK))        
    END

    INSERT INTO [dbo].[sysdac_history_internal]
        (action_id, sequence_id, instance_id, action_type, dac_object_type, action_status, required,
         dac_object_name_pretran, dac_object_name_posttran, sqlscript, payload, comments, error_string)
    VALUES
        (@action_id, @sequence_id, @instance_id, @action_type, @dac_object_type, @action_status, @required,
         @dac_object_name_pretran, @dac_object_name_posttran, @sqlscript, @payload, @comments, @error_string)

    COMMIT
    
    
    SELECT @retval = @@error
    RETURN(@retval)
END
GO

/*
Procedure [sp_sysdac_delete_instance]
This proc deletes DacInstance with the input instance_id in dbo.sysdac_instances_internal table
 
Parameters: 
    @instance_id - Guid identifier of a Dac Package
*/
RAISERROR('Creating procedure [dbo].[sp_sysdac_delete_instance]...', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysdac_delete_instance]  
    @instance_id UniqueIdentifier
AS  
SET NOCOUNT ON;
BEGIN  
    DECLARE @retval INT  
    DECLARE @partId INT

    IF @instance_id IS NULL
    BEGIN
        RAISERROR(14043, -1, -1, 'instance_id', 'sp_sysdac_delete_instance')
        RETURN(1)
    END
  
    -- Ensure that the package being referred to exists by using the package view. We only continue if we can see 
    -- the specified package. The package will only be visible if we are the associated dbo or sysadmin and it exists
    IF NOT EXISTS (SELECT * from dbo.sysdac_instances WHERE instance_id = @instance_id)
    BEGIN
        RAISERROR(36004, -1, -1)
        RETURN(1)
    END
    
    --Delete the entry of DacInstance
    DELETE FROM sysdac_instances_internal WHERE instance_id=@instance_id 

    SELECT @retval = @@error
    RETURN(@retval)
END
GO

/*
Procedure [sp_sysdac_delete_history]
This proc deletes History entries in the dbo.sysdac_history_internal table
 
Parameters: 
    @dac_instance_name - Name of the dac instance whose history is intended to be purged.
                         If it's null, all entries that the user can delete 'or' orphan entries shall be purged.

    @older_than - DateTime to limit the rows affected.  Rows whose modified datetime < @older_than will only be considered.
                  If it's null, current datetime(GETDATE()) is used.

User needs to either own the DAC or be sysadmin to purge the entries or else, purging will not succeed.

*/
RAISERROR('Creating procedure [dbo].[sp_sysdac_delete_history]...', 0, 1) WITH NOWAIT;
GO

CREATE PROCEDURE [dbo].[sp_sysdac_delete_history]  
    @dac_instance_name sysname, 
    @older_than datetime
AS  
SET NOCOUNT ON;
BEGIN  
    DECLARE @retval INT  
    DECLARE @instanceId UniqueIdentifier

    SELECT @older_than = COALESCE(@older_than, GETDATE())


    IF @dac_instance_name IS NULL
    BEGIN
       -- Delete everyone who is not orphaned that you have visibility to 
       DELETE FROM dbo.sysdac_history_internal
              WHERE instance_id IN (SELECT instance_id FROM dbo.sysdac_instances)
              AND (date_modified < @older_than)

       -- Also remove orphans (note that we need to look into sysdac_instances_internal table)
       DELETE FROM dbo.sysdac_history_internal
              WHERE instance_id NOT IN( SELECT instance_id FROM dbo.sysdac_instances_internal)
              AND (date_modified < @older_than)
    END
    ELSE
    BEGIN
        -- Delete all entries that the user can view (i.e own the DAC or be sysadmin)
        DELETE FROM dbo.sysdac_history_internal
        WHERE instance_id IN (
            SELECT instance_id 
            FROM dbo.sysdac_instances 
            WHERE instance_name = @dac_instance_name)
        AND (date_modified < @older_than)
    END


    SELECT @retval = @@error
    RETURN(@retval)
END
GO


/*
Procedure [sp_sysdac_upgrade_instance]
This proc upgrades existing DacInstance metadata with the new version DacInstance.
 
Parameters: 
    @source_instance_id - Instance identifier of the original Dac Instance
    @instance_id - identifer of the new Dac Instance that's installed as a temporary instance
    @instance_name -  name of the new Dac Instance that's installed as a temporary instance
    @database_name - database name of the original DAC 
*/
RAISERROR('Creating procedure [dbo].[sp_sysdac_upgrade_instance]...', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysdac_upgrade_instance]  
    @source_instance_id UniqueIdentifier = NULL,   
    @instance_id UniqueIdentifier = NULL,            
    @instance_name sysname,
    @database_name sysname
AS  
SET NOCOUNT ON;
BEGIN  
    DECLARE @retval INT  

    DECLARE @null_column sysname    
    SET @null_column = NULL

    IF (@source_instance_id IS NULL)
        SET @null_column = '@source_instance_id'
    ELSE IF (@instance_id IS NULL )
        SET @null_column = '@instance_id'
    ELSE IF( @database_name IS NULL)
        SET @null_column = '@database_name'

    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_sysdac_upgrade_instance')
        RETURN(1)
    END
    
    -- Ensure that the package being referred to exists by using the package view. We only continue if we can see 
    -- the specified package. The package will only be visible if we are the associated dbo or sysadmin and it exists
    IF NOT EXISTS (SELECT * from dbo.sysdac_instances WHERE instance_id = @instance_id)
    BEGIN
        RAISERROR(36004, -1, -1)
        RETURN(1)
    END
    
    --Ensure that the package being referred exists
    IF NOT EXISTS (SELECT * from dbo.sysdac_instances_internal WHERE instance_id = @instance_id)
    BEGIN
        RAISERROR(36004, -1, -1)
        RETURN(1)
    END

    BEGIN TRAN 
    
    --Delete the source DacInstance first
    EXEC dbo.sp_sysdac_delete_instance @instance_id = @instance_id
    
    --Update the new version DacInstance metadata with the original DacInstance
    UPDATE [dbo].[sysdac_instances_internal]
    SET instance_id   = @instance_id, 
        instance_name = @instance_name
    WHERE instance_id = @source_instance_id

    COMMIT
    
    SELECT @retval = @@error
    RETURN(@retval)
END
GO

/*
Procedure [sp_sysdac_drop_database]
This proc drops DAC databases
 
 Parameters
    @database_name : Name of the Database to be dropped
*/
RAISERROR('Creating procedure [dbo].[sp_sysdac_drop_database]...', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysdac_drop_database]  
        @database_name sysname
AS  
SET NOCOUNT ON;
BEGIN  
    IF EXISTS(SELECT name FROM sys.databases WHERE name = @database_name)
    BEGIN
        DECLARE @engineEdition int = CAST(SERVERPROPERTY('EngineEdition') AS int);
        
        DECLARE @quoteddbname nvarchar(258)
        SET @quoteddbname = QUOTENAME(@database_name)
        
        DECLARE @sqlstatement nvarchar(1000)
        
        IF (@engineEdition != 5)
        BEGIN
            SET @sqlstatement = 'ALTER DATABASE ' + @quoteddbname + ' SET SINGLE_USER WITH ROLLBACK IMMEDIATE'
            EXEC (@sqlstatement)
        END 
        
        SET @sqlstatement = 'DROP DATABASE ' + @quoteddbname
        
        IF (@engineEdition = 5)
        BEGIN
			DECLARE @dbname SYSNAME 
			SET @dbname = db_name()
			
            RAISERROR (36012, 0, 1, @dbname, @sqlstatement);
            SELECT @dbname as databasename, @sqlstatement as sqlscript
        END
        ELSE
        BEGIN
            EXEC (@sqlstatement)
        END    
    END
    
    RETURN(@@error)
END
GO



/*
Procedure [sp_sysdac_rename_database]
This proc renames DAC databases
 
 Parameters
    @@database_name : original name of the database 
    @new_name : new name of the database
*/
RAISERROR('Creating procedure [dbo].[sp_sysdac_rename_database]...', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysdac_rename_database]  
        @database_name sysname,
        @new_name sysname
AS  
SET NOCOUNT ON;
BEGIN  
    DECLARE @sqlstatement nvarchar(1000)

    -- Alter the database to single user mode    
    DECLARE @quoted_database_name nvarchar(258)
    SET @quoted_database_name = QUOTENAME(@database_name)
    SET @sqlstatement = 'ALTER DATABASE ' + @quoted_database_name + ' SET SINGLE_USER WITH ROLLBACK IMMEDIATE'
    EXEC (@sqlstatement)

    -- Rename the database
    EXEC sp_rename @objname=@quoted_database_name, @newname=@new_name, @objtype='DATABASE'

    -- Revert the database back to multi user mode
    DECLARE @quoted_new_name nvarchar(258)
    SET @quoted_new_name = QUOTENAME(@new_name)
    SET @sqlstatement = 'ALTER DATABASE ' + @quoted_new_name + ' SET MULTI_USER WITH ROLLBACK IMMEDIATE'
    EXEC (@sqlstatement)
            
    RETURN(@@error)
END
GO

/*
Procedure [sp_sysdac_setreadonly_database]
This proc renames DAC databases
 
 Parameters
    @database_name : original name of the database 
    @readonly : (0) to set readonly, (1) to set readwrite. Default value is 0
*/
RAISERROR('Creating procedure [dbo].[sp_sysdac_setreadonly_database]...', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysdac_setreadonly_database]  
        @database_name sysname,
        @readonly bit = 0
AS  
SET NOCOUNT ON;
BEGIN  
    DECLARE @sqlstatement nvarchar(1000)
    
    DECLARE @quoted_database_name nvarchar(258)   
    SET @quoted_database_name = QUOTENAME(@database_name)
    
    IF (@readonly = 0)
        SET @sqlstatement = 'ALTER DATABASE ' + @quoted_database_name + ' SET READ_ONLY WITH ROLLBACK IMMEDIATE'
    ELSE IF (@readonly = 1)
        SET @sqlstatement = 'ALTER DATABASE ' + @quoted_database_name + ' SET READ_WRITE WITH ROLLBACK IMMEDIATE'    

    EXEC (@sqlstatement)
            
    RETURN(@@error)
END
GO


/*
Procedure [sp_sysdac_update_history_entry]
This proc update entry in dbo.sysdac_history_internal table

Parameters: 
    @action_id --action identifier for each attempt of deploy/delete/detach/upgrade
    @instance_id --unique transaction ID = DAC package id
    @action_type --type of the action being performed 0-deploy 1-detach 2-delete
    @dac_object_type --type of the object affected 0-dacpac,1-login,2-database
    @action_status --pending/success/fail
    @dac_object_name_pretran --name of the  object before the current step completes
    @dac_object_name_posttran --name of the  object after the current step completes
    @sqlscript --sql script for performing the associated object
    @error_string --error while running associated sql
*/
RAISERROR('Creating procedure [dbo].[sp_sysdac_update_history_entry]...', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysdac_update_history_entry]
    @action_id int,
    @instance_id UniqueIdentifier = NULL,
    @action_type tinyint = NULL,
    @dac_object_type tinyint = NULL,
    @action_status tinyint = NULL,
    @dac_object_name_pretran sysname = N'',
    @dac_object_name_posttran sysname = N'',
    @sqlscript nvarchar(max) = N'',
    @error_string nvarchar(max) = N''
AS  
SET NOCOUNT ON;
BEGIN  
    DECLARE @retval INT  

    DECLARE @null_column sysname    
    SET @null_column = NULL

    IF (@instance_id IS NULL)
        SET @null_column = '@instance_id'
    ELSE IF (@action_type IS NULL)
        SET @null_column = '@action_type'
    ELSE IF (@dac_object_type IS NULL)
        SET @null_column = '@dac_object_type'
    ELSE IF (@action_status IS NULL) --action_status should be non-pending (success/failure)
        SET @null_column = '@action_status'

    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_sysdac_update_history_entry')
        RETURN(1)
    END
     
    -- Only allow users who created history entry or 'sysadmins' to update the row
    DECLARE @username SYSNAME
    SET @username = (SELECT created_by 
                     FROM dbo.sysdac_history_internal 
                     WHERE instance_id              = @instance_id AND 
                           action_id                = @action_id AND 
                           action_type              = @action_type AND
                           dac_object_type          = @dac_object_type AND
                           dac_object_name_pretran  = @dac_object_name_pretran AND
                           dac_object_name_posttran = @dac_object_name_posttran)

    IF ((@username != [dbo].[fn_sysdac_get_currentusername]()) AND ([dbo].[fn_sysdac_is_currentuser_sa]() != 1))
    BEGIN
        RAISERROR(36011, -1, -1);
        RETURN(1); -- failure
    END

    UPDATE [dbo].[sysdac_history_internal] 
    SET            
                action_status           = @action_status,
                sqlscript               = @sqlscript,
                error_string            = @error_string,
                date_modified           = (SELECT GETDATE()) 
    WHERE
                action_id               = @action_id AND
                action_type             = @action_type AND
                dac_object_type         = @dac_object_type AND
                dac_object_name_pretran = @dac_object_name_pretran AND
                dac_object_name_posttran = @dac_object_name_posttran
    
    SELECT @retval = @@error
    RETURN(@retval)
END
GO



/*
Procedure [sp_sysdac_resolve_pending_entry]
This proc resolves a pending entry to either completed or failed

Parameters: 
    @action_id --action identifier for each attempt of deploy/delete/detach/upgrade
    @sequence_id --step # of deployment/deletion process
*/
RAISERROR('Creating procedure [dbo].[sp_sysdac_resolve_pending_entry]...', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysdac_resolve_pending_entry]  
    @action_id INT,
    @sequence_id INT
AS  
SET NOCOUNT ON;
BEGIN  
    DECLARE @null_column sysname    
    SET @null_column = NULL

    IF (@action_id IS NULL)
        SET @null_column = '@action_id'
    ELSE IF (@sequence_id IS NULL)
        SET @null_column = '@sequence_id'

    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_sysdac_resolve_pending_entry')
        RETURN(1)
    END

    DECLARE @instance_id UNIQUEIDENTIFIER
    DECLARE @action_type TINYINT
    DECLARE @dac_object_type TINYINT        
    DECLARE @action_status TINYINT
    DECLARE @dac_object_name_pretran SYSNAME
    DECLARE @dac_object_name_posttran SYSNAME

    SELECT @instance_id = instance_id, 
            @action_type = action_type, 
            @dac_object_type = dac_object_type,
            @dac_object_name_pretran = dac_object_name_pretran, 
            @dac_object_name_posttran = dac_object_name_posttran
    FROM sysdac_history_internal
    WHERE action_id = @action_id AND sequence_id = @sequence_id

   
    --Below are the constants set based on history table    
    DECLARE @create TINYINT
    DECLARE @rename TINYINT
    DECLARE @database TINYINT
    DECLARE @success TINYINT
    DECLARE @rollback TINYINT
    DECLARE @fail TINYINT
    DECLARE @register TINYINT
    DECLARE @unregister TINYINT
    DECLARE @upgrade TINYINT
    DECLARE @readonly TINYINT
    DECLARE @readwrite TINYINT
    DECLARE @disconnectusers TINYINT
    DECLARE @readonlymode INT

    SET @create = 1
    SET @rename = 2
    SET @database = 2
    SET @success = 2
    SET @rollback = 4
    SET @fail = 3
    SET @register = 3
    SET @unregister = 14
    SET @upgrade = 15
    SET @readonly = 12
    SET @readwrite = 16
    SET @disconnectusers = 17
    SET @readonlymode = 1024
    
    SET @action_status = @fail --initialize result of the action to failure and adjust if below cases succeed!
    
    IF @action_type = @create AND @dac_object_type = @database --database create
    BEGIN
        IF EXISTS(SELECT 1 FROM sys.sysdatabases WHERE name = @dac_object_name_pretran)
            SET @action_status = @success
    END
    ELSE IF @action_type = @rename AND @dac_object_type = @database --database rename
    BEGIN
        IF (EXISTS(SELECT 1 FROM sys.sysdatabases WHERE name = @dac_object_name_posttran)) AND 
            (NOT EXISTS(SELECT 1 FROM sys.sysdatabases WHERE name = @dac_object_name_pretran))
            SET @action_status = @success 
    END
    ELSE IF @action_type = @register --register DAC
    BEGIN
        IF (EXISTS(SELECT 1 FROM dbo.sysdac_instances_internal WHERE instance_name = @dac_object_name_pretran))
            SET @action_status = @success
    END
    ELSE IF @action_type = @unregister --unregister DAC
    BEGIN
        IF (NOT EXISTS(SELECT 1 FROM dbo.sysdac_instances_internal WHERE instance_name = @dac_object_name_pretran))
            SET @action_status = @success
    END
    ELSE IF @action_type = @upgrade --upgrade DAC
    BEGIN
        IF (EXISTS(SELECT 1 FROM dbo.sysdac_instances_internal WHERE instance_name = @dac_object_name_posttran)) AND 
            (NOT EXISTS(SELECT 1 FROM dbo.sysdac_instances_internal WHERE instance_name = @dac_object_name_pretran))
            SET @action_status = @success     
    END
    ELSE IF @action_type = @readonly OR @action_type = @disconnectusers -- readonly/disconnect users state
    BEGIN
        IF (EXISTS(SELECT 1 FROM sys.sysdatabases 
                            WHERE ((status & @readonlymode) = @readonlymode) AND name=@dac_object_name_pretran))
            SET @action_status = @success
    END
    ELSE IF @action_type = @readwrite -- readwrite state
    BEGIN
        IF (EXISTS(SELECT 1 FROM sys.sysdatabases 
                            WHERE ((status & @readonlymode) != @readonlymode) AND name=@dac_object_name_pretran))
            SET @action_status = @success
    END

    UPDATE sysdac_history_internal
    SET action_status = @action_status
    WHERE action_id = @action_id AND sequence_id = @sequence_id
    
END
GO


/*
Procedure [sp_sysdac_rollback_committed_step]
This proc rollsback a given committed step.

Parameters: 
    @action_id --action identifier for each attempt of deploy/delete/detach/upgrade
    @sequence_id --step # of deployment/deletion process
*/
RAISERROR('Creating procedure [dbo].[sp_sysdac_rollback_committed_step]...', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysdac_rollback_committed_step]  
    @action_id INT,
    @sequence_id INT
AS  
SET NOCOUNT ON;
BEGIN  
    DECLARE @retval INT  

    DECLARE @null_column sysname    
    SET @null_column = NULL

    IF (@action_id IS NULL)
        SET @null_column = '@action_id'
    ELSE IF (@sequence_id IS NULL)
        SET @null_column = '@sequence_id'

    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_sysdac_rollback_committed_step')
        RETURN(1)
    END

    DECLARE @instance_id UNIQUEIDENTIFIER
    DECLARE @part_name NVARCHAR(128)
    DECLARE @action_type TINYINT
    DECLARE @dac_object_type TINYINT        
    DECLARE @action_status TINYINT
    DECLARE @dac_object_name_pretran SYSNAME
    DECLARE @dac_object_name_posttran SYSNAME
    DECLARE @sqlstatement NVARCHAR(1000)        

    SELECT @instance_id = instance_id, 
            @action_id = action_id, 
            @action_type = action_type, 
            @sequence_id = sequence_id,
            @dac_object_type = dac_object_type,
            @action_status = action_status, 
            @dac_object_name_pretran = dac_object_name_pretran, 
            @dac_object_name_posttran = dac_object_name_posttran
    FROM sysdac_history_internal
    WHERE action_id = @action_id AND sequence_id = @sequence_id
    
    --Below are the constants set based on history table    
    DECLARE @create TINYINT
    DECLARE @rename TINYINT
    DECLARE @register TINYINT
    DECLARE @database TINYINT
    DECLARE @rollback TINYINT
    DECLARE @rollback_pending TINYINT
    DECLARE @rollback_success TINYINT
    DECLARE @setreadonly TINYINT
    DECLARE @setreadwrite TINYINT

    SET @create = 1
    SET @rename = 2
    SET @register = 3
    SET @database = 2
    SET @rollback = 4
    SET @rollback_pending = 0
    SET @rollback_success = 1
    SET @setreadonly = 12
    SET @setreadwrite = 16
    
    IF @action_type = @create AND @dac_object_type = @database --database create
    BEGIN
        RAISERROR(N'%d, %d, %s', -1, 1, @sequence_id, @rollback_pending, NULL) WITH NOWAIT

        EXEC dbo.sp_sysdac_drop_database @database_name = @dac_object_name_pretran
        
        RAISERROR(N'%d, %d, %s', -1, 1, @sequence_id, @rollback_success, NULL) WITH NOWAIT
    END
    ELSE IF @action_type = @rename AND @dac_object_type = @database --database rename
    BEGIN
        RAISERROR(N'%d, %d, %s', -1, 1, @sequence_id, @rollback_pending, NULL) WITH NOWAIT

        EXEC dbo.sp_sysdac_rename_database @dac_object_name_posttran, @dac_object_name_pretran

        RAISERROR(N'%d, %d, %s', -1, 1, @sequence_id, @rollback_success, NULL) WITH NOWAIT
    END
    ELSE IF @action_type = @register --register DAC
    BEGIN
        SET @instance_id = (
            SELECT instance_id 
            FROM dbo.sysdac_instances_internal 
            WHERE instance_name = @dac_object_name_pretran)

        RAISERROR(N'%d, %d, %s', -1, 1, @sequence_id, @rollback_pending, NULL) WITH NOWAIT

        EXEC dbo.sp_sysdac_delete_instance @instance_id = @instance_id

        RAISERROR(N'%d, %d, %s', -1, 1, @sequence_id, @rollback_success, NULL) WITH NOWAIT
    END
    ELSE IF @action_type = @setreadonly  --readonly step
    BEGIN
        RAISERROR(N'%d, %d, %s', -1, 1, @sequence_id, @rollback_pending, NULL) WITH NOWAIT

        EXEC dbo.sp_sysdac_setreadonly_database @database_name = @dac_object_name_pretran, @readonly = 1

        RAISERROR(N'%d, %d, %s', -1, 1, @sequence_id, @rollback_success, NULL) WITH NOWAIT        
    END
    ELSE IF @action_type = @setreadwrite  --readonly step
    BEGIN
        RAISERROR(N'%d, %d, %s', -1, 1, @sequence_id, @rollback_pending, NULL) WITH NOWAIT

        EXEC dbo.sp_sysdac_setreadonly_database @database_name = @dac_object_name_pretran, @readonly = 0

        RAISERROR(N'%d, %d, %s', -1, 1, @sequence_id, @rollback_success, NULL) WITH NOWAIT        
    END
    
    --mark the entry as rolledback
    UPDATE sysdac_history_internal
    SET action_status = @rollback
    WHERE action_id = @action_id AND sequence_id = @sequence_id
    
    SELECT @retval = @@error
    RETURN(@retval)
END
GO


/*
Procedure [sp_sysdac_rollback_pending_object]
This proc rollsback the pending object of a given action id. 

Parameters: 
    @action_id --action identifier for each attempt of deploy/delete/detach/upgrade
*/
RAISERROR('Creating procedure [dbo].[sp_sysdac_rollback_pending_object]...', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysdac_rollback_pending_object]  
    @action_id INT
AS  
SET NOCOUNT ON;
BEGIN  
    IF (@action_id IS NULL)
    BEGIN
        RAISERROR(14043, -1, -1, '@action_id', 'sp_sysdac_rollback_pending_object')
        RETURN(1)
    END

    DECLARE @sequence_id INT
    DECLARE @action_status TINYINT

    --Below are the constants set based on history table    
    DECLARE @header_id bit
    DECLARE @pending TINYINT
    DECLARE @success TINYINT
    DECLARE @true bit
    DECLARE @rollback TINYINT
    DECLARE @fail TINYINT
    DECLARE @rollback_failure TINYINT

    SET @header_id = 0
    SET @pending = 1
    SET @success = 2
    SET @true = 1
    SET @rollback = 4
    SET @fail = 3
    SET @rollback_failure = 2
    
    --if step 0 is not pending, exit
    IF ((SELECT action_status 
        FROM sysdac_history_internal 
        WHERE action_id = @action_id AND sequence_id = @header_id) != @pending)
        RETURN;

    
    --STEP 1. Resolve pending entry 
    SET @sequence_id = (SELECT TOP 1 sequence_id 
                        FROM sysdac_history_internal 
                        WHERE sequence_id != @header_id AND action_id = @action_id AND action_status = @pending)

    IF (@sequence_id IS NOT NULL)
        EXEC dbo.sp_sysdac_resolve_pending_entry @action_id = @action_id, @sequence_id = @sequence_id
    
    --check if all required steps are committed(success). If so, mark the action success and return!
    IF NOT EXISTS (SELECT 1
                    FROM sysdac_history_internal 
                    WHERE action_id = @action_id AND sequence_id != @header_id AND required = @true AND action_status != @success)
    BEGIN
        UPDATE dbo.sysdac_history_internal
        SET action_status = @success
        WHERE action_id = @action_id AND sequence_id = @header_id
    
        RETURN
    END
     
    BEGIN TRY
        
        --STEP 2. rollback commit entries
        WHILE EXISTS( SELECT 1 
                        FROM sysdac_history_internal 
                        WHERE action_status = @success AND action_id = @action_id AND sequence_id > 0)
        BEGIN
            SELECT TOP 1 @sequence_id = sequence_id,
                        @action_status = action_status
            FROM sysdac_history_internal
            WHERE action_status = @success AND action_id = @action_id AND sequence_id != @header_id
            ORDER BY sequence_id DESC

            EXEC dbo.sp_sysdac_rollback_committed_step @action_id = @action_id, @sequence_id = @sequence_id
            
        END

        --Mark the header entry as rolledback
        SET @action_status = @rollback    

    END TRY
    BEGIN CATCH
        DECLARE @error_message NVARCHAR(4000);
        
        SELECT @error_message = ERROR_MESSAGE()

        RAISERROR(N'%d, %d, %s', -1, 1, @sequence_id, @rollback_failure, @error_message) WITH NOWAIT

        --Mark the header entry as failed
        SET @action_status = @fail
    END CATCH

    --STEP 3. Mark the header entry with final action status
    UPDATE dbo.sysdac_history_internal
    SET action_status = @action_status
    WHERE action_id = @action_id AND sequence_id = @header_id    

END
GO



/*
Procedure [sp_sysdac_rollback_all_pending_objects]

This proc rollsback all the pending actions.

Parameters: 
    @return_scripts -- if set to 1, stored procedure will return as a resultset 
    a set of sql scripts needed to be additionally executed to complete the rollback.
    
    Rollback procedure might not be able to execute them due to sql server limitations.
*/
RAISERROR('Creating procedure [dbo].[sp_sysdac_rollback_all_pending_objects]...', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysdac_rollback_all_pending_objects] (@return_scripts TINYINT = 0)
AS  
SET NOCOUNT ON;
BEGIN  
    DECLARE @action_id INT
    DECLARE @sequence_id INT

    --Below are the constants set based on history table    
    DECLARE @header_id bit
    DECLARE @pending TINYINT

    SET @header_id = 0
    SET @pending = 1
    
    CREATE TABLE #additional_scripts(databasename sysname, sqlscript VARCHAR(MAX))
    
    WHILE EXISTS (SELECT 1 FROM sysdac_history_internal WHERE sequence_id = @header_id AND action_status = @pending)
    BEGIN
        SET @action_id = (SELECT TOP 1 action_id FROM sysdac_history_internal WHERE sequence_id = @header_id AND action_status = @pending)

        INSERT INTO #additional_scripts
        EXEC dbo.sp_sysdac_rollback_pending_object @action_id = @action_id
    END
    
    IF (@return_scripts = 1)
    BEGIN
        SELECT databasename, sqlscript FROM #additional_scripts
    END
END
GO




-----------------------------------------------------------
-- Security for Dac objects
-----------------------------------------------------------
-- everybody can see the dacs view. These are filtered views so no info disclosure by it being granted to public
GRANT SELECT ON dbo.sysdac_instances to PUBLIC

-- everybody can see if they are a dac creator or not
GRANT EXECUTE ON dbo.fn_sysdac_is_dac_creator to PUBLIC
GRANT EXECUTE ON dbo.fn_sysdac_is_login_creator to PUBLIC
GRANT EXECUTE ON dbo.fn_sysdac_is_currentuser_sa to PUBLIC

GRANT EXECUTE ON dbo.sp_sysdac_ensure_dac_creator to PUBLIC

-- we allow anybody to execute the sprocs, they are guarded by the is_dac_creator function
GRANT EXECUTE ON dbo.sp_sysdac_rename_database to PUBLIC
GRANT EXECUTE ON dbo.sp_sysdac_drop_database to PUBLIC
GRANT EXECUTE ON dbo.sp_sysdac_update_instance to PUBLIC
GRANT EXECUTE ON dbo.sp_sysdac_upgrade_instance to PUBLIC
GRANT EXECUTE ON dbo.sp_sysdac_delete_instance to PUBLIC
GRANT EXECUTE ON dbo.sp_sysdac_delete_history to PUBLIC
GRANT EXECUTE ON dbo.sp_sysdac_add_instance to PUBLIC
GRANT EXECUTE ON dbo.sp_sysdac_add_history_entry to PUBLIC
GRANT EXECUTE ON dbo.sp_sysdac_update_history_entry to PUBLIC

--End DAC Script
RAISERROR('', 0, 1) WITH NOWAIT;
RAISERROR('------------------------------------', 0, 1) WITH NOWAIT;
RAISERROR('Execution of InstDac.SQL complete', 0, 1) WITH NOWAIT;
RAISERROR('------------------------------------', 0, 1) WITH NOWAIT;
GO


/**************************************************************/
/*                     END DAC SCRIPTS                     */
/**************************************************************/
PRINT '-----------------------------------------'
PRINT 'Starting execution of EXTENSIBILITY.SQL'
PRINT '-----------------------------------------'
go

IF (OBJECT_ID(N'msdb.dbo.external_libraries_installed', 'U') IS NULL)
BEGIN
	PRINT ''
	PRINT 'Creating table external_libraries_installed...'
	CREATE TABLE msdb.dbo.external_libraries_installed
	(
		db_id				INT				NOT NULL,
		principal_id		INT				NOT NULL,	-- SQL principal, ie a user
		language_id			INT				NOT NULL,	-- R/Python ID
		external_library_id	INT				NOT NULL,
		name				NVARCHAR(128)	NOT NULL,
		mdversion			BINARY(8)		NOT NULL,
	)
	CREATE UNIQUE CLUSTERED INDEX IX_external_libraries_installed ON msdb.dbo.external_libraries_installed (db_id, principal_id, language_id, external_library_id ASC, name)
END
GO

PRINT '-----------------------------------------'
PRINT 'Finished execution of EXTENSIBILITY.SQL'
PRINT '-----------------------------------------'
GO
/**************************************************************/
/*                   BEGIN UTILITY SCRIPTS                    */
/**************************************************************/

-- These settings are necessary for the Utility.
SET ANSI_NULLS ON
SET ANSI_NULL_DFLT_ON ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
SET ARITHABORT ON
SET CONCAT_NULL_YIELDS_NULL ON
SET NUMERIC_ROUNDABORT OFF
SET QUOTED_IDENTIFIER ON
GO

IF (OBJECT_ID ('tempdb..#configuration_original_values') IS NOT NULL)
BEGIN 
    DROP TABLE #configuration_original_values;
END;
GO

-- We call SQL Agent procs that require the Agent XPs config value to be enabled.  Normally, Agent XPs is 
-- only enabled when Agent is running, but there are cases (e.g. upgrade) where this script is executed but 
-- Agent will never be running.  Force the configuration setting on so that we can run even in these cases. 
DECLARE @advanced_options_original_value INT;
DECLARE @configuration_original_value    INT;
DECLARE @configuration_option            VARCHAR(128) = 'Agent XPs';
EXEC #sp_enable_component 
    @comp_name = @configuration_option, 
    @advopt_old_value = @advanced_options_original_value OUTPUT, 
    @comp_old_value = @configuration_original_value OUTPUT; 

-- Save the original value of the config options that we just changed.  We'll restore the original values 
-- when Utility DDL is done. 
SELECT 
   @configuration_option AS configuration_option, 
   @advanced_options_original_value AS advanced_options_original_value, 
   @configuration_original_value AS configuration_original_value 
INTO #configuration_original_values;
GO


-----------------------------------------------------------
-- Security for Utility objects
-----------------------------------------------------------
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'UtilityCMRReader' AND type = 'R'))
BEGIN
    CREATE ROLE [UtilityCMRReader]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'UtilityCMRReader' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [UtilityCMRReader]
        CREATE ROLE [UtilityCMRReader]
    END
END
GO



IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'UtilityIMRWriter' AND type = 'R'))
BEGIN
    CREATE ROLE [UtilityIMRWriter]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'UtilityIMRWriter' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [UtilityIMRWriter]
        CREATE ROLE [UtilityIMRWriter]
    END
END
GO



IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'UtilityIMRReader' AND type = 'R'))
BEGIN
    CREATE ROLE [UtilityIMRReader]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'UtilityIMRReader' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [UtilityIMRReader]
        CREATE ROLE [UtilityIMRReader]
    END
END
GO

-- A UtilityIMRWriter is also a UtilityIMRReader
EXECUTE sp_addrolemember @rolename = 'UtilityIMRReader' , 
                   @membername = 'UtilityIMRWriter' 
GO


/**************************************************************************/
/* Create the Utility Control Point configuration table                   */
/* This table stores configuration values for the UCP.                    */
/**************************************************************************/

IF(OBJECT_ID(N'[dbo].[sysutility_ucp_configuration_internal]', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table [dbo].[sysutility_ucp_configuration_internal]', 0, 1) WITH NOWAIT;
    CREATE TABLE [dbo].[sysutility_ucp_configuration_internal] (
        name sysname PRIMARY KEY CLUSTERED NOT NULL,
        current_value sql_variant NULL);

    INSERT INTO [dbo].[sysutility_ucp_configuration_internal] (name, current_value)
    VALUES (N'UtilityName', '');
    
    INSERT INTO [dbo].[sysutility_ucp_configuration_internal] (name, current_value)
    VALUES (N'MdwDatabaseName', '');
    
    INSERT INTO [dbo].[sysutility_ucp_configuration_internal] (name, current_value)
    VALUES (N'UtilityDescription', '');
    
    INSERT INTO [dbo].[sysutility_ucp_configuration_internal] (name, current_value)
    VALUES (N'UtilityDateCreated', '');
    
    INSERT INTO [dbo].[sysutility_ucp_configuration_internal] (name, current_value)
    VALUES (N'UtilityCreatedBy', '');

    INSERT INTO [dbo].[sysutility_ucp_configuration_internal] (name, current_value)
    VALUES (N'OverUtilizationTrailingWindow', 1);

    INSERT INTO [dbo].[sysutility_ucp_configuration_internal] (name, current_value)
    VALUES (N'OverUtilizationOccurenceFrequency', 25);

    INSERT INTO [dbo].[sysutility_ucp_configuration_internal] (name, current_value)
    VALUES (N'UnderUtilizationTrailingWindow', 168);

    INSERT INTO [dbo].[sysutility_ucp_configuration_internal] (name, current_value)
    VALUES (N'UnderUtilizationOccurenceFrequency', 90);

    INSERT INTO [dbo].[sysutility_ucp_configuration_internal] (name, current_value)
    VALUES (N'MdwRetentionLengthInDaysForMinutesHistory', 2);

    INSERT INTO [dbo].[sysutility_ucp_configuration_internal] (name, current_value)
    VALUES (N'MdwRetentionLengthInDaysForHoursHistory', 31);

    INSERT INTO [dbo].[sysutility_ucp_configuration_internal] (name, current_value)
    VALUES (N'MdwRetentionLengthInDaysForDaysHistory', 366);

    INSERT INTO [dbo].[sysutility_ucp_configuration_internal] (name, current_value)
    VALUES (N'UtilityVersion', N'1.0.0.0');
    
END
GO


/**********************************************************************/
/* Create the Utility Control Point configuration view                */
/**********************************************************************/
IF(OBJECT_ID(N'[dbo].[sysutility_ucp_configuration]', 'V') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping view [dbo].[sysutility_ucp_configuration]', 0, 1) WITH NOWAIT;
    DROP VIEW [dbo].[sysutility_ucp_configuration]
END
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_configuration]...', 0, 1) WITH NOWAIT;
GO
CREATE VIEW [dbo].[sysutility_ucp_configuration]
AS
    SELECT     
        name,
        current_value
    FROM [dbo].[sysutility_ucp_configuration_internal]
GO


/**********************************************************************/
/* Create the utility control point policy configuration view         */
/* This view returns the occurrence frequency and trailing window for */
/* both over and under utilization resource health policy types       */
/**********************************************************************/
IF (OBJECT_ID(N'[dbo].[sysutility_ucp_policy_configuration]', 'V') IS NOT NULL) 
BEGIN
    RAISERROR ('Dropping view [dbo].[sysutility_ucp_policy_configuration]', 0, 1) WITH NOWAIT;
    DROP VIEW dbo.sysutility_ucp_policy_configuration
END	
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_policy_configuration]...', 0, 1) WITH NOWAIT;
GO
CREATE VIEW dbo.sysutility_ucp_policy_configuration AS
(    
    SELECT 1 AS utilization_type
        , CAST(UnderUtilizationOccurenceFrequency AS INT) AS occurence_frequency
        , CAST(UnderUtilizationTrailingWindow AS INT) AS trailing_window
    FROM (SELECT name, current_value FROM msdb.dbo.sysutility_ucp_configuration) config
        PIVOT (MAX(current_value) FOR name IN (UnderUtilizationOccurenceFrequency, UnderUtilizationTrailingWindow)) pvt

    UNION ALL

    SELECT 2 AS utilization_type
        , CAST(OverUtilizationOccurenceFrequency AS INT) AS occurence_frequency
        , CAST(OverUtilizationTrailingWindow AS INT) AS trailing_window
    FROM (SELECT name, current_value FROM msdb.dbo.sysutility_ucp_configuration) config
        PIVOT (MAX(current_value) FOR name IN (OverUtilizationOccurenceFrequency, OverUtilizationTrailingWindow)) pvt
) 
GO


/********************************************************************
Function fn_sysutility_get_is_instance_ucp 
   Description: Returns 1 if the local instance is a UCP, 0 otherwise
********************************************************************/
IF OBJECT_ID(N'[dbo].[fn_sysutility_get_is_instance_ucp]') IS NOT NULL
BEGIN
   RAISERROR('Dropping [dbo].[fn_sysutility_get_is_instance_ucp]', 0, 1)  WITH NOWAIT;
   DROP FUNCTION [dbo].[fn_sysutility_get_is_instance_ucp];
END
GO
RAISERROR('Creating [dbo].[fn_sysutility_get_is_instance_ucp]', 0, 1)  WITH NOWAIT;
GO

CREATE FUNCTION [dbo].[fn_sysutility_get_is_instance_ucp]()
RETURNS BIT 
AS
BEGIN
   RETURN (
      SELECT 
         CASE 
            WHEN ISNULL ((SELECT CAST (current_value as sysname) FROM msdb.dbo.sysutility_ucp_configuration_internal WHERE name = 'UtilityName'), '') = ''
            THEN 0
            ELSE 1
         END)
END;
GO


/********************************************************************
Function fn_sysutility_get_culture_invariant_conversion_style_internal
   Description: Returns an integer that can be passed to CONVERT's "style" parameter 
   in order to round-trip a value of the specified type to or from a string without 
   data loss and in a culture-invariant way.  
********************************************************************/
IF OBJECT_ID('dbo.fn_sysutility_get_culture_invariant_conversion_style_internal') IS NOT NULL
BEGIN
   DROP FUNCTION dbo.fn_sysutility_get_culture_invariant_conversion_style_internal;
END;
GO
CREATE FUNCTION dbo.fn_sysutility_get_culture_invariant_conversion_style_internal (@data_type varchar(30))
RETURNS tinyint
AS 
BEGIN
   RETURN 
      CASE 
         -- ISO8601, e.g. "yyyy-mm-ddThh:mi:ss.mmm"
         WHEN @data_type IN ('datetime', 'datetimeoffset', 'smalldatetime', 'datetime2', 'date', 'time') THEN 126
         -- scientific notation, 16 digits
         WHEN @data_type IN ('real', 'float') THEN 2
         -- e.g. "0x12AB"
         WHEN @data_type IN ('binary', 'varbinary') THEN 1 
         -- all other types including bit, integer types, (n)varchar, decimal/numeric
         ELSE 0 
      END;
END;
GO



/********************************************************************
   Create "stub" objects that stand in for Utility MDW objects. 
   
   We have views in msdb that wrap tables, views, and functions in the MDW database.  The 
   sysutility_mdw database is only created when an instance is made a UCP, so at the time of 
   instmsdb.sql execution sysutility_mdw doesn't yet exist.  This is a problem because views 
   and inline functions in msdb cannot be created unless all of the objects (and columns) 
   that they reference do exist.  The current solution is to create these "stub" objects in 
   msdb, create synonyms pointing to the stub objects, then reference the synonyms within any 
   functions or views in msdb that need to reference MDW tables.  If the instance is made a 
   UCP, the Create UCP process executes the sp_sysutility_ucp_initialize_mdw stored proc, 
   which will redirect the synonyms to reference the true MDW tables after the sysutility_mdw 
   database has been created.  
   
   These stub objects should only be modified when the structure of one of the corresponding 
   MDW objects is modified.  Note that these objects will never hold any data, so there is no 
   reason to bother with build-to-build schema upgrade steps; we can safely drop and recreate 
   them whenever instmsdb.sql is executed.  
********************************************************************/

------ BEGIN UTILITY MDW STUB OBJECTS ------

-- Stub for MDW object [snapshots].[sysutility_ucp_core.latest_dacs]
IF OBJECT_ID(N'[dbo].[sysutility_ucp_dacs_stub]') IS NOT NULL
BEGIN
   RAISERROR('Dropping table [dbo].[sysutility_ucp_dacs_stub]', 0, 1) WITH NOWAIT;
   DROP TABLE [dbo].[sysutility_ucp_dacs_stub];
END;
GO
RAISERROR('Creating table [sysutility_ucp_dacs_stub]', 0, 1)  WITH NOWAIT;
CREATE TABLE [sysutility_ucp_dacs_stub]
(
   [dac_id] INT IDENTITY,   -- todo (VSTS #345036): This column will be removed
   
   [physical_server_name] SYSNAME,
   [server_instance_name] SYSNAME,  -- the server-qualified instance name
   [dac_name] SYSNAME,
   [dac_deploy_date] DATETIME,
   [dac_description] NVARCHAR(4000) NULL,
   [urn] NVARCHAR(4000),
   [powershell_path] NVARCHAR(4000),
   
   [processing_time] DATETIMEOFFSET(7),               
   batch_time DATETIMEOFFSET(7),
   
   -- todo (VSTS #345040) (no measure columns in dimension tables)
   [dac_percent_total_cpu_utilization] REAL 
)
GO

-- Stub for MDW object [snapshots].[sysutility_ucp_core.latest_volumes]

IF OBJECT_ID(N'[dbo].[sysutility_ucp_volumes_stub]') IS NOT NULL
BEGIN
   RAISERROR('Dropping table [dbo].[sysutility_ucp_volumes_stub]', 0, 1) WITH NOWAIT;
   DROP TABLE [dbo].[sysutility_ucp_volumes_stub];
END;
GO
RAISERROR('Creating table [sysutility_ucp_latest_volumes_stub]', 0, 1)  WITH NOWAIT;
CREATE TABLE [sysutility_ucp_volumes_stub]
(
     [ID] INT IDENTITY,    -- todo (VSTS #345036): This column will be removed
   
     virtual_server_name SYSNAME,
     physical_server_name SYSNAME,
   
     volume_device_id SYSNAME,
     volume_name SYSNAME,

     -- todo (VSTS #345040) (no measure columns in dimension tables)
     total_space_available real,
     free_space  real,
     total_space_utilized real,
     percent_total_space_utilization real,
     processing_time DATETIMEOFFSET(7),
     batch_time DATETIMEOFFSET(7),
     powershell_path NVARCHAR(4000)

)

-- Stub for MDW object [snapshots].[sysutility_ucp_core.latest_computers]
IF OBJECT_ID(N'[dbo].[sysutility_ucp_computers_stub]') IS NOT NULL
BEGIN
   RAISERROR('Dropping table [dbo].[sysutility_ucp_computers_stub]', 0, 1) WITH NOWAIT;
   DROP TABLE [dbo].[sysutility_ucp_computers_stub];
END;
GO
RAISERROR('Creating table [sysutility_ucp_computers_stub]', 0, 1)  WITH NOWAIT;
CREATE TABLE [sysutility_ucp_computers_stub]
(
   [id] INT IDENTITY,    -- todo (VSTS #345036): This column will be removed 
   
   virtual_server_name SYSNAME,
   physical_server_name SYSNAME,    -- differs from server_name for clustered servers
   is_clustered_server INT,
   
   num_processors INT,
   cpu_name NVARCHAR(128),
   cpu_caption NVARCHAR(128),
   cpu_family NVARCHAR(128),
   cpu_architecture NVARCHAR(64),
   cpu_max_clock_speed DECIMAL(10),
   cpu_clock_speed DECIMAL(10),
   l2_cache_size DECIMAL(10),
   l3_cache_size DECIMAL(10),

   urn NVARCHAR(4000),
   powershell_path NVARCHAR(4000),
      
   processing_time DATETIMEOFFSET(7),
   batch_time DATETIMEOFFSET(7),

   percent_total_cpu_utilization REAL    -- todo (VSTS #345040) (no measure columns in dimension tables)
   
)

-- Stub for MDW object [snapshots].[sysutility_ucp_latest_smo_servers]
IF OBJECT_ID(N'[dbo].[sysutility_ucp_smo_servers_stub]') IS NOT NULL
BEGIN
   RAISERROR('Dropping table [dbo].[sysutility_ucp_smo_servers_stub]', 0, 1) WITH NOWAIT;
   DROP TABLE [dbo].[sysutility_ucp_smo_servers_stub];
END;
GO

RAISERROR ('Creating table [dbo].[sysutility_ucp_smo_servers_stub]', 0, 1) WITH NOWAIT;
CREATE TABLE [dbo].[sysutility_ucp_smo_servers_stub]
(
   [urn] NVARCHAR(512),
   [powershell_path] NVARCHAR(4000),
   [processing_time] DATETIMEOFFSET(7),
   [batch_time] DATETIMEOFFSET(7),
   [AuditLevel] SMALLINT,
   [BackupDirectory] NVARCHAR(260),
   [BrowserServiceAccount] NVARCHAR(128),
   [BrowserStartMode] SMALLINT,
   [BuildClrVersionString] NVARCHAR(20),
   [BuildNumber] INT,
   [Collation] NVARCHAR(128),
   [CollationID] INT,
   [ComparisonStyle] INT,
   [ComputerNamePhysicalNetBIOS] NVARCHAR(128),
   [DefaultFile] NVARCHAR(260),
   [DefaultLog] NVARCHAR(260),
   [Edition] NVARCHAR(64),
   [EngineEdition] SMALLINT,
   [ErrorLogPath] NVARCHAR(260),
   [FilestreamShareName] NVARCHAR(260),
   [InstallDataDirectory] NVARCHAR(260),
   [InstallSharedDirectory] NVARCHAR(260),
   [InstanceName] NVARCHAR(128),
   [IsCaseSensitive] BIT,
   [IsClustered] BIT,
   [IsFullTextInstalled] BIT,
   [IsSingleUser] BIT,
   [Language] NVARCHAR(64),
   [MailProfile] NVARCHAR(128),
   [MasterDBLogPath] NVARCHAR(260),
   [MasterDBPath] NVARCHAR(260),
   [MaxPrecision] TINYINT,
   [Name] NVARCHAR(128),
   [NamedPipesEnabled] BIT,
   [NetName] NVARCHAR(128),
   [NumberOfLogFiles] INT,
   [OSVersion] NVARCHAR(32),
   [PerfMonMode] SMALLINT,
   [PhysicalMemory] INT,
   [Platform] NVARCHAR(32),
   [Processors] SMALLINT,
   [ProcessorUsage] INT,
   [Product] NVARCHAR(48),
   [ProductLevel] NVARCHAR(32),
   [ResourceVersionString] NVARCHAR(32),
   [RootDirectory] NVARCHAR(260),
   [ServerType] SMALLINT,
   [ServiceAccount] NVARCHAR(128),
   [ServiceInstanceId] NVARCHAR(64),
   [ServiceName] NVARCHAR(64),
   [ServiceStartMode] SMALLINT,
   [SqlCharSet] SMALLINT,
   [SqlCharSetName] NVARCHAR(32),
   [SqlDomainGroup] NVARCHAR(260),
   [SqlSortOrder] SMALLINT,
   [SqlSortOrderName] NVARCHAR(64),
   [Status] SMALLINT,
   [TapeLoadWaitTime] INT,
   [TcpEnabled] BIT,
   [VersionMajor] INT,
   [VersionMinor] INT,
   [VersionString] NVARCHAR(32)
);
GO


-- Stub for MDW object [snapshots].[sysutility_ucp_databases]
IF OBJECT_ID(N'[dbo].[sysutility_ucp_databases_stub]') IS NOT NULL
BEGIN
   RAISERROR('Dropping table [dbo].[sysutility_ucp_databases_stub]', 0, 1) WITH NOWAIT;
   DROP TABLE [dbo].[sysutility_ucp_databases_stub];
END;
GO

RAISERROR ('Creating table [dbo].[sysutility_ucp_databases_stub]', 0, 1) WITH NOWAIT;
CREATE TABLE [dbo].[sysutility_ucp_databases_stub]
(
     [urn] NVARCHAR(512)
     , [powershell_path]  NVARCHAR(MAX)
     , [processing_time] DATETIMEOFFSET(7)
     , [batch_time] DATETIMEOFFSET(7)
     , [server_instance_name]    SYSNAME
     , [parent_urn] NVARCHAR(320)
     , [Collation] NVARCHAR(128)
     , [CompatibilityLevel] SMALLINT
     , [CreateDate] DATETIME
     , [EncryptionEnabled] BIT
     , [Name] NVARCHAR(128)
     , [RecoveryModel] SMALLINT
     , [Trustworthy]	BIT  
     , [state] TINYINT
);
GO

-- Stub for MDW object [snapshots].[sysutility_ucp_filegroups]
IF OBJECT_ID(N'[dbo].[sysutility_ucp_filegroups_stub]') IS NOT NULL
BEGIN
   RAISERROR('Dropping table [dbo].[sysutility_ucp_filegroups_stub]', 0, 1) WITH NOWAIT;
   DROP TABLE [dbo].[sysutility_ucp_filegroups_stub];
END;
GO

RAISERROR ('Creating table [dbo].[sysutility_ucp_filegroups_stub]', 0, 1) WITH NOWAIT;
CREATE TABLE [dbo].[sysutility_ucp_filegroups_stub]
(
    [urn] NVARCHAR(780)
    , [powershell_path]  NVARCHAR(MAX)
    , [processing_time] DATETIMEOFFSET(7)
    , [batch_time] DATETIMEOFFSET(7)
    , [server_instance_name]    SYSNAME
    , [database_name] SYSNAME
    , [parent_urn] NVARCHAR(512)
    , [Name] NVARCHAR(128)
);
GO

-- Stub for MDW object [snapshots].[sysutility_ucp_datafiles]
IF OBJECT_ID(N'[dbo].[sysutility_ucp_datafiles_stub]') IS NOT NULL
BEGIN
   RAISERROR('Dropping table [dbo].[sysutility_ucp_datafiles_stub]', 0, 1) WITH NOWAIT;
   DROP TABLE [dbo].[sysutility_ucp_datafiles_stub];
END;
GO

RAISERROR ('Creating table [dbo].[sysutility_ucp_datafiles_stub]', 0, 1) WITH NOWAIT;
CREATE TABLE [dbo].[sysutility_ucp_datafiles_stub]
(
     [urn] NVARCHAR(1500)
     , [powershell_path]  NVARCHAR(MAX)
     , [processing_time] DATETIMEOFFSET(7)
     , [batch_time] DATETIMEOFFSET(7)    
     , [server_instance_name]    SYSNAME
     , [database_name] SYSNAME
     , [filegroup_name] SYSNAME	
     , [parent_urn] NVARCHAR(780)
     , [physical_server_name] SYSNAME
     , [volume_name] SYSNAME  
     , [volume_device_id] SYSNAME
     , [Growth]  REAL
     , [GrowthType] SMALLINT
     , [MaxSize]  REAL
     , [Name] NVARCHAR(128)
     , [Size] REAL
     , [UsedSpace]  REAL
     , [FileName] NVARCHAR(260)
     , [VolumeFreeSpace] BIGINT
     , [available_space] REAL
);
GO


-- Stub for MDW object [snapshots].[sysutility_ucp_logfiles]
IF OBJECT_ID(N'[dbo].[sysutility_ucp_logfiles_stub]') IS NOT NULL
BEGIN
   RAISERROR('Dropping table [dbo].[sysutility_ucp_logfiles_stub]', 0, 1) WITH NOWAIT;
   DROP TABLE [dbo].[sysutility_ucp_logfiles_stub];
END;
GO

RAISERROR ('Creating table [dbo].[sysutility_ucp_logfiles_stub]', 0, 1) WITH NOWAIT;
CREATE TABLE [dbo].[sysutility_ucp_logfiles_stub]
(
     [urn] NVARCHAR(1500)
     , [powershell_path]  NVARCHAR(MAX)
     , [processing_time] DATETIMEOFFSET(7)
     , [batch_time] DATETIMEOFFSET(7)
     , [server_instance_name]    SYSNAME
     , [database_name] SYSNAME
     , [parent_urn] NVARCHAR(780)
     , [physical_server_name] SYSNAME
     , [volume_name] SYSNAME
     , [volume_device_id] SYSNAME
     , [Growth]  REAL
     , [GrowthType] SMALLINT
     , [MaxSize]  REAL
     , [Name] NVARCHAR(128)
     , [Size] REAL
     , [UsedSpace]  REAL
     , [FileName] NVARCHAR(260)
     , [VolumeFreeSpace] BIGINT
     , [available_space] REAL
)
GO


-- Stub for MDW object [snapshots].[sysutility_ucp_cpu_utilization]
IF OBJECT_ID(N'[dbo].[sysutility_ucp_cpu_utilization_stub]') IS NOT NULL
BEGIN
   RAISERROR('Dropping table [dbo].[sysutility_ucp_cpu_utilization_stub]', 0, 1) WITH NOWAIT;
   DROP TABLE [dbo].[sysutility_ucp_cpu_utilization_stub];
END;
GO
RAISERROR ('Creating table [dbo].[sysutility_ucp_cpu_utilization_stub]', 0, 1) WITH NOWAIT;
CREATE TABLE [dbo].[sysutility_ucp_cpu_utilization_stub]
(
       [processing_time] DATETIMEOFFSET(7),
       [aggregation_type] TINYINT NOT NULL,
       [object_type]      TINYINT NOT NULL, 
       
       -- Dimension keys
       [physical_server_name] SYSNAME DEFAULT '',
       [server_instance_name] SYSNAME DEFAULT '',
       [database_name]        SYSNAME DEFAULT '',
      
       -- The actual measure columns.
       percent_total_cpu_utilization REAL

)
GO

-- Stub for MDW object [snapshots].[sysutility_ucp_logfiles]
IF OBJECT_ID(N'[dbo].[sysutility_ucp_space_utilization_stub]') IS NOT NULL
BEGIN
   RAISERROR('Dropping table [dbo].[sysutility_ucp_space_utilization_stub]', 0, 1) WITH NOWAIT;
   DROP TABLE [dbo].[sysutility_ucp_space_utilization_stub];
END;
GO
RAISERROR ('Creating table [dbo].[sysutility_ucp_space_utilization_stub]', 0, 1) WITH NOWAIT;
CREATE TABLE [dbo].[sysutility_ucp_space_utilization_stub]
(
       [processing_time] DATETIMEOFFSET(7) NOT NULL,
       [aggregation_type] TINYINT NOT NULL,
       [object_type]      TINYINT NOT NULL, 
          
       -- The dimension columns
       [virtual_server_name] SYSNAME DEFAULT '',
       [server_instance_name] SYSNAME DEFAULT '',
       [volume_device_id]          SYSNAME DEFAULT '',
       [database_name]        SYSNAME DEFAULT '',
       [filegroup_name]       SYSNAME DEFAULT '',
       [dbfile_name]          SYSNAME DEFAULT '',
      
       [used_space_bytes]      REAL,
       [allocated_space_bytes] REAL,
       [total_space_bytes]     REAL,
       [available_space_bytes] REAL
)
GO

------ END UTILITY MDW STUB OBJECTS ------



/********************************************************************
Procedure [sp_sysutility_ucp_recreate_synonym_internal]
   Description: Helper proc to create/recreate a synonym
********************************************************************/
IF OBJECT_ID(N'[dbo].[sp_sysutility_ucp_recreate_synonym_internal]') IS NOT NULL
BEGIN
   RAISERROR('Dropping [dbo].[sp_sysutility_ucp_recreate_synonym_internal] procedure', 0, 1)  WITH NOWAIT;
   DROP PROCEDURE [dbo].[sp_sysutility_ucp_recreate_synonym_internal];
END
GO

RAISERROR('Creating [dbo].[sp_sysutility_ucp_recreate_synonym_internal] procedure', 0, 1)  WITH NOWAIT;
GO

CREATE PROCEDURE [dbo].[sp_sysutility_ucp_recreate_synonym_internal]
   @synonym_name sysname, @database_name sysname, @schema_name sysname, @object_name sysname
WITH EXECUTE AS CALLER
AS
BEGIN
   DECLARE @null_column nvarchar(100)
   SET @null_column = NULL

   IF (@synonym_name IS NULL OR @synonym_name = N'')
        SET @null_column = '@synonym_name'
   ELSE IF (@object_name IS NULL OR @object_name = N'')
        SET @null_column = '@object_name'

   IF @null_column IS NOT NULL
   BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_sysutility_ucp_recreate_synonym')
        RETURN(1)
   END

   IF  EXISTS (SELECT name FROM sys.objects WHERE object_id = OBJECT_ID(@synonym_name) )
   BEGIN
      DECLARE @drop_statement nvarchar(600)
      SET @drop_statement = N'DROP SYNONYM [dbo].' + QUOTENAME(@synonym_name)
      RAISERROR ('Executing: %s', 0, 1, @drop_statement) WITH NOWAIT;
      EXEC  sp_executesql @drop_statement
   END

   DECLARE @full_object_name nvarchar(776) = QUOTENAME(@database_name) + '.' + QUOTENAME(@schema_name) + '.' + QUOTENAME(@object_name)
   DECLARE @create_statement nvarchar(1060)
   SET @create_statement = N'CREATE SYNONYM [dbo].' + QUOTENAME(@synonym_name) + ' FOR ' + @full_object_name
   RAISERROR ('Executing: %s', 0, 1, @create_statement) WITH NOWAIT;
   EXEC  sp_executesql @create_statement
END
GO


/********************************************************************
Procedure [sp_sysutility_ucp_initialize_mdw]
   Description: This procedure will create the Utility synonyms.  This proc is called 
   by the Utility OM as part of the Create UCP process. It is also called whenever 
   instmsdb.sql is executed. If the instance is a UCP and if sysutility_mdw exists, the 
   synonyms will be created to reference objects in the Utility MDW database.  
   Otherwise, the synonyms will reference stub objects in msdb.  This allows the 
   Utility msdb objects to be created even when sysutility_mdw does not exist. 

   Note that this proc requires the 'Agent XPs' sp_configure setting to be enabled. 
   During upgrade, this setting will need to be manually enabled, since upgrade scripts 
   are executed while Agent is stopped. 

   Parameters: 
      @mdw_database_name - Name of the Utility MDW database ('sysutility_mdw')
      @require_mdw - If set to 1, the sysutility_mdw database must exist or an error is 
         thrown.  When this procedure is executed within instmsdb.sql, the param is set 
         to 0 so that it tolerates a missing MDW db.  When the procedure is executed by 
         the Utility OM, this parameter is not passed (and defaults to 1), so the MDW 
         database must exist.  
      @force_stub_use - If set to 1 (default is 0), the msdb wrapper views will always be 
         redirected to the msdb stub tables, even if the instance is a UCP. This is set to 
         1 when the proc is run by instmsdb.sql, to solve an upgrade issue: setup upgrades 
         the msdb schema before upgrading MDW schema. The new msdb view definitions could 
         reference new columns that have not yet been added to the MDW tables (because MDW 
         has not yet been upgraded by setup).  Setting @force_stub_use to 1 allows 
         instmsdb.sql to update the msdb wrapper views before MDW has been upgraded. The 
         procedure will be executed again after instmdw.sql runs (in 
         post_upgrade_ucp_cmdw.sql), and that execution will redirect the synonyms to 
         MDW, if the instance is a UCP. 
      @refresh_views - If set to 1 (default) the msdb wrapper views will be updated so 
         that view metadata reflects any changes to the schema of referenced tables. 
         @refresh_views is always set to 0 when this proc is executed by instmsdb.sql. 
         This is because the final execution of the proc in instmsdb.sql may have pointed 
         the (upgraded) msdb wrapper views to MDW tables that have not yet been upgraded, 
         and may lack columns that are referenced by the msdb wrapper views. If the 
         instance is a UCP, the views will be refreshed when the proc is run following 
         MDW upgrade. 
********************************************************************/
IF OBJECT_ID ('[dbo].[sp_sysutility_ucp_initialize_mdw]') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_ucp_initialize_mdw]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE [dbo].[sp_sysutility_ucp_initialize_mdw]
END;
GO

RAISERROR ('Creating procedure [dbo].[sp_sysutility_ucp_initialize_mdw]', 0, 1) WITH NOWAIT;
GO
 
CREATE PROCEDURE [dbo].[sp_sysutility_ucp_initialize_mdw]
   @mdw_database_name   SYSNAME, 
   @require_mdw         BIT = 1, 
   @force_stub_use      BIT = 0, 
   @refresh_views       BIT = 1
WITH EXECUTE AS OWNER
AS
BEGIN

   -- Check if @mdw_database_name is NULL or empty
   IF (@mdw_database_name IS NULL OR @mdw_database_name = N'')
   BEGIN
       RAISERROR(14043, -1, -1, 'mdw_database_name', 'sp_sysutility_ucp_initialize_mdw')
       RETURN(1)
   END


   IF (@require_mdw = 1) AND NOT EXISTS (SELECT * FROM master.dbo.sysdatabases WHERE name = @mdw_database_name)
   BEGIN
        RAISERROR(37002, -1, -1, @mdw_database_name)
        RETURN(1)
   END

   DECLARE @database sysname;
   DECLARE @schema sysname;
   DECLARE @is_ucp bit;

   -- If the sysutility_mdw database has been installed and the instance appears to be a UCP, we should 
   -- point the synonyms at the MDW objects.  Otherwise, the synonyms should reference the stub objects 
   -- (with a "_stub" suffix) that we just created.  Note that during UCP creation, this proc is called 
   -- at an interim step before fn_sysutility_get_is_instance_ucp returns 1.  However, @require_mdw will 
   -- be set to 1 in this case, telling us that we should redirect the synonyms to the MDW db even though . 
   -- the instance is not (yet) completely set up as a UCP. 
   IF (DB_ID (@mdw_database_name) IS NOT NULL) 
      AND ((@require_mdw = 1) OR (dbo.fn_sysutility_get_is_instance_ucp() = 1))
      AND (@force_stub_use = 0)
   BEGIN
       -- This instance is a UCP; synonyms should reference objects in sysutility_mdw
       SET @database = @mdw_database_name;
       SET @schema = 'sysutility_ucp_core';
      
       -- Dimensions
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_computers', @database, @schema, 'latest_computers';
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_volumes', @database, @schema, 'latest_volumes';
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_dacs', @database, @schema, 'latest_dacs';	   
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_smo_servers', @database, @schema, 'latest_smo_servers';
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_databases', @database, @schema, 'latest_databases';
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_filegroups', @database, @schema, 'latest_filegroups';
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_datafiles', @database, @schema, 'latest_datafiles';
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_logfiles', @database, @schema, 'latest_logfiles';

       -- Measures
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_cpu_utilization', @database, @schema, 'cpu_utilization';
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_space_utilization', @database, @schema, 'space_utilization';

      -- Now that msdb is set up, call a setup proc in MDW to do any runtime initialization that is 
      -- needed in that database.  Only exec the proc if it exists -- it won't exist yet when instmsdb.sql 
      -- is run on upgrade from CTP3 to RTM, because the MDW initialization proc was added post-CTP3 and 
      -- upgrade executes instmsdb.sql prior to instmdw.sql.  This proc will be re-executed by the post-upgrade 
      -- script post_upgrade_ucp_cmdw.sql, and at that time the MDW proc will have been created. 
      DECLARE @sql nvarchar(max);
      DECLARE @mdw_proc_name nvarchar(max);
      SET @mdw_proc_name = QUOTENAME(@mdw_database_name) + '.sysutility_ucp_core.sp_initialize_mdw_internal';
      SET @sql = 'EXEC ' + @mdw_proc_name;
      IF OBJECT_ID (@mdw_proc_name) IS NOT NULL 
      BEGIN
         RAISERROR ('Executing %s', 0, 1, @mdw_proc_name) WITH NOWAIT;
         EXEC (@sql);
      END
      ELSE BEGIN
         RAISERROR ('Skipping execution of %s', 0, 1, @mdw_proc_name) WITH NOWAIT;
      END;
                 
   END
   ELSE BEGIN
       -- This instance is not a UCP; synonyms should reference msdb stub objects
       SET @database = 'msdb';
       SET @schema = 'dbo';
               
       -- Dimensions
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_computers', @database, @schema, 'sysutility_ucp_computers_stub';
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_volumes', @database, @schema, 'sysutility_ucp_volumes_stub';
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_dacs', @database, @schema, 'sysutility_ucp_dacs_stub';
       
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_smo_servers', @database, @schema, 'sysutility_ucp_smo_servers_stub';
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_databases', @database, @schema, 'sysutility_ucp_databases_stub';
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_filegroups', @database, @schema, 'sysutility_ucp_filegroups_stub';
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_datafiles', @database, @schema, 'sysutility_ucp_datafiles_stub';
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_logfiles', @database, @schema, 'sysutility_ucp_logfiles_stub';
       
       -- Measures
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_cpu_utilization', @database, @schema, 'sysutility_ucp_cpu_utilization_stub';
       EXEC dbo.sp_sysutility_ucp_recreate_synonym_internal N'syn_sysutility_ucp_space_utilization', @database, @schema, 'sysutility_ucp_space_utilization_stub';		   
                   
   END;

   IF (@refresh_views = 1)
   BEGIN
       -- Refresh the msdb wrapper views to ensure that the view metadata matches the underlying table metadata. 
       -- This is necessary for two reasons: 
       --  a) When this procecure is executed by the Create UCP process, it may change the structure of the tables 
       --     that the msdb wrapper views reference, by redirecting the synonyms from the msdb stub tables to 
       --     different tables in MDW.  The refresh ensures that the view metadata matches that of the new 
       --     referenced tables. 
       --  b) The proc is also executed after msdb and MDW schema upgrade.  In this case, the MDW upgrade may have 
       --     changed the MDW table schema even if the synonyms weren't redirected.  
       RAISERROR ('Refreshing msdb wrapper views', 0, 1) WITH NOWAIT;
       EXEC dbo.sp_refreshview N'dbo.sysutility_ucp_computers';
       EXEC dbo.sp_refreshview N'dbo.sysutility_ucp_volumes';
       EXEC dbo.sp_refreshview N'dbo.sysutility_ucp_instances';
       EXEC dbo.sp_refreshview N'dbo.sysutility_ucp_databases';
       EXEC dbo.sp_refreshview N'dbo.sysutility_ucp_filegroups';
       EXEC dbo.sp_refreshview N'dbo.sysutility_ucp_datafiles';
       EXEC dbo.sp_refreshview N'dbo.sysutility_ucp_logfiles';
       EXEC dbo.sp_refreshview N'dbo.sysutility_ucp_utility_space_utilization';
   END;
END;
GO


-- Create the Utility synonyms.  For details, see comments prior to "BEGIN UTILITY MDW STUB OBJECTS", 
-- above. We need to create the synonyms at this point so that we can create the Utility msdb wrapper 
-- views that reference the synonyms. This execution uses @force_stub_use=1 and @refresh_views=0; see  
-- the documentation for these parameters in the proc header. 
EXEC msdb.dbo.sp_sysutility_ucp_initialize_mdw 
    @mdw_database_name = 'sysutility_mdw', 
    @require_mdw = 0, 
    @force_stub_use = 1, 
    @refresh_views = 0;
GO


---------------------------------------------------------------------
-- SP to setup the managed instance proxy for a network account
---------------------------------------------------------------------
IF OBJECT_ID ('dbo.sp_sysutility_mi_configure_proxy_account') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_mi_configure_proxy_account]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE [dbo].[sp_sysutility_mi_configure_proxy_account]
END;
GO

RAISERROR ('Creating procedure [dbo].[sp_sysutility_mi_configure_proxy_account]', 0, 1) WITH NOWAIT;
GO


CREATE PROCEDURE [dbo].[sp_sysutility_mi_configure_proxy_account]
   @proxy_name sysname,
   @credential_name sysname,
   @network_account sysname,
   @password sysname
AS
BEGIN
   DECLARE @retval INT
   DECLARE @null_column    sysname
   DECLARE @expression NVARCHAR(MAX) = N''
   DECLARE @network_account_sid varbinary(85)
   
   SET @null_column = NULL

   IF (@proxy_name IS NULL OR @proxy_name = N'')
       SET @null_column = '@proxy_name'
   ELSE IF (@credential_name IS NULL OR @credential_name = N'')
       SET @null_column = '@credential_name'
   ELSE IF (@network_account IS NULL OR @network_account = N'')
       SET @null_column = '@network_account'
   ELSE IF (@password IS NULL OR @password = N'')
       SET @null_column = '@password'

   IF @null_column IS NOT NULL
   BEGIN
       RAISERROR(14043, -1, -1, @null_column, 'sp_sysutility_mi_configure_proxy_account')
       RETURN(1)
   END

   SET @network_account_sid = SUSER_SID(@network_account, 0) -- case insensensitive lookup
   SET @network_account = SUSER_SNAME(@network_account_sid)  -- get the caseing of the user that the server recognizes
   IF NOT EXISTS (SELECT sid FROM msdb.sys.syslogins WHERE sid = @network_account_sid)
   BEGIN
        SET @expression = N'CREATE LOGIN '+ QUOTENAME(@network_account) +' FROM WINDOWS;'
        EXEC sp_executesql @expression
   END 
   
   DECLARE @create_credential nvarchar(4000)
   DECLARE @print_credential nvarchar(4000)
   
   IF EXISTS(SELECT * FROM master.sys.credentials WHERE name = @credential_name)
   BEGIN
      set @create_credential = 'DROP CREDENTIAL ' + QUOTENAME(@credential_name)
      RAISERROR (@create_credential, 0, 1) WITH NOWAIT;
      EXEC sp_executesql @create_credential
   END


   set @create_credential = 'CREATE CREDENTIAL ' + QUOTENAME(@credential_name) + ' WITH IDENTITY=N' + QUOTENAME(@network_account, '''') + ', SECRET=N' + QUOTENAME(@password, '''')
   set @print_credential = 'CREATE CREDENTIAL ' + QUOTENAME(@credential_name) + ' WITH IDENTITY=N' + QUOTENAME(@network_account, '''')
   RAISERROR (@print_credential, 0, 1) WITH NOWAIT;
   EXEC sp_executesql @create_credential

   
   IF EXISTS(SELECT * FROM dbo.sysproxies WHERE (name = @proxy_name))
   BEGIN
      EXEC dbo.sp_delete_proxy @proxy_name=@proxy_name
   END
   
   EXEC dbo.sp_add_proxy @proxy_name=@proxy_name, @credential_name=@credential_name, @enabled=1

   EXEC dbo.sp_grant_login_to_proxy @msdb_role=N'dc_admin', @proxy_name=@proxy_name

   -- Grant the cmdexec subsystem to the proxy.  This is the subsystem that DC uses to perform upload.
   EXEC dbo.sp_grant_proxy_to_subsystem @proxy_name=@proxy_name, @subsystem_id=3
      
   -- Allow the account to see the table schemas.  This is because DC checks to make sure the mdw
   -- schema matches the schema on the client.   

   -- One cannot grant privledges to oneself.
   -- Since the caller is creating users by virtue of this sproc, it already can view server state
   -- So, only grant veiw server state if the network_account is not the caller
   IF( SUSER_SID() <> @network_account_sid )
   BEGIN       
       -- GRANT VIEW SERVER STATE requires the expression to be executed in master.
       SET @expression = N'use master; GRANT VIEW SERVER STATE TO ' + QUOTENAME(@network_account)
       RAISERROR (@expression, 0, 1) WITH NOWAIT;
       EXEC sp_executesql @expression
   END
       
   -- Add a user to the msdb database so that the proxy can be associated with the appropriate roles.
   
   -- The user might already be associated with a user in msdb.  If so, find that user name so that
   -- roles can be assigned to it.
   DECLARE @user_name SYSNAME = (SELECT name FROM msdb.sys.database_principals WHERE sid = @network_account_sid)
   
   -- The "special principles" are not allowed to have roles added to them.
   -- The database Users in the "special" category are dbo, sys, and INFORMATION_SCHEMA.  
   -- dbo is the only one that can have logins associated with it.
   -- The following only checks dbo because the network_account has an associated login.
   -- The else case (the user is msdb dbo), then they are effectively sysadmin in msdb and have 
   -- the required permissions for the proxy, and there is not need to grant roles anyway.
   IF ((@user_name IS NULL) OR (@user_name <> N'dbo'))
   BEGIN
        
        -- This login doesn't have a user associated with it.
        -- Go ahead and create a user for it in msdb
       IF( @user_name IS NULL )
       BEGIN
          SET @user_name = @network_account
          SET @expression = N'CREATE USER ' + QUOTENAME(@user_name)
          EXEC sp_executesql @expression 
       END; 
       
       -- Allow the user to view the msdb database metadata.  This allows DC (and ssis) to verify
       -- the proxy's privledges.
       -- One cannot grant privledges to oneself.
       IF( SUSER_SID() <> @network_account_sid )
       BEGIN
           SET @expression = N'GRANT VIEW DEFINITION TO ' + QUOTENAME(@network_account)
           RAISERROR (@expression, 0, 1) WITH NOWAIT;
           EXEC sp_executesql @expression
       END

       -- Adding roles is idempotent, so go ahead and add them.
       
       -- This role necessary for the proxy
       EXEC sp_addrolemember @rolename=N'dc_proxy', @membername=@user_name

       -- It needs to read the Utility tables.  It requires execute permissions on the dac performance sp, so writer role is required.
       EXEC sp_addrolemember @rolename=N'UtilityIMRWriter', @membername=@user_name
   END

END
GO


---------------------------------------------------------------------
-- SP to provision the proxy for a network account on the CMR
---------------------------------------------------------------------
IF OBJECT_ID ('dbo.sp_sysutility_ucp_provision_proxy_account') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_ucp_provision_proxy_account]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE [dbo].[sp_sysutility_ucp_provision_proxy_account]
END;
GO

RAISERROR ('Creating procedure [dbo].[sp_sysutility_ucp_provision_proxy_account]', 0, 1) WITH NOWAIT;
GO


CREATE PROCEDURE [dbo].[sp_sysutility_ucp_provision_proxy_account]
   @network_account sysname,
   @mdw_database_name sysname
AS
BEGIN
   DECLARE @retval INT
   DECLARE @null_column    sysname
   DECLARE @expression NVARCHAR(MAX) = N''
   DECLARE @network_account_sid varbinary(85)
    
   SET @null_column = NULL

   IF (@network_account IS NULL OR @network_account = N'')
       SET @null_column = '@network_account'
   ELSE IF (@mdw_database_name IS NULL OR @mdw_database_name = N'')
       SET @null_column = '@mdw_database_name'

   IF @null_column IS NOT NULL
   BEGIN
       RAISERROR(14043, -1, -1, @null_column, 'sp_sysutility_ucp_provision_proxy_account')
       RETURN(1)
   END

   IF NOT EXISTS (SELECT * FROM master.dbo.sysdatabases WHERE name = @mdw_database_name)
   BEGIN
        RAISERROR(37002, -1, -1, @mdw_database_name)
        RETURN(1)
   END

   SET @network_account_sid = SUSER_SID(@network_account, 0) -- case insensensitive lookup
   SET @network_account = SUSER_SNAME(@network_account_sid)  -- get the caseing of the user that the server recognizes
   IF NOT EXISTS (SELECT sid FROM msdb.sys.syslogins WHERE sid = @network_account_sid)
   BEGIN
        SET @expression = N'USE msdb; CREATE LOGIN '+ QUOTENAME(@network_account) + ' FROM WINDOWS;'
        EXEC sp_executesql @expression
   END 

   DECLARE @is_sysadmin INT
   SELECT @is_sysadmin = 0
   EXECUTE msdb.dbo.sp_sqlagent_has_server_access @login_name = @network_account, @is_sysadmin_member = @is_sysadmin OUTPUT

   IF (@is_sysadmin = 0)
   BEGIN
      DECLARE @print_expression nvarchar(500)
      SET @print_expression = @network_account + ' is NOT a SQL sysadmin'
      RAISERROR (@print_expression, 0, 1) WITH NOWAIT;

      IF NOT EXISTS(SELECT * FROM msdb.sys.database_principals WHERE sid = @network_account_sid)
      BEGIN
         SET @expression = N'USE msdb; CREATE USER ' + QUOTENAME(@network_account) +';'
         EXEC sp_executesql @expression    
      END;

      EXEC msdb.dbo.sp_addrolemember @rolename='dc_proxy', @membername=@network_account

      DECLARE @grant_expression nvarchar(4000)

      IF NOT EXISTS
      (SELECT name from master.sys.databases
       WHERE @network_account_sid = owner_sid
       AND database_id = DB_ID(@mdw_database_name))
      BEGIN
         set @grant_expression =
         'IF NOT EXISTS(SELECT * FROM ' + QUOTENAME(@mdw_database_name) +'.[sys].[database_principals] WHERE sid = SUSER_SID(' + QUOTENAME(@network_account, '''') +', 0))

         BEGIN
            RAISERROR (''Creating user ' + QUOTENAME(@network_account) + ' in ' + QUOTENAME(@mdw_database_name) + ''', 0, 1) WITH NOWAIT;
            USE ' + QUOTENAME(@mdw_database_name) + '; CREATE USER ' + QUOTENAME(@network_account) + ';
         END;

         RAISERROR (''Add to UtilityMDWWriter role'', 0, 1) WITH NOWAIT;
         EXEC ' + QUOTENAME(@mdw_database_name) + '.[dbo].[sp_addrolemember] @rolename=''UtilityMDWWriter'', @membername=' + QUOTENAME(@network_account) + ';'

         RAISERROR (@grant_expression, 0, 1) WITH NOWAIT;
         EXEC sp_executesql @grant_expression
      END

   END

END
GO



----------------------------------------------------------------------
-- SP to create the proxy account validator job
----------------------------------------------------------------------
IF OBJECT_ID ('dbo.sp_sysutility_mi_validate_proxy_account') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_mi_validate_proxy_account]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE [dbo].[sp_sysutility_mi_validate_proxy_account]
END;
GO

RAISERROR ('Creating procedure [dbo].[sp_sysutility_mi_validate_proxy_account]', 0, 1) WITH NOWAIT;
GO


CREATE PROCEDURE [dbo].[sp_sysutility_mi_validate_proxy_account]
   @proxy_name sysname,
   @credential_name sysname,
   @network_account sysname,
   @password sysname
AS
BEGIN
   DECLARE @retval INT
   DECLARE @null_column    sysname
    
   SET @null_column = NULL

   IF (@proxy_name IS NULL OR @proxy_name = N'')
       SET @null_column = '@proxy_name'
   ELSE IF (@credential_name IS NULL OR @credential_name = N'')
       SET @null_column = '@credential_name'
   ELSE IF (@network_account IS NULL OR @network_account = N'')
       SET @null_column = '@network_account'
   ELSE IF (@password IS NULL OR @password = N'')
       SET @null_column = '@password'

   IF @null_column IS NOT NULL
   BEGIN
       RAISERROR(14043, -1, -1, @null_column, 'sp_sysutility_mi_validate_proxy_account')
       RETURN(1)
   END


   DECLARE @instance_name  nvarchar(128)
   SET @instance_name = ISNULL(CONVERT(nvarchar(128), SERVERPROPERTY('InstanceName')), N'MSSQLSERVER')

   DECLARE @job_name sysname
   DECLARE @job_id uniqueidentifier        
   DECLARE @description nvarchar(512)

   -- Delete the job if it already exists
   SET @job_name = N'sysutility_check_proxy_credentials'
   WHILE (EXISTS (SELECT * FROM msdb.dbo.sysjobs WHERE name = @job_name))
   BEGIN
      EXEC sp_delete_job @job_name=@job_name
   END



   DECLARE @credential_statement nvarchar(4000)
   DECLARE @print_credential nvarchar(4000)
   
   IF EXISTS(select * from master.sys.credentials where name = @credential_name)
   BEGIN
      set @credential_statement = 'DROP CREDENTIAL ' + QUOTENAME(@credential_name)
      RAISERROR (@credential_statement, 0, 1) WITH NOWAIT;
      EXEC sp_executesql @credential_statement
   END


   set @credential_statement = 'CREATE CREDENTIAL ' + QUOTENAME(@credential_name) + ' WITH IDENTITY=N' + QUOTENAME(@network_account, '''') + ', SECRET=N' + QUOTENAME(@password, '''')
   set @print_credential = 'CREATE CREDENTIAL ' + QUOTENAME(@credential_name) + ' WITH IDENTITY=N' + QUOTENAME(@network_account, '''')
   RAISERROR (@print_credential, 0, 1) WITH NOWAIT;
   EXEC sp_executesql @credential_statement

   
   IF EXISTS(SELECT * FROM dbo.sysproxies WHERE (name = @proxy_name))
   BEGIN
      EXEC dbo.sp_delete_proxy @proxy_name=@proxy_name
   END
   
   
   -- Create the proxy and grant it to the cmdExec subsytem
   EXEC dbo.sp_add_proxy @proxy_name=@proxy_name, @credential_name=@credential_name, @enabled=1

   EXEC dbo.sp_grant_proxy_to_subsystem @proxy_name=@proxy_name, @subsystem_id=3


   -- Create the job
	EXEC  msdb.dbo.sp_add_job @job_name=@job_name, 
			@enabled=1,
			@notify_level_eventlog=0, 
			@notify_level_email=2, 
			@notify_level_netsend=2, 
			@notify_level_page=2, 
			@delete_level=0, 
			@category_id=0,
			@job_id = @job_id OUTPUT

	DECLARE @server_name SYSNAME = CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))
	EXEC msdb.dbo.sp_add_jobserver @job_name=@job_name, @server_name = @server_name

   DECLARE @collection_step_command nvarchar(512)
   SET @collection_step_command = N'time /T'

   EXEC msdb.dbo.sp_add_jobstep 
          @job_id=@job_id, 
          @step_name=N'Validate proxy account', 
		    @step_id=1, 
		    @cmdexec_success_code=0, 
		    @on_fail_action=2, 
		    @on_fail_step_id=0,
		    @on_success_action=1, 
		    @retry_attempts=0, 
		    @retry_interval=0, 
		    @os_run_priority=0, 
		    @subsystem=N'CMDEXEC',
		    @command=@collection_step_command,
		    @proxy_name=@proxy_name,
		    @flags=16

END
GO

----------------------------------------------------------------------
-- A function that returns a powershell script which is used for 
-- ensuring that the WMI queries necessary for data collection in the
-- Utility work correctly.  The function is used for validation of the
-- Managed Instance and is run within a job step.  It is exposed as a 
-- to ease testing and troublshooting.
----------------------------------------------------------------------
IF  (OBJECT_ID(N'[dbo].[fn_sysutility_mi_get_validate_wmi_script]') IS NOT NULL)
BEGIN
   RAISERROR('Dropping [dbo].[fn_sysutility_mi_get_validate_wmi_script] function', 0, 1)  WITH NOWAIT;
   DROP FUNCTION [dbo].[fn_sysutility_mi_get_validate_wmi_script];
END
GO
RAISERROR('Creating [dbo].[fn_sysutility_mi_get_validate_wmi_script] function', 0, 1)  WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysutility_mi_get_validate_wmi_script]()
   RETURNS NVARCHAR(MAX)
AS
BEGIN     
   RETURN 
'# This script verifies that the following WMI objects are queriable
$objectsToValidate = "Win32_MountPoint", 
                "Win32_PerfRawData_PerfProc_Process", 
                "Win32_PerfRawData_PerfOS_Processor",
                "Win32_Processor",
                "Win32_Volume",
                "Win32_LogicalDisk"
                
# The errorHappend variable keeps track of whether any class failed the check
$errorHappened=$false

# The SQL Agent Powershell subsystem does not have an interactive host associated
# with it, thus standard Write-Host and other host-based cmdlets have no place
# to write to.  This knowledge is used to tell if the script is in an Agent
# or if it is running on a standard PowerShell host.
$isNotConsole = ($host.Name -ne "ConsoleHost")
function Get-IsAgentStep
{
   $global:isNotConsole
}

# Writing to the agent logs is easiest to achieve with [Console]::Error.WriteLine
# If the script is in Agent, write through the Console directly.  If the script
# is not in Agent (someone is using it to debug), then just output to the pipeline.
function Write-AgentLog($object)
{
     if(Get-IsAgentStep)
     {
        [Console]::Error.WriteLine($object)
     } 
     else 
     {
        $object
     }
}

# Query the given WMI object and report pass or fail on the object.
function Validate-WmiObject([string] $wmiObject)
{
   process
   {
      Write-AgentLog "#Running Command:"
      Write-AgentLog "Get-WmiObject $wmiObject | Out-Null"
      
      # Use ErrorVariable and ErrorAction SilentlyContinue so that all of the 
      # objects can be tested without stopping the script or having spurrious messages
      # in the Agent logs.  
      Get-WmiObject $wmiObject -ErrorVariable wmiError -ErrorAction SilentlyContinue | Out-Null
      
      # Check the error message and report pass or fail
      if($wmiError)
      {
         $global:errorHappened=$true
         Write-AgentLog "#Command FAILED. Exception : $wmiError"
      }
      else
      {
         Write-AgentLog "#Command PASSED."
      }

   }
}

# Validate all of the Wmi objects.  If any one of them fail, then
# report an error.
function Validate-AllWmiObjects
{
   $objectsToValidate | %{ 
      Validate-WmiObject $_ 
   }
   
   if($global:errorHappened)
   {
      Write-Error -ErrorAction Stop "One or more WMI classes failed the test"
   }
}

# Automatically check the status of the objects if the script is running in Agent
# Otherwise, allow the user to call the Validate functions interactively.
if(Get-IsAgentStep)
{ 
   Validate-AllWmiObjects
}
'
END
GO

----------------------------------------------------------------------
-- A sp that creates a job that ensures that WMI works appropreately for
-- Utility data collection.  
----------------------------------------------------------------------
IF OBJECT_ID ('dbo.sp_sysutility_mi_create_job_validate_wmi') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_mi_create_job_validate_wmi]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE [dbo].[sp_sysutility_mi_create_job_validate_wmi]
END;
GO

RAISERROR ('Creating procedure [dbo].[sp_sysutility_mi_create_job_validate_wmi]', 0, 1) WITH NOWAIT;
GO


CREATE PROCEDURE [dbo].[sp_sysutility_mi_create_job_validate_wmi]
AS
BEGIN

   DECLARE @job_name sysname = N'sysutility_mi_validate_wmi'
   DECLARE @job_id uniqueidentifier
   DECLARE @description nvarchar(512) = N''
   DECLARE @psScript NVARCHAR(MAX) = (SELECT [dbo].[fn_sysutility_mi_get_validate_wmi_script]());

   -- Delete the job if it already exists
   WHILE (EXISTS (SELECT * FROM msdb.dbo.sysjobs WHERE name = @job_name))
   BEGIN
      EXEC sp_delete_job @job_name=@job_name
   END

   -- Create the job
   EXEC  msdb.dbo.sp_add_job @job_name=@job_name, 
			@enabled=1,
			@notify_level_eventlog=0, 
			@notify_level_email=2, 
			@notify_level_netsend=2, 
			@notify_level_page=2, 
			@delete_level=0, 
			@category_id=0,
			@job_id = @job_id OUTPUT

   EXEC msdb.dbo.sp_add_jobserver @job_name=@job_name, @server_name = @@SERVERNAME

   -- Add the validation step
   EXEC msdb.dbo.sp_add_jobstep 
          @job_id=@job_id, 
          @step_name=N'Validate WMI configuration', 
          @step_id=1, 
          @cmdexec_success_code=0, 
          @on_fail_action=2, 
          @on_fail_step_id=0,
          @on_success_action=1, 
          @retry_attempts=0, 
          @retry_interval=0, 
          @os_run_priority=0, 
          @subsystem=N'Powershell',
          @command=@psScript

END
GO


------------------------------------------------------------------------
-- SP to create the job which creates the UtilityCacheDirectory
------------------------------------------------------------------------
IF OBJECT_ID ('dbo.sp_sysutility_mi_create_cache_directory') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_mi_create_cache_directory]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE [dbo].[sp_sysutility_mi_create_cache_directory]
END;
GO

RAISERROR ('Creating procedure [dbo].[sp_sysutility_mi_create_cache_directory]', 0, 1) WITH NOWAIT;
GO

CREATE PROCEDURE [dbo].[sp_sysutility_mi_create_cache_directory]
   @network_account sysname,
   @agent_service_account sysname
AS
BEGIN
    DECLARE @instance_name  nvarchar(128)
    DECLARE @null_column    sysname
    
    SET @null_column = NULL
    
    IF (@network_account IS NULL OR @network_account = N'')
       SET @null_column = '@network_account'
    ELSE IF (@agent_service_account IS NULL OR @agent_service_account = N'')
       SET @null_column = '@agent_service_account'
       
    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_sysutility_create_cache_directory_job')
        RETURN(1)
    END


    SET @instance_name = ISNULL(CONVERT(nvarchar(128), SERVERPROPERTY('InstanceName')), N'MSSQLSERVER')
 

    DECLARE @job_name sysname
    DECLARE @job_id uniqueidentifier        
    DECLARE @description nvarchar(512)

    DECLARE @cachepath nvarchar(2048);  -- SQL Eye reports that xp_instance_regread can truncate the cachepath
									    -- but xp_instance_regread doesn't handle LOB types to use nvarchar(MAX)

    EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent', N'WorkingDirectory', @cachepath OUTPUT;

    set @cachepath=@cachepath + '\UtilityDC'

    RAISERROR (@cachepath, 0, 1) WITH NOWAIT;

    -- create unique job name
    SET @job_name = N'sysutility_create_cache_directory'

    WHILE (EXISTS (SELECT * FROM msdb.dbo.sysjobs WHERE name = @job_name))
    BEGIN
       EXEC sp_delete_job @job_name=@job_name
    END
 

    EXEC  msdb.dbo.sp_add_job @job_name=@job_name, 
                @enabled=1,
                @notify_level_eventlog=0, 
                @notify_level_email=2, 
                @notify_level_netsend=2, 
                @notify_level_page=2, 
                @delete_level=0, 
                @category_id=0,
                @job_id = @job_id OUTPUT

    DECLARE @server_name SYSNAME = CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))      
    EXEC msdb.dbo.sp_add_jobserver @job_name=@job_name, @server_name = @server_name

    DECLARE @command nvarchar(MAX)
    SET @command = N'if exist "' + @cachepath + '"  rmdir /S /Q "' + @cachepath + '"'

    EXEC msdb.dbo.sp_add_jobstep 
          @job_id=@job_id, 
                @step_name=N'Delete existing cache directory', 
                @step_id=1, 
                @cmdexec_success_code=0, 
                @on_fail_action=2, 
                @on_fail_step_id=0,
                @on_success_action=3, 
                @retry_attempts=0, 
                @retry_interval=0, 
                @os_run_priority=0, 
                @subsystem=N'CMDEXEC',
                @command=@command, 
                @flags=16
 
    IF NOT (@network_account LIKE @agent_service_account)
    BEGIN
        -- If network_account (proxy) and agent_service_account are different, we shall ACL the cache
        RAISERROR ('network_account is different from agent_service_account', 0, 1) WITH NOWAIT;
        SET @command = N'md "' + @cachepath + '"'

        EXEC msdb.dbo.sp_add_jobstep 
                @job_id=@job_id, 
                @step_name=N'Create cache directory', 
                @step_id=2, 
                @cmdexec_success_code=0, 
                @on_fail_action=2, 
                @on_fail_step_id=0,
                @on_success_action=3, 
                @retry_attempts=0, 
                @retry_interval=0, 
                @os_run_priority=0, 
                @subsystem=N'CMDEXEC',
                @command=@command,
                @flags=16

        SET @command = N'cacls "' + @cachepath + '" /E /G ' + @network_account + ':C'

        EXEC msdb.dbo.sp_add_jobstep 
                @job_id=@job_id, 
                @step_name=N'ACL cache directory', 
                @step_id=3, 
                @cmdexec_success_code=0, 
                @on_fail_action=2, 
                @on_fail_step_id=0, 
                @on_success_action=1,
                @retry_attempts=0, 
                @retry_interval=0, 
                @os_run_priority=0, 
                @subsystem=N'CMDEXEC',
                @command=@command, 
                @flags=16
    END
    ELSE
    BEGIN
        -- If network_account (proxy) and agent_service_account are the same, then there is no need to ACL the cache
        -- as the account already has write access to it courtesy the agent service account provisioning.
        -- In this case explicit provisioning of cache with the account leads to error.
        RAISERROR ('network_account is the same as the agent_service_account', 0, 1) WITH NOWAIT;
        SET @command = N'md "' + @cachepath + '"'

        EXEC msdb.dbo.sp_add_jobstep 
                @job_id=@job_id, 
                @step_name=N'Create cache directory', 
                @step_id=2, 
                @cmdexec_success_code=0, 
                @on_fail_action=2, 
                @on_fail_step_id=0,
                @on_success_action=1, 
                @retry_attempts=0, 
                @retry_interval=0, 
                @os_run_priority=0, 
                @subsystem=N'CMDEXEC',
                @command=@command,
                @flags=16

    END

END
GO

---------------------------------------------------------------------
-- Table to hold information about a managed instance and its
-- configuration
---------------------------------------------------------------------

IF NOT EXISTS (SELECT name FROM [sys].[objects] WHERE object_id = OBJECT_ID(N'[dbo].[sysutility_mi_configuration_internal]') AND type in (N'U'))
BEGIN
RAISERROR('Creating [dbo].[sysutility_mi_configuration_internal] table', 0, 1)  WITH NOWAIT;
CREATE TABLE [dbo].[sysutility_mi_configuration_internal]
(
   -- configuration_id + pk enforces a single row in the table
   configuration_id              AS (1), 
   
   ucp_instance_name             SYSNAME NULL, 
   mdw_database_name             SYSNAME NULL,
   
   CONSTRAINT pk_sysutility_mi_configuration_internal_configuration_id PRIMARY KEY (configuration_id)
   -- this table should only contain one row
);
END
GO

IF (NOT OBJECT_ID('[dbo].[sysutility_mi_configuration]', 'V') IS NULL)
BEGIN
    RAISERROR('Dropping [dbo].[sysutility_mi_configuration] view', 0, 1)  WITH NOWAIT;
    DROP VIEW [dbo].[sysutility_mi_configuration]
END
GO

RAISERROR('Creating [dbo].[sysutility_mi_configuration] view', 0, 1)  WITH NOWAIT;
GO
CREATE VIEW [dbo].[sysutility_mi_configuration]
AS
    SELECT config.ucp_instance_name, config.mdw_database_name, t.upload_schema_version
    FROM 
    -- The upload_schema_version represents the contract between the UCP and MI for data upload
    -- Change this value when a breaking change with a (downlevel) UCP may be introduced in the MI
    -- upload code.
    (SELECT 100 AS upload_schema_version) t
    LEFT OUTER JOIN
    [dbo].[sysutility_mi_configuration_internal] config
    ON 1=1
GO

----------------------------------------------------------
-- Stored PROCs to add/removed/check the UCP configuration
----------------------------------------------------------
IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[sp_sysutility_mi_add_ucp_registration]') AND type in (N'P', N'PC'))
BEGIN
   RAISERROR('Dropping [dbo].[sp_sysutility_mi_add_ucp_registration] procedure', 0, 1)  WITH NOWAIT;
   DROP PROCEDURE [dbo].[sp_sysutility_mi_add_ucp_registration];
END
GO
RAISERROR('Creating [dbo].[sp_sysutility_mi_add_ucp_registration] procedure', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysutility_mi_add_ucp_registration]
    @ucp_instance_name SYSNAME,
    @mdw_database_name SYSNAME
WITH EXECUTE AS OWNER
AS
BEGIN
   
   DECLARE @null_column SYSNAME = NULL
   SET NOCOUNT ON;
   SET XACT_ABORT ON;

   IF (@ucp_instance_name IS NULL)
     SET @null_column = '@ucp_instance_name'
   ELSE IF (@mdw_database_name IS NULL)
     SET @null_column = '@mdw_database_name'

   IF @null_column IS NOT NULL
   BEGIN
     RAISERROR(14043, -1, -1, @null_column, 'sp_sysutility_mi_add_ucp_registration')
     RETURN(1)
   END
    
   BEGIN TRANSACTION;
    
     IF EXISTS (SELECT * FROM [msdb].[dbo].[sysutility_mi_configuration_internal])
    BEGIN
      UPDATE [msdb].[dbo].[sysutility_mi_configuration_internal]
      SET
         ucp_instance_name          = @ucp_instance_name,
         mdw_database_name          = @mdw_database_name
    END
    ELSE
    BEGIN
         INSERT INTO [msdb].[dbo].[sysutility_mi_configuration_internal] (ucp_instance_name, mdw_database_name)
         VALUES (@ucp_instance_name, @mdw_database_name);
    END          
    
   COMMIT TRANSACTION;

   ---- Add the MiUcpName registry key values.
   ---- If the value is already present this XP will update them.
   EXEC master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                        N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Utility',
                                        N'MiUcpName',
                                        N'REG_SZ',
                                        @ucp_instance_name
                                         
END
GO


IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[sp_sysutility_mi_remove_ucp_registration]') AND type in (N'P', N'PC'))
BEGIN
   RAISERROR('Dropping [dbo].[sp_sysutility_mi_remove_ucp_registration] procedure', 0, 1)  WITH NOWAIT;
   DROP PROCEDURE [dbo].[sp_sysutility_mi_remove_ucp_registration];
END
GO
RAISERROR('Creating [dbo].[sp_sysutility_mi_remove_ucp_registration] procedure', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysutility_mi_remove_ucp_registration]
WITH EXECUTE AS OWNER
AS
BEGIN
   SET NOCOUNT ON;
   SET XACT_ABORT ON;
 
   BEGIN TRANSACTION;
    
    IF EXISTS (SELECT * FROM [msdb].[dbo].[sysutility_mi_configuration_internal])
    BEGIN
      UPDATE [msdb].[dbo].[sysutility_mi_configuration_internal]
      SET
         ucp_instance_name          = NULL,
         mdw_database_name          = NULL
    END
    ELSE
    BEGIN
         INSERT INTO [msdb].[dbo].[sysutility_mi_configuration_internal] (ucp_instance_name, mdw_database_name)
         VALUES (NULL, NULL);
    END     

   COMMIT TRANSACTION;

   ---- If the above part fails it will not execute the following XPs.
   ---- The following XP calls are not transactional, so they are put outside
   ---- the transaction.
   ---- Remove the MiUcpName registry key if it is present
   DECLARE @mi_ucp_name nvarchar(1024)
   EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                       N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Utility',
                                       N'MiUcpName',
                                       @mi_ucp_name OUTPUT

   IF @mi_ucp_name IS NOT NULL
   BEGIN
       EXEC master.dbo.xp_instance_regdeletevalue N'HKEY_LOCAL_MACHINE',
                                                  N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Utility',
                                                  N'MiUcpName'
   END

   ---- Remove the registry key if this instance is NOT a UCP.
   ---- If this instance is a UCP we cannot remove the key entirely as
   ---- the version number is still stored under the key.
   IF (msdb.dbo.fn_sysutility_get_is_instance_ucp() = 0)
   BEGIN
       EXEC master.dbo.xp_instance_regdeletekey N'HKEY_LOCAL_MACHINE',
                                                  N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Utility'
   END
   
END
GO


IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[fn_sysutility_ucp_get_instance_is_mi]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
BEGIN
   RAISERROR('Dropping [dbo].[fn_sysutility_ucp_get_instance_is_mi] function', 0, 1)  WITH NOWAIT;
   DROP FUNCTION [dbo].[fn_sysutility_ucp_get_instance_is_mi];
END
GO
RAISERROR('Creating [dbo].[fn_sysutility_ucp_get_instance_is_mi] function', 0, 1)  WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysutility_ucp_get_instance_is_mi]()
RETURNS BIT
WITH EXECUTE AS OWNER
AS
BEGIN
    DECLARE @status BIT = (SELECT 
                            CASE  
                            WHEN ((ucp_instance_name IS NOT NULL) AND (mdw_database_name IS NOT NULL)) THEN CAST(1 AS BIT)
                            ELSE CAST(0 AS BIT)
                            END
                           FROM sysutility_mi_configuration_internal config)
    RETURN ISNULL(@status,0)
END	
GO



----------------------------------------------------------------------------------
-- Table sysutility_mi_dac_execution_statistics_internal
--    Staging table used to store execution statistics for DACs on the local instance.  This table stores 
--    relatively transient data; in the worst case, dropping and recreating it will result in the loss of 
--    up to one upload interval (15 minutes) of DAC execution statistics.  The data in this table is created 
--    and updated by sp_sysutility_mi_collect_dac_execution_statistics_internal, which runs every 15 seconds 
--    on each MI.  The data is harvested every 15 min by sp_sysutility_mi_get_dac_execution_statistics_internal, 
--    then moved to the UCP's CMDW database by the Utility collection set. 
----------------------------------------------------------------------------------
IF OBJECT_ID ('dbo.sysutility_mi_dac_execution_statistics_internal', 'U') IS NOT NULL
BEGIN
   RAISERROR('Dropping [dbo].[sysutility_mi_dac_execution_statistics_internal] table', 0, 1)  WITH NOWAIT;
   DROP TABLE [dbo].[sysutility_mi_dac_execution_statistics_internal];
END
GO
RAISERROR('Creating [dbo].[sysutility_mi_dac_execution_statistics_internal] table', 0, 1)  WITH NOWAIT;
CREATE TABLE [dbo].[sysutility_mi_dac_execution_statistics_internal]
(
   dac_instance_name sysname NOT NULL,
   database_name sysname UNIQUE NOT NULL,     -- database name, from sysdac_instances
   database_id int UNIQUE NOT NULL,           -- database ID, from sysdac_instances
   date_created datetime NULL,                -- DAC creation date, from sysdac_instances
   [description] nvarchar(4000) NULL,         -- DAC description, from sysdac_instances
   first_collection_time datetimeoffset NULL, -- date and time of the first update by [sp_sysutility_mi_collect_dac_execution_statistics_internal]
   last_collection_time datetimeoffset NULL,  -- date and time of the last update by [sp_sysutility_mi_collect_dac_execution_statistics_internal]
   last_upload_time datetimeoffset NULL,      -- date and time of the last upload of this data by DC
   lifetime_cpu_time_ms bigint NULL,          -- cumulative CPU time observed within this DAC since [first_collection_time]
   cpu_time_ms_at_last_upload bigint NULL,    -- [lifetime_cpu_time_ms] value at the last DC upload
   
   CONSTRAINT PK_sysutility_mi_dac_execution_statistics_internal PRIMARY KEY CLUSTERED (dac_instance_name)
);
GO


----------------------------------------------------------------------------------
-- Table dbo.sysutility_mi_session_statistics_internal
--    This table is effectively part of the implementation of sp_sysutility_mi_collect_dac_execution_statistics_internal. 
--    We can safely drop it because it holds ephemeral data that does not need to be preserved across 
--    an msdb upgrade.  The only reason we need a separate table here is because we must temporarily 
--    store execution statistics at the session level in order to approximate the database/DAC execution 
--    statistics (SQL doesn't automatically roll execution stats up to the database level). 
----------------------------------------------------------------------------------
IF (OBJECT_ID ('dbo.sysutility_mi_session_statistics_internal', 'U') IS NOT NULL)
BEGIN
   RAISERROR('Dropping table [dbo].[sysutility_mi_session_statistics_internal]', 0, 1)  WITH NOWAIT;
   DROP TABLE [dbo].[sysutility_mi_session_statistics_internal];
END;
GO

IF (OBJECT_ID ('dbo.sysutility_mi_session_statistics_internal', 'U') IS NULL)
BEGIN
   RAISERROR('Creating table [dbo].[sysutility_mi_session_statistics_internal]', 0, 1)  WITH NOWAIT;
   CREATE TABLE dbo.sysutility_mi_session_statistics_internal (
      collection_time datetimeoffset NOT NULL, 
      session_id int NOT NULL, 
      dac_instance_name sysname NOT NULL, 
      database_name sysname NOT NULL, 
      login_time datetime NOT NULL, 
      cumulative_cpu_ms int NOT NULL, 
      
      CONSTRAINT PK_sysutility_mi_session_statistics_internal PRIMARY KEY CLUSTERED 
         (collection_time, session_id, dac_instance_name, database_name, login_time)
   );
END;
GO


-------------------------------------------------------------------------------------
-- Configuration table to snapshot partitioning to enhance the performance of caching job 
-- time_id = 1 means the previous snapshot cut, time_id = 0, means the current snapshot_id cut
-- Preserve the table during upgrade scenarios (create if it doesn't exist)
-------------------------------------------------------------------------------------
IF(OBJECT_ID(N'[dbo].[sysutility_ucp_snapshot_partitions_internal]', 'U') IS NULL)
BEGIN
RAISERROR('Creating [dbo].[sysutility_ucp_snapshot_partitions_internal] table', 0, 1)  WITH NOWAIT;
CREATE TABLE [dbo].[sysutility_ucp_snapshot_partitions_internal]
(
   time_id INT,
   latest_consistent_snapshot_id INT,
);
END
GO



----------------------------------------------------------------------------------
-- Procedure dbo.sp_sysutility_mi_collect_dac_execution_statistics_internal
--    Captures execution statistics (currently, just CPU usage) by sessions within each DAC since the last 
--    execution of this sp. The incremental new CPU usage is added to the the lifetime DAC total CPU usage 
--    recorded in the cache table [sysutility_mi_dac_execution_statistics_internal].  This sp is executed 
--    by the Agent job that runs every 15 seconds on a Utility managed instance.  The output (the execution 
--    stats in the cache table) is harvested by sp_sysutility_mi_get_dac_execution_statistics_internal as 
--    part of Utility's Data Collector collection set. 
--    
-- Parameters: None
-- Output Resultsets: None
----------------------------------------------------------------------------------
IF OBJECT_ID('dbo.sp_sysutility_mi_collect_dac_execution_statistics_internal', 'P') IS NOT NULL
BEGIN
   RAISERROR('Dropping [dbo].[sp_sysutility_mi_collect_dac_execution_statistics_internal] procedure', 0, 1) WITH NOWAIT;
   DROP PROCEDURE [dbo].[sp_sysutility_mi_collect_dac_execution_statistics_internal];
END
GO

RAISERROR('Creating [dbo].[sp_sysutility_mi_collect_dac_execution_statistics_internal] procedure', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysutility_mi_collect_dac_execution_statistics_internal]
AS
BEGIN
   DECLARE @current_collection_time datetimeoffset = SYSDATETIMEOFFSET();
   DECLARE @previous_collection_time datetimeoffset;
  
   -- At this point the session stats table should contain only rows from the prior collection time (or no 
   -- rows, in which case @previous_collection_time will be set to NULL). Retrieve the prior collection time. 
   SELECT TOP 1 @previous_collection_time = collection_time 
   FROM dbo.sysutility_mi_session_statistics_internal
   ORDER BY collection_time DESC; 

   -- Get a list of the DACs deployed on this instance.  In the sysdac_instances view, some of these values 
   -- are unindexed computed columns.  Store them in a temp table so that we get indexed retrieval by dbid 
   -- or db/instance name and stats on the columns we'll use as join columns. 
   CREATE TABLE #dacs (
      dac_instance_name sysname PRIMARY KEY, 
      database_name sysname UNIQUE, 
      database_id int UNIQUE, 
      date_created datetime, 
      [description] nvarchar(4000));
      
   INSERT INTO #dacs 
   SELECT DISTINCT 
      instance_name, 
      database_name, 
      DB_ID(database_name), 
      date_created, 
      [description]
   FROM dbo.sysdac_instances
   -- Filter out "orphaned" DACs that have had their database deleted or renamed
   WHERE DB_ID(database_name) IS NOT NULL;

   -- Step 1: Capture execution stats for all current sessions in DAC databases. Now this table should 
   -- hold two snapshots: one that we just inserted (referred to as the "current" data from here on), and 
   -- the immediately prior snapshot of the same data from ~15 seconds ago ("previous").  Note that we 
   -- include a dummy row with a fake spid number for any DACs that don't have any active sessions.  This 
   -- is because of a downstream requirement that we return a row for all DACs, even if there are no stats 
   -- to report for the DAC.  
   INSERT INTO dbo.sysutility_mi_session_statistics_internal 
      (collection_time, session_id, dac_instance_name, database_name, login_time, cumulative_cpu_ms)
   SELECT 
      @current_collection_time, 
      ISNULL (sp.spid, -10) AS session_id, 
      #dacs.dac_instance_name, 
      #dacs.database_name, 
      ISNULL (sp.login_time, GETDATE()) AS login_time, 
      ISNULL (SUM(sp.cpu), 0) AS cumulative_cpu_ms
   FROM #dacs 
   LEFT OUTER JOIN sys.sysprocesses AS sp ON #dacs.database_id = sp.[dbid]
   GROUP BY ISNULL (sp.spid, -10), #dacs.dac_instance_name, #dacs.database_name, ISNULL (sp.login_time, GETDATE()); 

   -- Step 2: If this is the first execution, set @prev_collection_time to @cur_collection_time. 
   -- This has the effect of generating stats that indicate no work done for all DACs.  This is 
   -- the best we can do on the first execution because we need to snapshots in order to calculate 
   -- correct resource consumption over a time interval.  We can't just return here, because we 
   -- need at least a "stub" row for each DAC, so they all DACs will appear in the UI if a DC 
   -- upload runs before our next collection time. 
   IF (@previous_collection_time IS NULL)
   BEGIN
      SET @previous_collection_time = @current_collection_time;
   END;

   -- Step 3: Determine the amount of new CPU time used by each DAC in the just-completed ~15 second interval 
   -- (this defines a CTE that is used in the following step). 
   WITH interval_dac_statistics AS (
      SELECT 
         #dacs.dac_instance_name, 
         -- Add up the approximate CPU time used by each session since the last execution of this proc. 
         -- The [current CPU] - [previous CPU] calculation for a session will return NULL if 
         -- [previous CPU] is NULL ([current CPU] should never be NULL).  Previous CPU might be 
         -- NULL if the session is new.  Previous CPU could also be NULL if an existing session 
         -- changed database context.  In either case, we will not charge any of the session's 
         -- CPU time to the database for this interval.  We will start charging any new session 
         -- CPU time to the session's current database on the next execution of this procedure, 
         -- assuming that the session doesn't change database context between now and then.  
         SUM (ISNULL (cur.cumulative_cpu_ms - prev.cumulative_cpu_ms, 0)) AS interval_cpu_time_ms, 
         #dacs.database_name, 
         #dacs.database_id, 
         #dacs.date_created, 
         #dacs.[description]
      FROM #dacs 
      LEFT OUTER JOIN dbo.sysutility_mi_session_statistics_internal AS cur   -- current snapshot, "right" side of time interval
         ON #dacs.dac_instance_name = cur.dac_instance_name AND cur.collection_time = @current_collection_time
      LEFT OUTER JOIN dbo.sysutility_mi_session_statistics_internal AS prev  -- previous snapshot, "left" side of time interval
         ON cur.dac_instance_name = prev.dac_instance_name AND prev.collection_time = @previous_collection_time 
            AND cur.session_id = prev.session_id AND cur.login_time = prev.login_time 
      GROUP BY #dacs.dac_instance_name, #dacs.database_name, #dacs.database_id, #dacs.date_created, #dacs.[description]
   )
   
   -- Step 4: Do an "upsert" to the staging table.  If the DAC is already represented in this table, we will 
   -- add our interval CPU time to that row's [lifetime_cpu_time_ms] value. If the DAC does not yet exist 
   -- in the staging table, we will insert a new row.  
   -- 
   -- A note about overflow risk for the [lifetime_cpu_time_ms] column (bigint).  A DAC that used 100% of 
   -- every CPU on a 500 processor machine 24 hours a day could run for more than half a million years without 
   -- overflowing this column.  
   MERGE [dbo].[sysutility_mi_dac_execution_statistics_internal] AS [target]
   USING interval_dac_statistics AS [source] 
      ON [source].dac_instance_name = [target].dac_instance_name 
         -- Filter out "orphaned" DACs that have had their database deleted or renamed
         AND DB_ID([target].database_name) IS NOT NULL 
   
   WHEN MATCHED THEN UPDATE 
      SET [target].lifetime_cpu_time_ms = ISNULL([target].lifetime_cpu_time_ms, 0) + [source].interval_cpu_time_ms, 
         [target].last_collection_time = @current_collection_time, 
         [target].first_collection_time = ISNULL ([target].first_collection_time, @previous_collection_time), 
         [target].database_name = [source].database_name, 
         [target].database_id = [source].database_id, 
         [target].date_created = [source].date_created, 
         [target].[description] = [source].[description] 
   
   WHEN NOT MATCHED BY TARGET THEN INSERT (  -- new DAC
      dac_instance_name, 
      first_collection_time, 
      last_collection_time, 
      last_upload_time, 
      lifetime_cpu_time_ms, 
      cpu_time_ms_at_last_upload, 
      database_name, 
      database_id, 
      date_created, 
      [description])
      VALUES (
         [source].dac_instance_name, 
         @previous_collection_time, 
         @current_collection_time, 
         @previous_collection_time, 
         [source].interval_cpu_time_ms, 
         0, 
         [source].database_name, 
         [source].database_id, 
         [source].date_created, 
         [source].[description])
   
   WHEN NOT MATCHED BY SOURCE THEN DELETE;   -- deleted or orphaned DAC

   -- Step 5: Delete the session stats from the previous collection time as we no longer need them (but keep the 
   -- current stats we just collected in Step 1; at the next collection time these will be the "previous" 
   -- stats that we'll use to calculate the stats for the next interval). 
   DELETE FROM dbo.sysutility_mi_session_statistics_internal WHERE collection_time != @current_collection_time;
END;
GO

----------------------------------------------------------------------------------
-- Procedure dbo.sp_sysutility_mi_get_dac_execution_statistics_internal
--    Retrieves execution statistics (currently, just CPU usage) for each DAC on the local managed instance. 
--    The CPU usage values are updated in the staging table [sysutility_mi_dac_execution_statistics_internal] 
--    by the [sp_sysutility_mi_collect_dac_execution_statistics_internal] stored proc.  This stored proc is 
--    executed by Utility's Data Collector collection set.  This sp returns an output resultset, but cannot 
--    be implemented as a TVF because it has side effects (records its last upload time in the staging table). 
--    
-- Parameters: None
-- Output Resultsets: A data set containing metadata and execution statistics for each DAC on this managed 
--    instance
----------------------------------------------------------------------------------
IF OBJECT_ID('dbo.sp_sysutility_mi_get_dac_execution_statistics_internal', 'P') IS NOT NULL
BEGIN
   RAISERROR('Dropping [dbo].[sp_sysutility_mi_get_dac_execution_statistics_internal] procedure', 0, 1) WITH NOWAIT;
   DROP PROCEDURE [dbo].[sp_sysutility_mi_get_dac_execution_statistics_internal];
END;
GO

RAISERROR('Creating [dbo].[sp_sysutility_mi_get_dac_execution_statistics_internal] procedure', 0, 1) WITH NOWAIT;
GO

CREATE PROCEDURE [dbo].[sp_sysutility_mi_get_dac_execution_statistics_internal]
AS
BEGIN
   SET NOCOUNT ON;   -- Required for SSIS to retrieve the proc's output rowset 
   DECLARE @logical_processor_count int;
   SELECT @logical_processor_count = cpu_count FROM sys.dm_os_sys_info;
   
   -- Get the shared "batch time" that will be a part of all data collection query rowsets.  On the UCP, this 
   -- will be used to tie together all of the data from one execution of the MI data collection job. 
   
   -- Check for the existance of the temp table.  If it is there, then the Utility is
   -- set up correctly.  If it is not there, do not fail the upload.  This handles the
   -- case when a user might run the collection set out-of-band from the Utility.
   -- The data may not be staged, but no sporratic errors should occur
   DECLARE @current_batch_time datetimeoffset(7) = SYSDATETIMEOFFSET();
   IF OBJECT_ID ('[tempdb].[dbo].[sysutility_batch_time_internal]') IS NOT NULL
   BEGIN
      SELECT @current_batch_time = latest_batch_time FROM tempdb.dbo.sysutility_batch_time_internal;
   END

   -- Temp storage for the DAC execution statistics for this data collection interval (typically, a 15 minute window). 
   -- This and the following table variable would be better as a temp table (b/c of the potentially large number 
   -- of rows), but this proc is run by DC with SET FMTONLY ON to get the output rowset schema.  That means no 
   -- temp tables.  
   DECLARE @upload_interval_dac_stats TABLE (
      dac_instance_name sysname PRIMARY KEY, 
      lifetime_cpu_time_ms bigint NULL,   -- amount of CPU time consumed since we started tracking this DAC
      interval_cpu_time_ms bigint NULL,   -- amount of CPU time used by the DAC in this ~15 min upload interval
      interval_start_time datetimeoffset NULL, 
      interval_end_time datetimeoffset NULL
   );
   
   -- We use an update with an OUTPUT clause to atomically update the staging table and retrieve data from it.  
   -- The use of the "inserted"/"deleted" pseudo-tables in this query ensures that we don't lose any data if the 
   -- collection job happens to be running at the same time. 
   UPDATE dbo.sysutility_mi_dac_execution_statistics_internal
   SET last_upload_time = SYSDATETIMEOFFSET(), 
       cpu_time_ms_at_last_upload = lifetime_cpu_time_ms 
   OUTPUT 
      inserted.dac_instance_name, 
      inserted.lifetime_cpu_time_ms, 
      -- Calculate the amount of CPU time consumed by this DAC since the last time we did an upload. 
      (inserted.cpu_time_ms_at_last_upload - ISNULL (deleted.cpu_time_ms_at_last_upload, 0)) AS interval_cpu_time_ms, 
      deleted.last_upload_time AS interval_start_time, 
      inserted.last_upload_time AS interval_end_time
   INTO @upload_interval_dac_stats;
   
   -- Return the data to the collection set
   SELECT 
      CONVERT (sysname, SERVERPROPERTY('ComputerNamePhysicalNetBIOS')) AS physical_server_name, 
      CONVERT (sysname, SERVERPROPERTY('ServerName')) AS server_instance_name, 
      CONVERT (sysname, dacs.database_name) AS dac_db, 
      dacs.date_created AS dac_deploy_date, 
      dacs.[description] AS dac_description, 
      dacs.dac_instance_name AS dac_name, 
      dac_stats.interval_start_time, 
      dac_stats.interval_end_time, 
      dac_stats.interval_cpu_time_ms, 
      CONVERT (real, CASE 
         WHEN dac_stats.interval_cpu_time_ms IS NOT NULL 
            AND DATEDIFF (second, dac_stats.interval_start_time, dac_stats.interval_end_time) > 0
            -- % CPU calculation is: [avg seconds of cpu time used per processor] / [interval duration in sec]
            -- The percentage value is returned as an int (e.g. 76 for 76%, not 0.76)
            THEN 100 * (dac_stats.interval_cpu_time_ms / @logical_processor_count) / 1000 / 
               DATEDIFF (second, dac_stats.interval_start_time, dac_stats.interval_end_time)
         ELSE 0
      END) AS interval_cpu_pct, 
      dac_stats.lifetime_cpu_time_ms, 
      @current_batch_time AS batch_time
   FROM dbo.sysutility_mi_dac_execution_statistics_internal AS dacs 
   LEFT OUTER JOIN @upload_interval_dac_stats AS dac_stats ON dacs.dac_instance_name = dac_stats.dac_instance_name;
END;
GO


----------------------------------------------------------------------------
--- Function to get a readable processor architecture from stored architecture number
----------------------------------------------------------------------------
IF EXISTS (SELECT name FROM [sys].[objects] WHERE object_id = OBJECT_ID(N'[dbo].[fn_sysutility_mi_get_cpu_architecture_name]'))
   DROP FUNCTION [dbo].[fn_sysutility_mi_get_cpu_architecture_name];
GO
CREATE FUNCTION [dbo].[fn_sysutility_mi_get_cpu_architecture_name](@architecture INT)
RETURNS NVARCHAR(64)
AS
BEGIN
   DECLARE @architecture_name NVARCHAR(64) = N''
   SELECT @architecture_name = 
      CASE 
         WHEN @architecture = 0 THEN 'x86'
         WHEN @architecture = 1 THEN 'MIPS'
         WHEN @architecture = 2 THEN 'Alpha'
         WHEN @architecture = 3 THEN 'PowerPC'
         WHEN @architecture = 6 THEN 'Intel Itanium Processor Family (IPF)'
         WHEN @architecture = 9 THEN 'x64'
      END
   RETURN @architecture_name
END
GO



----------------------------------------------------------------------------
--- Function to get a readable processor family from stored family number
----------------------------------------------------------------------------
IF EXISTS (SELECT name FROM [sys].[objects] WHERE object_id = OBJECT_ID(N'[dbo].[fn_sysutility_mi_get_cpu_family_name]'))
   DROP FUNCTION [dbo].[fn_sysutility_mi_get_cpu_family_name];
GO
CREATE FUNCTION [dbo].[fn_sysutility_mi_get_cpu_family_name](@family INT)
RETURNS NVARCHAR(128)
AS
BEGIN
   DECLARE @family_name NVARCHAR(128) = N''
   SELECT @family_name = 
      CASE 
         WHEN @family = 1 THEN 'Other'
         WHEN @family = 2 THEN 'Unknown'
         WHEN @family = 3 THEN '8086'
         WHEN @family = 4 THEN '80286'
         WHEN @family = 5 THEN 'Intel386 Processor'
         WHEN @family = 6 THEN 'Intel486 Processor'
         WHEN @family = 7 THEN '8087'
         WHEN @family = 8 THEN '80287'
         WHEN @family = 9 THEN '80387'
         WHEN @family = 10 THEN '80487'
         WHEN @family = 11 THEN 'Pentium Brand'
         WHEN @family = 12 THEN 'Pentium Pro'
         WHEN @family = 13 THEN 'Pentium II'
         WHEN @family = 14 THEN 'Pentium Processor with MMX Technology'
         WHEN @family = 15 THEN 'Celeron'
         WHEN @family = 16 THEN 'Pentium II Xeon'
         WHEN @family = 17 THEN 'Pentium III'
         WHEN @family = 18 THEN 'M1 Family'
         WHEN @family = 19 THEN 'M2 Family'
         WHEN @family = 24 THEN 'AMD Duron Processor Family'
         WHEN @family = 25 THEN 'K5 Family'
         WHEN @family = 26 THEN 'K6 Family'
         WHEN @family = 27 THEN 'K6-2'
         WHEN @family = 28 THEN 'K6-3'
         WHEN @family = 29 THEN 'AMD Athlon Processor Family'
         WHEN @family = 30 THEN 'AMD2900 Family'
         WHEN @family = 31 THEN 'K6-2+'
         WHEN @family = 32 THEN 'Power PC Family'
         WHEN @family = 33 THEN 'Power PC 601'
         WHEN @family = 34 THEN 'Power PC 603'
         WHEN @family = 35 THEN 'Power PC 603+'
         WHEN @family = 36 THEN 'Power PC 604'
         WHEN @family = 37 THEN 'Power PC 620'
         WHEN @family = 38 THEN 'Power PC X704'
         WHEN @family = 39 THEN 'Power PC 750'
         WHEN @family = 48 THEN 'Alpha Family'
         WHEN @family = 49 THEN 'Alpha 21064'
         WHEN @family = 50 THEN 'Alpha 21066'
         WHEN @family = 51 THEN 'Alpha 21164'
         WHEN @family = 52 THEN 'Alpha 21164PC'
         WHEN @family = 53 THEN 'Alpha 21164a'
         WHEN @family = 54 THEN 'Alpha 21264'
         WHEN @family = 55 THEN 'Alpha 21364'
         WHEN @family = 64 THEN 'MIPS Family'
         WHEN @family = 65 THEN 'MIPS R4000'
         WHEN @family = 66 THEN 'MIPS R4200'
         WHEN @family = 67 THEN 'MIPS R4400'
         WHEN @family = 68 THEN 'MIPS R4600'
         WHEN @family = 69 THEN 'MIPS R10000'
         WHEN @family = 80 THEN 'SPARC Family'
         WHEN @family = 81 THEN 'SuperSPARC'
         WHEN @family = 82 THEN 'microSPARC II'
         WHEN @family = 83 THEN 'microSPARC IIep'
         WHEN @family = 84 THEN 'UltraSPARC'
         WHEN @family = 85 THEN 'UltraSPARC II'
         WHEN @family = 86 THEN 'UltraSPARC IIi'
         WHEN @family = 87 THEN 'UltraSPARC III'
         WHEN @family = 88 THEN 'UltraSPARC IIIi'
         WHEN @family = 96 THEN '68040'
         WHEN @family = 97 THEN '68xxx Family'
         WHEN @family = 98 THEN '68000'
         WHEN @family = 99 THEN '68010'
         WHEN @family = 100 THEN '68020'
         WHEN @family = 101 THEN '68030'
         WHEN @family = 112 THEN 'Hobbit Family'
         WHEN @family = 120 THEN 'Crusoe TM5000 Family'
         WHEN @family = 121 THEN 'Crusoe TM3000 Family'
         WHEN @family = 122 THEN 'Efficeon TM8000 Family'
         WHEN @family = 128 THEN 'Weitek'
         WHEN @family = 130 THEN 'Itanium Processor'
         WHEN @family = 131 THEN 'AMD Athlon 64 Processor Family'
         WHEN @family = 132 THEN 'AMD Opteron Processor Family'
         WHEN @family = 144 THEN 'PA-RISC Family'
         WHEN @family = 145 THEN 'PA-RISC 8500'
         WHEN @family = 146 THEN 'PA-RISC 8000'
         WHEN @family = 147 THEN 'PA-RISC 7300LC'
         WHEN @family = 148 THEN 'PA-RISC 7200'
         WHEN @family = 149 THEN 'PA-RISC 7100LC'
         WHEN @family = 150 THEN 'PA-RISC 7100'
         WHEN @family = 160 THEN 'V30 Family'
         WHEN @family = 176 THEN 'Pentium III Xeon Processor'
         WHEN @family = 177 THEN 'Pentium III Processor with Intel SpeedStep Technology'
         WHEN @family = 178 THEN 'Pentium 4'
         WHEN @family = 179 THEN 'Intel Xeon'
         WHEN @family = 180 THEN 'AS400 Family'
         WHEN @family = 181 THEN 'Intel Xeon Processor MP'
         WHEN @family = 182 THEN 'AMD Athlon XP Family'
         WHEN @family = 183 THEN 'AMD Athlon MP Family'
         WHEN @family = 184 THEN 'Intel Itanium 2'
         WHEN @family = 185 THEN 'Intel Pentium M Processor'
         WHEN @family = 190 THEN 'K7'
         WHEN @family = 200 THEN 'IBM390 Family'
         WHEN @family = 201 THEN 'G4'
         WHEN @family = 202 THEN 'G5'
         WHEN @family = 203 THEN 'G6'
         WHEN @family = 204 THEN 'z/Architecture Base'
         WHEN @family = 250 THEN 'i860'
         WHEN @family = 251 THEN 'i960'
         WHEN @family = 260 THEN 'SH-3'
         WHEN @family = 261 THEN 'SH-4'
         WHEN @family = 280 THEN 'ARM'
         WHEN @family = 281 THEN 'StrongARM'
         WHEN @family = 300 THEN '6x86'
         WHEN @family = 301 THEN 'MediaGX'
         WHEN @family = 302 THEN 'MII'
         WHEN @family = 320 THEN 'WinChip'
         WHEN @family = 350 THEN 'DSP'
         WHEN @family = 500 THEN 'Video Processor'
      END
   RETURN @family_name
END
GO

IF (OBJECT_ID(N'[msdb].[dbo].[sysutility_mi_volumes_stage_internal]', N'U') IS NULL)
BEGIN
RAISERROR('Creating [dbo].[sysutility_mi_volumes_stage_internal] table', 0, 1)  WITH NOWAIT;
CREATE TABLE [dbo].[sysutility_mi_volumes_stage_internal]
(  
   -- Collected columns
   [volume_device_id]     SYSNAME          NOT NULL DEFAULT N'',   -- Collected as String
   [volume_name]   NVARCHAR(260)	NOT NULL DEFAULT N'',   -- Collected as String

   [capacity_mb]    DECIMAL(20,0)   NOT NULL DEFAULT 0.0,   -- Collected as UInt64
   [free_space_mb]  DECIMAL(20,0)   NOT NULL DEFAULT 0.0,   -- Collected as UInt64
   
   -- Computed columns
   [server_instance_name]     AS (CAST(SERVERPROPERTY('ServerName') AS SYSNAME)),
   [virtual_server_name]      AS (CAST(SERVERPROPERTY('MachineName') AS SYSNAME)),
   [physical_server_name]     AS (CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS SYSNAME))
);
END
GO


IF (OBJECT_ID(N'[dbo].[sysutility_mi_cpu_stage_internal]', 'U') IS NULL)
BEGIN
RAISERROR('Creating [dbo].[sysutility_mi_cpu_stage_internal]', 0, 1) WITH NOWAIT;
CREATE TABLE [dbo].[sysutility_mi_cpu_stage_internal]
(
   -- Collected columns
   [num_processors]          INT            NOT NULL DEFAULT -1,  -- Collected as Int32, -1 represents unknown
   [cpu_name]                NVARCHAR(128)  NOT NULL DEFAULT N'', -- Collected as String
   [cpu_caption]             NVARCHAR(128)  NOT NULL DEFAULT N'', -- Collected as String
   [cpu_family_id]           DECIMAL(5,0)   NOT NULL DEFAULT -1,  -- Collected as UInt16, -1 represents unknown
   [cpu_architecture_id]     DECIMAL(5,0)   NOT NULL DEFAULT -1,  -- Collected as UInt16, -1 represents unknown
   [cpu_max_clock_speed]     DECIMAL(10,0)  NOT NULL DEFAULT 0.0, -- Collected as UInt32
   [cpu_clock_speed]         DECIMAL(10,0)  NOT NULL DEFAULT 0.0, -- Collected as UInt32
   [l2_cache_size]           DECIMAL(10,0)  NOT NULL DEFAULT 0.0, -- Collected as UInt32
   [l3_cache_size]           DECIMAL(10,0)  NOT NULL DEFAULT 0.0, -- Collected as UInt32
   
   [instance_processor_usage_start_ticks]   DECIMAL(20,0) NOT NULL DEFAULT 0.0, -- Collected as UInt64
   [instance_collect_time_start_ticks]      DECIMAL(20,0) NOT NULL DEFAULT 0.0, -- Collected as Int64, but should not be negative
   
   [computer_processor_idle_start_ticks]    DECIMAL(20,0) NOT NULL DEFAULT 0.0, -- Collected as UInt64
   [computer_collect_time_start_ticks]      DECIMAL(20,0) NOT NULL DEFAULT 0.0, -- Collected as UInt64
   
   [instance_processor_usage_end_ticks]     DECIMAL(20,0) NOT NULL DEFAULT 0.0, -- Collected as UInt64
   [instance_collect_time_end_ticks]        DECIMAL(20,0) NOT NULL DEFAULT 0.0, -- Collected as Int64, but should not be negative
   
   [computer_processor_idle_end_ticks]      DECIMAL(20,0) NOT NULL DEFAULT 0.0, -- Collected as UInt64
   [computer_collect_time_end_ticks]        DECIMAL(20,0) NOT NULL DEFAULT 0.0, -- Collected as UInt64

   -- Computed columns
   [server_instance_name]     AS (CAST(SERVERPROPERTY('ServerName') AS SYSNAME)),
   [virtual_server_name]      AS (CAST(SERVERPROPERTY('MachineName') AS SYSNAME)),
   [physical_server_name]     AS (CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS SYSNAME)),
   
   [instance_processor_usage_percentage] AS 
                                (-- negative elapsed usage indicates instance reboot.  use 0 as usage ticks in this case
                                CAST(
                                   CASE WHEN 
                                           -- usage time can be 0 when the instance never got a timeslice (very unlikely)
                                          (0 > instance_processor_usage_end_ticks - instance_processor_usage_start_ticks)
                                       OR (0 >= instance_collect_time_end_ticks - instance_collect_time_start_ticks) 
                                       THEN 0.0  
                                   ELSE 
                                   -- we divide by num_processors since we're getting this info from the process' overall consumption. 
                                   -- not the same for the computer-processor information below
                                   ((instance_processor_usage_end_ticks - instance_processor_usage_start_ticks) 
                                   / (instance_collect_time_end_ticks - instance_collect_time_start_ticks)
                                   / num_processors) * 100.0
                                   END
                                AS REAL)),
       
   [computer_processor_usage_percentage] AS								
                                (-- negative elapsed usage indicates instance reboot.  use 0 as usage ticks in this case
                                CAST(
                                   CASE WHEN 
                                           -- idle time can be 0 when the processor is completely saturated
                                          (0 > computer_processor_idle_end_ticks - computer_processor_idle_start_ticks)
                                       OR (0 >= computer_collect_time_end_ticks - computer_collect_time_start_ticks) 
                                       THEN 0.0  
                                   ELSE 
                                   -- 1.0 - PercentProcessorTime because using idle time and want percent utilized time
                                   (1.0 - 
                                   ((computer_processor_idle_end_ticks - computer_processor_idle_start_ticks)  
                                   / (computer_collect_time_end_ticks - computer_collect_time_start_ticks))) * 100.0
                                   END
                                AS REAL))
)
END
GO

IF NOT EXISTS (SELECT name FROM [sys].[objects] WHERE object_id = OBJECT_ID(N'[dbo].[sysutility_mi_smo_stage_internal]'))
BEGIN
RAISERROR('Creating [dbo].[sysutility_mi_smo_stage_internal]', 0, 1) WITH NOWAIT;
CREATE TABLE [dbo].[sysutility_mi_smo_stage_internal]
(
   -- Collected columns
   [object_type]           INT        NOT NULL,          -- FK reference to sysutility_mi_smo_objects_to_collect_internal.object_type
                                                         -- FK is not defined on the table to keep table a light-weight staging table
   [urn]                   NVARCHAR(4000) NOT NULL,      -- Collected as String
                                                         -- Needs to hold /Server[@Name='']/Database[@Name='']/Filegroup[@Name='']/DataFile[@Name='']
                                                         -- where value within '' is SYSNAME, so 600 is enough thus 4000 is plenty
   [property_name]         NVARCHAR(128)   NOT NULL,     -- Collected as String, no property should be longer than 80 characters, but we'll be safe
   [property_value]        SQL_VARIANT    NULL,          -- Collected as Object

   
   -- Computed columns
   [server_instance_name]     AS (CAST(SERVERPROPERTY('ServerName') AS SYSNAME)),
   [physical_server_name]     AS (CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS SYSNAME))
);
END
GO

-------------------------------------------------------------------------
-- Create function: fn_sysutility_mi_get_batch_manifest
-- This function returns the manifest information for the most recent batch collected 
-- and is included as a part of the data uploaded from the MI to UCP. 
-- The purpose of the manifest is to qualify the consistency of the data uploaded on the UCP.
-- The batch manifest primarily includes:
-- 1. server_instance_name: the server\instance name of the MI
-- 2. batch_time: the batch creation date-time stamp.  
-- 3. xx_row_count: row count for each of the table collected/uploaded by the utility T-SQL collection item query
-- Note: a. The server_instance_name & batch_time make the composite key for batch_manifest
--       b. The parameter name is of type string and value is of type sql_variant.	
-------------------------------------------------------------------------
IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[fn_sysutility_mi_get_batch_manifest]') AND type in (N'IF'))
BEGIN
   RAISERROR('Dropping [dbo].[fn_sysutility_mi_get_batch_manifest] function', 0, 1)  WITH NOWAIT;
   DROP FUNCTION [dbo].[fn_sysutility_mi_get_batch_manifest];
END
GO
RAISERROR('Creating [dbo].[fn_sysutility_mi_get_batch_manifest] function', 0, 1)  WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysutility_mi_get_batch_manifest]()
RETURNS TABLE
AS
RETURN
(
    -- DAC execution statistics row count
    SELECT N'dac_packages_row_count' AS parameter_name
    , CONVERT(SQL_VARIANT, COUNT(*)) AS parameter_value
    FROM [msdb].[dbo].[sysutility_mi_dac_execution_statistics_internal] 

    UNION ALL
    
    -- MI CPU and memory configurations row count
    SELECT N'cpu_memory_configurations_row_count' AS parameter_name
    , CONVERT(SQL_VARIANT, COUNT(*)) AS parameter_value
    FROM [msdb].[dbo].[sysutility_mi_cpu_stage_internal]

    UNION ALL

    -- MI volumes row count
    SELECT N'volumes_row_count' AS parameter_name
    , CONVERT(SQL_VARIANT, COUNT(*)) AS parameter_value 
    FROM [msdb].[dbo].[sysutility_mi_volumes_stage_internal]

    UNION ALL
    
    -- SMO properties row count
    SELECT N'smo_properties_row_count' AS parameter_name
    , CONVERT(SQL_VARIANT, COUNT(*)) AS parameter_value 
    FROM [msdb].[dbo].[sysutility_mi_smo_stage_internal]
)
GO
-------------------------------------------------------------------------------
-- Stores the Smo sfc query that is used during data collection
-------------------------------------------------------------------------------
IF (OBJECT_ID(N'[dbo].[sysutility_mi_smo_objects_to_collect_internal]', 'U') IS NULL)
BEGIN
RAISERROR('Creating [dbo].[sysutility_mi_smo_objects_to_collect_internal]', 0, 1) WITH NOWAIT;
CREATE TABLE [dbo].[sysutility_mi_smo_objects_to_collect_internal]
(
   [object_type]              INT       NOT NULL,
   [sfc_query]                NVARCHAR(MAX) NOT NULL,
   PRIMARY KEY (object_type)
);
END
GO
 

-------------------------------------------------------------------------------
--Script for filling the smo configurations tables
-------------------------------------------------------------------------------
DELETE FROM [dbo].[sysutility_mi_smo_objects_to_collect_internal]

INSERT INTO [dbo].[sysutility_mi_smo_objects_to_collect_internal] VALUES(1, N'Server'); 
INSERT INTO [dbo].[sysutility_mi_smo_objects_to_collect_internal] VALUES(2, N'Server/Database');
INSERT INTO [dbo].[sysutility_mi_smo_objects_to_collect_internal] VALUES(3, N'Server/Database[@IsAccessible=1]/LogFile');
-- for SQL10/10.5 file stat dmv support for filestream isn't there, thus we do not collect filestream related
-- file groups and data files. VSTS 351631.
-- VSTS 1030808: Modified the filter to only include rows file groups and exclude filestream and hekaton ones.
INSERT INTO [dbo].[sysutility_mi_smo_objects_to_collect_internal] VALUES(4, N'Server/Database[@IsAccessible=1]/FileGroup[@FileGroupType=0]');
INSERT INTO [dbo].[sysutility_mi_smo_objects_to_collect_internal] VALUES(5, N'Server/Database[@IsAccessible=1]/FileGroup[@FileGroupType=0]/File');

-------------------------------------------------------------------------------
-- Stores SMO properties to collect
-------------------------------------------------------------------------------
IF (OBJECT_ID(N'[dbo].[sysutility_mi_smo_properties_to_collect_internal]', 'U') IS NULL)
BEGIN
RAISERROR('Creating [dbo].[sysutility_mi_smo_properties_to_collect_internal]', 0, 1) WITH NOWAIT;
CREATE TABLE [dbo].[sysutility_mi_smo_properties_to_collect_internal]
(
   [object_type]         INT NOT NULL,
   [property_name]       NVARCHAR(80) NOT NULL,
   PRIMARY KEY (object_type, property_name)
);

ALTER TABLE [dbo].[sysutility_mi_smo_properties_to_collect_internal]  WITH CHECK ADD  CONSTRAINT [FK_sysutility_mi_smo_properties] FOREIGN KEY([object_type])
REFERENCES [dbo].[sysutility_mi_smo_objects_to_collect_internal] ([object_type])
ON DELETE CASCADE

END
GO

DELETE FROM [dbo].[sysutility_mi_smo_properties_to_collect_internal]
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'AuditLevel');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'BackupDirectory');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'BrowserServiceAccount');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'BrowserStartMode');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'BuildClrVersionString');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'BuildNumber');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'Collation');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'CollationID');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'ComparisonStyle');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'ComputerNamePhysicalNetBIOS');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'DefaultFile');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'DefaultLog');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'Edition');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'EngineEdition');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'ErrorLogPath');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'FilestreamShareName');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'InstallDataDirectory');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'InstallSharedDirectory');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'InstanceName');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'IsCaseSensitive');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'IsClustered');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'IsFullTextInstalled');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'IsSingleUser');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'Language');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'MailProfile');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'MasterDBLogPath');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'MasterDBPath');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'MaxPrecision');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'Name');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'NamedPipesEnabled');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'NetName');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'NumberOfLogFiles');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'OSVersion');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'PerfMonMode');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'PhysicalMemory');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'Platform');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'Processors');
-- note for this 'ProcessorUsage' property this is just a placeholder, we in fact collect its value
-- by ourselves instead of relying on SMO.Server.ProcessorUsage property for two reasons
-- 1) our collection mechanism is more accurate because we're doing averaging over
--    our entire collection cycle versus, SMO property value was very instantaneous.
-- 2) this SMO property isn't available downlevel (before 10.5)
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'ProcessorUsage');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'Product');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'ProductLevel');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'ResourceVersionString');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'RootDirectory');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'ServerType');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'ServiceAccount');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'ServiceInstanceId');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'ServiceName');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'ServiceStartMode');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'SqlCharSet');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'SqlCharSetName');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'SqlDomainGroup');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'SqlSortOrder');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'SqlSortOrderName');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'Status');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'TapeLoadWaitTime');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'TcpEnabled');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'VersionMajor');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'VersionMinor');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(1, N'VersionString');

INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(2, N'CreateDate');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(2, N'Collation');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(2, N'CompatibilityLevel');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(2, N'EncryptionEnabled');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(2, N'ID');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(2, N'Name');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(2, N'RecoveryModel');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(2, N'Trustworthy');

INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(3, N'FileName');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(3, N'Growth');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(3, N'GrowthType');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(3, N'ID');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(3, N'MaxSize');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(3, N'Name');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(3, N'Size');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(3, N'UsedSpace');

INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(4, N'ID');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(4, N'Name');

INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(5, N'FileName');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(5, N'Growth');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(5, N'GrowthType');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(5, N'ID');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(5, N'MaxSize');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(5, N'Name');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(5, N'Size');
INSERT INTO [dbo].[sysutility_mi_smo_properties_to_collect_internal] VALUES(5, N'UsedSpace');
GO

-----------------------------------------------------------------------
--  Script for creating Utility Collection sets and Collection set items.
-----------------------------------------------------------------------

   DECLARE @collection_set_number INT = 0 ;
   DECLARE @running_state BIT;    
    DECLARE @collection_set_name NVARCHAR(128);
   DECLARE @description NVARCHAR(4000);
   DECLARE @collection_set_uid uniqueidentifier;
   DECLARE @collection_mode smallint;
   DECLARE @schedule_name sysname;
   DECLARE @days_until_expiration smallint;
   DECLARE @name_id int;
   DECLARE @description_id int;
   DECLARE @collection_set_id INT
   DECLARE @proxy_id int = NULL;
    
    
   SET @name_id = 14716;
   SET @description_id = 14715;
   SET @collection_set_uid = N'ABA37A22-8039-48C6-8F8F-39BFE0A195DF';
   SET @collection_mode = 1; -- Non-cached
   SET @schedule_name = N''; -- Non-scheduled.  The Utility collection runs on-demand through the Utility-owned job.
   DECLARE @frequency int = 900; -- Collection is on demand, this value is ignored
   
   SET @days_until_expiration = 1;
    
   SET @description = FORMATMESSAGE(@description_id);
   SET @collection_set_name = ISNULL (FORMATMESSAGE(@name_id), 'Utility Information');
    
   -- Unlike the MDW system collection sets, we don't need to retain any customized schedule added by the user.  The 
   -- Utility collection set is not scheduled and we want it to stay that way.  To simplify this code, we just drop 
   -- the collection set if it exists, then recreate it.      
   IF EXISTS (SELECT * FROM dbo.syscollector_collection_sets_internal WHERE collection_set_uid = @collection_set_uid)
   BEGIN
      RAISERROR ('Deleting collection set "%s"...', 0, 1, @collection_set_name) WITH NOWAIT;

      -- Get the collection set's collection set ID and save off the current proxy ID.  If the collection set was 
      -- configured to run under a proxy account, this is the one user-specified attribute that we need to preserve 
      -- when we recreate it.  If the collection set has not been configured, proxy_id will be NULL.  When we pass 
      -- NULL to sp_syscollector_create_collection_set when recreating the collection set, a NULL value for the 
      -- @proxy_id parameter also means "don't use a proxy".  
      SELECT 
         @collection_set_id = collection_set_id, 
         @proxy_id = proxy_id 
      FROM syscollector_collection_sets 
      WHERE collection_set_uid = @collection_set_uid;
      
      -- Temporarily clear the is_system flag so that we can modify the collection set definition
      UPDATE syscollector_collection_sets
      SET is_system = 0
      WHERE collection_set_id = @collection_set_id
      
      EXEC sp_syscollector_delete_collection_set @collection_set_id = @collection_set_id;
   END

   RAISERROR ('Creating system Collection Set "%s"...', 0, 1, @collection_set_name) WITH NOWAIT;
   EXEC dbo.sp_syscollector_create_collection_set
      @collection_set_uid = @collection_set_uid,
      @name = @collection_set_name, 
      @schedule_name = @schedule_name, 
      @collection_mode = @collection_mode, 
      @days_until_expiration = @days_until_expiration, 
      @description = @description,
      @logging_level = 0, 
      @proxy_id = @proxy_id, 
      @collection_set_id = @collection_set_id OUTPUT;
   
   IF(FORMATMESSAGE(@name_id) IS NOT NULL)
   BEGIN
      -- for localization of collection set name and description
      UPDATE syscollector_collection_sets_internal
      SET name_id = @name_id, description_id = @description_id
      WHERE collection_set_uid = @collection_set_uid;
   END

   DECLARE @collector_type_uid_3 UNIQUEIDENTIFIER
    SELECT @collector_type_uid_3 = collector_type_uid 
   FROM [dbo].[syscollector_collector_types] 
   WHERE name = N'Generic T-SQL Query Collector Type';
   
   DECLARE @collection_item_loc_name NVARCHAR(128);
   DECLARE @collection_item_enu_name NVARCHAR(128);
   DECLARE @collection_item_id int;

   SET @name_id = 14718;
   SET @collection_item_enu_name = 'Utility Information - Managed Instance';
   SET @collection_item_loc_name = ISNULL (FORMATMESSAGE(@name_id), @collection_item_enu_name);


   DECLARE @parameters xml;
   SELECT @parameters = convert(xml, N'<ns:TSQLQueryCollector xmlns:ns="DataCollectorType">
   <Query>
      <Value>
         EXEC [msdb].[dbo].[sp_sysutility_mi_get_dac_execution_statistics_internal];
      </Value><OutputTable>sysutility_ucp_dac_collected_execution_statistics_internal</OutputTable>
   </Query>
   
   <Query>
      <Value>
      -- Check for the existance of the temp table.  If it is there, then the Utility is
      -- set up correctly.  If it is not there, do not fail the upload.  This handles the
      -- case when a user might run the collection set out-of-band from the Utility.
      -- The data may not be staged, but no sporratic errors should occur
      DECLARE @batch_time datetimeoffset(7) = SYSDATETIMEOFFSET()
      IF OBJECT_ID (''[tempdb].[dbo].[sysutility_batch_time_internal]'') IS NOT NULL
      BEGIN
          SELECT @batch_time = latest_batch_time FROM tempdb.dbo.sysutility_batch_time_internal
      END
      SELECT 
         [server_instance_name],
         CAST(clustered_check.is_clustered_server AS SMALLINT) AS [is_clustered_server], 
         [virtual_server_name],
         [physical_server_name],
         [num_processors],
         [computer_processor_usage_percentage]  AS [server_processor_usage],
         [instance_processor_usage_percentage]  AS [instance_processor_usage],
         [cpu_name],
           [cpu_caption],
         [msdb].[dbo].[fn_sysutility_mi_get_cpu_family_name](cpu_family_id) AS [cpu_family],
         [msdb].[dbo].[fn_sysutility_mi_get_cpu_architecture_name](cpu_architecture_id) AS [cpu_architecture],
         [cpu_max_clock_speed],
         [cpu_clock_speed],
         [l2_cache_size],
         [l3_cache_size], 
         @batch_time AS [batch_time]
         FROM [msdb].[dbo].[sysutility_mi_cpu_stage_internal],
           (SELECT TOP 1 CAST (CASE WHEN COUNT(*) = 0 THEN 0 ELSE 1 END AS bit) AS is_clustered_server
            FROM msdb.sys.dm_os_cluster_nodes 
            WHERE NodeName = SERVERPROPERTY(''ComputerNamePhysicalNetBIOS'')) AS clustered_check
      </Value><OutputTable>sysutility_ucp_cpu_memory_configurations_internal</OutputTable>
   </Query>

   <Query>
      <Value>
         -- Check for the existance of the temp table.  If it is there, then the Utility is
         -- set up correctly.  If it is not there, do not fail the upload.  This handles the
         -- case when a user might run the collection set out-of-band from the Utility.
         -- The data may not be staged, but no sporratic errors should occur
         DECLARE @batch_time datetimeoffset(7) = SYSDATETIMEOFFSET()
         IF OBJECT_ID (''[tempdb].[dbo].[sysutility_batch_time_internal]'') IS NOT NULL
         BEGIN
             SELECT @batch_time = latest_batch_time FROM tempdb.dbo.sysutility_batch_time_internal
         END
         SELECT 
            [volume_device_id],
            [volume_name],
            CAST([capacity_mb] AS REAL)   AS [total_space_available],
            CAST([free_space_mb] AS REAL) AS [free_space],
            [virtual_server_name],
            [physical_server_name],
            [server_instance_name], 
            @batch_time AS [batch_time]
         FROM [msdb].[dbo].[sysutility_mi_volumes_stage_internal]
      </Value>
      <OutputTable>sysutility_ucp_volumes_internal</OutputTable>
   </Query>
         
   <Query>
   <Value>
      -- Check for the existance of the temp table.  If it is there, then the Utility is
      -- set up correctly.  If it is not there, do not fail the upload.  This handles the
      -- case when a user might run the collection set out-of-band from the Utility.
      -- The data may not be staged, but no sporratic errors should occur
      DECLARE @batch_time datetimeoffset(7) = SYSDATETIMEOFFSET()
      IF OBJECT_ID (''[tempdb].[dbo].[sysutility_batch_time_internal]'') IS NOT NULL
      BEGIN
          SELECT @batch_time = latest_batch_time FROM tempdb.dbo.sysutility_batch_time_internal
      END
      
      SELECT 
         smo.[physical_server_name],
         smo.[server_instance_name],
         [object_type],
         [urn],
         [property_name],
         -- DC (SSIS, really) does not support sql_variant.  It will implicitly convert all variant columns to nvarchar(256), 
         -- which can cause data loss.  To avoid this we explicitly convert to nvarchar(4000) so that nothing gets truncated. 
         -- On the UCP, we reverse this conversion in sp_copy_live_table_data_into_cache_tables.  In order to round-trip the 
         -- data through nvarchar successfully, we must use the same language-independent conversion style on MI and UCP. We 
         -- use the shared fn_sysutility_get_culture_invariant_conversion_style_internal function to get a consistent 
         -- language-independent conversion style for each property data type.  (References: VSTS 361531, 359504, 12967)
         CONVERT 
         (
            nvarchar(4000), 
            CASE [property_name] 
               WHEN N''ProcessorUsage'' -- Hijack the ProcessorUsage property and insert our own value
               THEN CAST(cpu.[instance_processor_usage_percentage] AS INT)  -- loss of decimal places
               ELSE [property_value] 
            END, 
            msdb.dbo.fn_sysutility_get_culture_invariant_conversion_style_internal(CONVERT (varchar(30), SQL_VARIANT_PROPERTY (property_value, ''BaseType''))) 
         ) AS [property_value], 
         @batch_time AS [batch_time]
      FROM [msdb].[dbo].[sysutility_mi_smo_stage_internal] AS smo
      INNER JOIN [msdb].[dbo].[sysutility_mi_cpu_stage_internal] AS cpu 
         ON smo.[server_instance_name] = cpu.[server_instance_name]
      </Value><OutputTable>sysutility_ucp_smo_properties_internal</OutputTable>
  </Query>
              
  <!-- Query to collect/upload the batch manifest -->
  <Query>
    <Value>
        -- Check for the existance of the temp table.  If it is there, then the Utility is
        -- set up correctly.  If it is not there, do not fail the upload.  This handles the
        -- case when a user might run the collection set out-of-band from the Utility.
        -- The data may not be staged, but no sporratic errors should occur
        DECLARE @batch_time datetimeoffset(7) = SYSDATETIMEOFFSET()
        IF OBJECT_ID (''[tempdb].[dbo].[sysutility_batch_time_internal]'') IS NOT NULL
        BEGIN
          SELECT @batch_time = latest_batch_time FROM tempdb.dbo.sysutility_batch_time_internal
        END 
        SELECT CONVERT(SYSNAME, SERVERPROPERTY(N''ServerName'')) AS [server_instance_name],
            @batch_time AS [batch_time],
            bm.parameter_name,
            bm.parameter_value
        FROM [msdb].[dbo].[fn_sysutility_mi_get_batch_manifest]() bm  
    </Value>
    <OutputTable>sysutility_ucp_batch_manifests_internal</OutputTable>
  </Query> 
  </ns:TSQLQueryCollector>');
         
   SET @collection_item_id = NULL;
   SELECT @collection_item_id = collection_item_id
   FROM syscollector_collection_items_internal 
   WHERE collection_set_id = @collection_set_id 
      AND (name = @collection_item_loc_name OR name = @collection_item_enu_name);

   IF (@collection_item_id IS NOT NULL)
   BEGIN
      RAISERROR ('Updating Collection Item "%s"...', 0, 1, @collection_item_loc_name);
      EXEC dbo.sp_syscollector_update_collection_item 
         @collection_item_id = @collection_item_id, 
         @new_name = @collection_item_loc_name, 
         @frequency = @frequency, 
         @parameters = @parameters;
   END
   ELSE
   BEGIN
      RAISERROR ('Creating Collection Item "%s"...', 0, 1, @collection_item_loc_name);
      EXEC dbo.sp_syscollector_create_collection_item
         @collection_set_id = @collection_set_id,
         @collector_type_uid = N'302E93D1-3424-4BE7-AA8E-84813ECF2419',
         @name = @collection_item_loc_name,
         @parameters = @parameters,
         @frequency = @frequency, 
         @collection_item_id = @collection_item_id output;
   END;
   
   IF(FORMATMESSAGE(@name_id) IS NOT NULL)
   BEGIN
      -- for localization of collection item name
      UPDATE syscollector_collection_items_internal
      SET name_id = @name_id
      WHERE collection_item_id = @collection_item_id;
   END

   -- Turn the is_system flag on so users can't change the definition of this collection set 
   UPDATE syscollector_collection_sets
   SET is_system = 1
   WHERE collection_set_id = @collection_set_id

GO

----------------------------------------------------------------
-- An SP to get whether the instance is already running DC
-- OR not
----------------------------------------------------------------

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[fn_sysutility_mi_get_data_collector_status]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
BEGIN
   RAISERROR('Dropping [dbo].[fn_sysutility_mi_get_data_collector_status] procedure', 0, 1)  WITH NOWAIT;
   DROP FUNCTION [dbo].[fn_sysutility_mi_get_data_collector_status];
END
GO
RAISERROR('Creating [dbo].[fn_sysutility_mi_get_data_collector_status] procedure', 0, 1)  WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysutility_mi_get_data_collector_status]()
   RETURNS BIT
AS
BEGIN
   RETURN 
   (
    SELECT CAST (ISNULL (parameter_value, 0) AS bit) 
       FROM [msdb].[dbo].[syscollector_config_store_internal]
       WHERE parameter_name = 'CollectorEnabled'
   );
END
GO



--------------------------------------------------------
-- Utility collect and upload procedures and functions
--------------------------------------------------------


-------------------------------------------------------------------------------
-- Upload the collected data to the utility control point.
--
-- Specifics: 
--  The Utility uses the data collector to upload data to the UCP.  The
--  collection set corresponding to the Utility is set to on-demand mode.
--  The Utility directs the data collector to run the collect and upload cycle
--  through a Utility-owned job.  The Utility-owned job first stages data,
--  and in the final job step calls this procedure to direct DC to pick up
--  the staged data and upload it to the UCP.  
-------------------------------------------------------------------------------
IF OBJECT_ID ('[dbo].[sp_sysutility_mi_upload]') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_mi_upload]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE [dbo].[sp_sysutility_mi_upload] 
END;
RAISERROR ('Creating procedure [dbo].[sp_sysutility_mi_upload]', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysutility_mi_upload]
WITH EXECUTE AS OWNER
AS
BEGIN
   SET NOCOUNT ON;
   
   -- Check if the instance is enrolled
   IF ( 0 = (select [dbo].[fn_sysutility_ucp_get_instance_is_mi]()) )
   BEGIN
	  RAISERROR(37006, -1, -1)
     RETURN(1)
   END
   
   -- Check if Data Collector is enabled
   -- The following sproc will throw the correct error DC is disabled
   DECLARE @return_code INT;
   EXEC @return_code = [dbo].[sp_syscollector_verify_collector_state] @desired_state = 1
   IF (@return_code <> 0)
        RETURN (1)

   DECLARE @poll_delay_hh_mm_ss char(8)  = '00:00:10';
   DECLARE @start_delay_hh_mm_ss char(8)  = '00:00:10';
   
   DECLARE @start_time datetime2 = SYSUTCDATETIME();
   DECLARE @elapsed_time_ss INT
   
   DECLARE @collection_set_uid UNIQUEIDENTIFIER = N'ABA37A22-8039-48C6-8F8F-39BFE0A195DF';  
   DECLARE @collection_set_id INT = (SELECT collection_set_id FROM [dbo].[syscollector_collection_sets_internal]
                                    WHERE collection_set_uid = @collection_set_uid);
           
   DECLARE @is_upload_running INT;
   DECLARE @is_collection_running INT;

   -- If the collection set is running for some reason, wait for it
   -- to complete before instructing it to upload again.
   -- Assume that the collection is running before the loop starts
   SET @is_upload_running = 1;
   SET @is_collection_running = 1;
   
   -- Wait for the collection set to finish its previous execution        
   WHILE(1 = @is_collection_running OR 1 = @is_upload_running)
   BEGIN  
      -- Reset the while loop variables.  The sp will not update the values, if the collection
      -- is currently not running
      SET @is_upload_running = NULL;
      SET @is_collection_running = NULL;
      EXEC @return_code = [dbo].[sp_syscollector_get_collection_set_execution_status] 
            @collection_set_id = @collection_set_id,
            @is_collection_running = @is_collection_running OUTPUT,
            @is_upload_running = @is_upload_running OUTPUT
      IF (@@ERROR <> 0 OR @return_code <> 0) GOTO QuitWithError;

      -- Check to see if the collection is running before calling wait.
      -- It is more likely that it is not running, thus it is not optimal to wait.
      IF (1 = @is_collection_running OR 1 = @is_upload_running)
      BEGIN    
         SET @elapsed_time_ss = DATEDIFF(second, @start_time, SYSUTCDATETIME())
         RAISERROR ('Waiting for collection set to finish its previous run.  Total seconds spent waiting : %i', 0, 1, @elapsed_time_ss) WITH NOWAIT;
         WAITFOR DELAY @poll_delay_hh_mm_ss
      END

   END

   -- Grab the time before running the collection.  Use local time because later this value is used
   -- to find failures in the DC logs, which use local time.
   DECLARE @run_start_time datetime = SYSDATETIME();

   -- Start the collect and upload by invoking the run command
   RAISERROR ('Starting collection set.', 0, 1) WITH NOWAIT;
   EXEC @return_code = [msdb].[dbo].[sp_syscollector_run_collection_set] @collection_set_id = @collection_set_id
   IF (@@ERROR <> 0 OR @return_code <> 0) GOTO QuitWithError;    

   -- Allow the collection set to start
   RAISERROR ('Waiting for the collection set to kick off jobs.', 0, 1) WITH NOWAIT;
   WAITFOR DELAY @start_delay_hh_mm_ss
   
   -- Assume that the collection is running before the loop starts
   SET @is_upload_running = 1;
   SET @is_collection_running = 1;
   
   -- Wait for the collection set to finish it's previous execution        
   WHILE(1 = @is_collection_running OR 1 = @is_upload_running)
   BEGIN
      -- Reset the while loop variables.  The sp will not update the values, if the collection
      -- is currently not running
      SET @is_upload_running = NULL;
      SET @is_collection_running = NULL;
      
      -- Go ahead and wait on entry to the loop because it takes a 
      -- while for the collection set to finish collection
      SET @elapsed_time_ss = DATEDIFF(second, @start_time, SYSUTCDATETIME())
      RAISERROR ('Waiting for collection set to finish its previous run.  Total seconds spent waiting : %i', 0, 1, @elapsed_time_ss) WITH NOWAIT;
      WAITFOR DELAY @poll_delay_hh_mm_ss

      EXEC @return_code = [dbo].[sp_syscollector_get_collection_set_execution_status] 
            @collection_set_id = @collection_set_id,
            @is_collection_running = @is_collection_running OUTPUT,
            @is_upload_running = @is_upload_running OUTPUT
      IF (@@ERROR <> 0 OR @return_code <> 0) GOTO QuitWithError;
   END
   
   DECLARE @status_failure smallint = 2
   DECLARE @last_reported_status smallint = NULL
   
   -- Check if the collect/upload failed anytime after the call to run
   -- This is not precise in finding our exact run, but most of the time it will find our run
   -- What we really need to know is if the collect/upload failed
   -- There is a possibility that there are false positives (report failure, when our call to run passed)
   -- However, we are willing to risk it for simplicity.
   SELECT TOP 1 @last_reported_status = status 
   FROM msdb.dbo.syscollector_execution_log_internal 
      WHERE collection_set_id = @collection_set_id 
      AND parent_log_id IS NULL
      AND finish_time IS NOT NULL   
      AND start_time >= @run_start_time
      ORDER BY finish_time DESC

    IF (@last_reported_status = @status_failure)
    BEGIN
        RAISERROR(37007, -1, -1)
        RETURN(1) -- Failure
    END

   Return(0);
    
   QuitWithError:
      RAISERROR ('An error occurred during execution.', 0, 1) WITH NOWAIT;
      RETURN (1);
END
GO


IF  (OBJECT_ID(N'[dbo].[fn_sysutility_mi_get_collect_script]') IS NOT NULL)
BEGIN
   RAISERROR('Dropping [dbo].[fn_sysutility_mi_get_collect_script] function', 0, 1)  WITH NOWAIT;
   DROP FUNCTION [dbo].[fn_sysutility_mi_get_collect_script];
END
GO
RAISERROR('Creating [dbo].[fn_sysutility_mi_get_collect_script] function', 0, 1)  WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysutility_mi_get_collect_script]()
   RETURNS NVARCHAR(MAX)
AS
BEGIN     
   RETURN '[Void] [System.Reflection.Assembly]::LoadWithPartialName("System.Data")
[Void] [System.Reflection.Assembly]::LoadWithPartialName("System.Diagnostics")
[Void] [System.Reflection.Assembly]::LoadWithPartialName("System.Collections")

###############################################################################
# Powershell settings
###############################################################################

# Generate an error if attempt to access a nonexisting variable
Set-PsDebug -Strict

# Global settings for what to do on a error, warning, or verbose call
# Change these settings to change how this script writes output in the agent logs
# Settings also affects how SQL Agent reports success or failure in the script
# Options are:
#      Continue - Continue processing and notify the user 
#             - Agent reaction: step will report success, and
#                           log will include message
#      Inquire - Stop processing and ask the user how it should proceed 
#             - Agent reaction: step fails with "cannot invoke this function"
#                           the Agent PS provider does not implement this
#      SilentlyContinue - Continue processing without notifying the user
#                    - Agent reaction:  will not fail step 
#                                 and will not log any message
#      Stop - Stop processing when an action occurs
#          - Agent reaction: step will fail with message in log
$VerbosePreference = "SilentlyContinue"
$WarningPreference = "Continue"
$ErrorActionPreference = "Stop"

###############################################################################
# Global Variables
###############################################################################

# The following line uses SQL Agent tokens to set the server name
# ESCAPE_SQUOTE(SRVR) with a $ sign in front is a special token to SQL Agent
# When the job is run, SQL Agent will expand the string to the server name
# Use single quotes so that PS considers the string a literal and will not
# try to expand the $ reference and the script will not fail in a test environment
$serverName = ''$(ESCAPE_SQUOTE(SRVR))''

# Currently the best way to tell if the script is running in Agent
# is to check if the console is not the ConsoleHost.  The Powershell
# subsystem for Agent has no console and thus writing to the host directly
# does not show up in the Agent logs.
$isNotConsole = ($host.Name -ne "ConsoleHost")

$connection = $null
$transaction = $null
$isVistaOrXPSp2OrHigher = $null
$sleepTimeoutSeconds = 5
$directoryNameToDeviceId=$null

$cpuStageTableName = "[msdb].[dbo].[sysutility_mi_cpu_stage_internal]"
$cpuStageDataTable = $null
$cpuNumProcessorsColumnName = "num_processors"
$cpuNameColumnName = "cpu_name"
$cpuCaptionColumnName = "cpu_caption"
$cpuFamilyIdColumnName = "cpu_family_id"  
$cpuArchitectureIdColumnName = "cpu_architecture_id"
$cpuMaxClockSpeedColumnName = "cpu_max_clock_speed"
$cpuClockSpeedColumnName = "cpu_clock_speed"
$cpuL2CacheSizeColumnName = "l2_cache_size"
$cpuL3CacheSizeColumnName = "l3_cache_size"
# Start of collection column names
$cpuInstanceProcessorUsageStartTicks = "instance_processor_usage_start_ticks"
$cpuInstanceCollectTimeStartTicks = "instance_collect_time_start_ticks"
$cpuComputerProcessorIdleStartTicks = "computer_processor_idle_start_ticks"
$cpuComputerCollectTimeStartTicks = "computer_collect_time_start_ticks"
# End of collection column names
$cpuInstanceProcessorUsageEndTicks = "instance_processor_usage_end_ticks"
$cpuInstanceCollectTimeEndTicks = "instance_collect_time_end_ticks"
$cpuComputerProcessorIdleEndTicks = "computer_processor_idle_end_ticks"
$cpuComputerCollectTimeEndTicks = "computer_collect_time_end_ticks"


$volumeStageTableName = "[msdb].[dbo].[sysutility_mi_volumes_stage_internal]"
$volumeStageDataTable = $null
$volumeDeviceIdColumnName = "volume_device_id"
$volumeNameColumnName = "volume_name"
$volumeCapacityColumnName = "capacity_mb"
$volumeFreeSpaceColumnName = "free_space_mb"

$smoStageTableName = "[msdb].[dbo].[sysutility_mi_smo_stage_internal]"
$smoStageDataTable = $null
$smoTypeColumnName = "object_type"
$smoUrnColumnName = "urn"
$smoPropertyNameColumnName = "property_name"
$smoPropertyValueColumnName = "property_value"

###############################################################################
# Functions that help with handling output to SQL Agent
#
# Sql Agent PS provider does not write output to the log from
# the warnings, errors, and verbose Write cmdlets.  The following
# functions wrap these cmdlets for execution as an agent job step.
###############################################################################

# This function is a helper function throws an exception if the passed in object 
# is null or empty.  The intent is to mimic the PowerShell version 2.0 parameter 
# validation function with the same name. The paramter validation is available 
# in 2.0 or higher, but this script can run in 1.0 or 2.0 runtime environment.
function ValidateNotNullOrEmpty($object)
{
    if(($object -eq $null) -or ($object -eq ""))
    {
        throw "The argument is null or empty."
    }
}

# This function helps control control flow for the agent step context
# When running within agent, there are different semantics for writing
# errors, warnings, and messages.  In addition, when running inside an
# agent step, the script will automatically collect and stage data.
# However, if the script is loaded in a PS environment outside of 
# agent, the script will not automatically start to collect and stage data
#
# Returns True if the script is run inside an agent step
#         False if the script is run outside an agent step 
function Get-IsAgentStep
{
   $global:isNotConsole
}

function Write-AgentLog([String] $prefix, [String] $printString, [String] $preference)
{
   if((Get-IsAgentStep) -and ($preference -ne "SilentlyContinue"))
   {
      [Console]::Error.WriteLine($prefix + $printString)
   }
}

function Get-PrintString ($object)
{
   ValidateNotNullOrEmpty $object
   
   $date = Get-Date -DisplayHint Time
   $printString = $date.ToString() + " : " +  $object.ToString()
   $printString
}

function Write-ScriptVerbose ($object)
{
   $printString = Get-PrintString $object
   
   Write-AgentLog "VERBOSE : " $printString $VerbosePreference 
   Write-Verbose $printString
}


function Write-ScriptWarning ($object)
{
   $printString = Get-PrintString $object
   
   Write-AgentLog "WARNING : " $printString $WarningPreference 
   Write-Warning $printString
}

function Write-ScriptError ($object)
{
   $printString = Get-PrintString $object
   
   Write-AgentLog "ERROR : " $printString $ErrorActionPreference 
   Write-Error $printString   
}

function Resolve-Error ($ErrorRecord=$Error[0])
{
   $errorString = $ErrorRecord | Format-List * -Force  | Out-String
   Write-ScriptWarning $errorString
   $errorString = $ErrorRecord.InvocationInfo | Format-List * | Out-String
   Write-ScriptWarning $errorString
   $Exception = $ErrorRecord.Exception
   
   # Print the entire stack of exceptions
   for ($i = 0; $Exception; $i++, ($Exception = $Exception.InnerException))
   {   Write-ScriptWarning ("$i" * 80)
       $errorString = $Exception | Format-List * -Force  | Out-String
      Write-ScriptWarning $errorString
   }
}


###############################################################################
# Connection Functions help to send queries to and manage the connection 
# to the server .
###############################################################################
function Get-Connection
{
   if($global:serverName.Contains(''ESCAPE_SQUOTE(SRVR)''))
   {
      throw "The global variable serverName has not been set."
   }
   if($global:connection -eq $null)
   {
      Write-ScriptVerbose "Opening connection to $global:serverName"
      $connString="Application Name=SQL Server Utility Managed Instance;Server=$global:serverName;Database=msdb;Trusted_Connection=True;"
      
      $global:connection = New-Object System.Data.SqlClient.SqlConnection
      $global:connection.ConnectionString = $connString
      [Void]$global:connection.Open()
      Write-ScriptVerbose "Opened connection with connection string:`n $connString"
   }
   $global:connection
}

function Remove-Connection
{
   if($global:connection -ne $null)
   {
      $dataSource=$global:connection.DataSource
      Write-ScriptVerbose "Closing and disposing connection to $dataSource"
      [Void]$global:connection.Close()
      [Void]$global:connection.Dispose()
      Write-ScriptVerbose "Connection is closed and disposed"
   }
   $global:connection = $null
}

function Invoke-BeginTransaction([string] $tranName)
{
   Write-ScriptVerbose "Opening transaction"
   $sqlConnection = Get-Connection
   $global:transaction = $sqlConnection.BeginTransaction($tranName)
}

function Invoke-CommitTransaction
{
   if($global:transaction -ne $null)
   {
      Write-ScriptVerbose "Committing transaction"
      $global:transaction.Commit()
      $global:transaction.Dispose()
      $global:transaction = $null
   }
}

function Invoke-RollbackTransaction
{
   if($global:transaction -ne $null)
   {
      Write-ScriptVerbose "Rolling back transaction"
      $global:transaction.Rollback() 
      $global:transaction.Dispose()
      $global:transaction = $null
   }
}

function Invoke-SubmitSqlCommandNonQuery([string] $query)
{
   ValidateNotNullOrEmpty $query
      
   Write-ScriptVerbose "Submitting as NonQuery : $query"
   $TsqlCommand = New-Object System.Data.SqlClient.SqlCommand;
   $TsqlCommand.CommandText = $query
   $TsqlCommand.CommandType = "Text";
   $TsqlCommand.Transaction = $global:transaction
   
   $TsqlCommand.Connection = Get-Connection
   $TsqlCommand.CommandTimeout = 0
   [Void] $TsqlCommand.ExecuteNonQuery()
}

function Get-SqlDataTable([string] $query)
{
   ValidateNotNullOrEmpty $query
   
   Write-ScriptVerbose "Requesting data table for : $query"
   $sqlConnection = Get-Connection
    $dataAdapter = New-Object System.Data.SqlClient.SqlDataAdapter($query, $sqlConnection)
    $dataTable = New-Object System.Data.DataTable
    $rowsFilled = $dataAdapter.Fill($dataTable)
   Write-ScriptVerbose "Query added $rowsFilled rows to the data table"
    # return the data table.  We need to wrap the variable because PS will
   # return data rows otherwise.
   return @(,($dataTable))
} 

function Invoke-BulkCopyCommand([System.Data.DataTable] $dataTableData)
{
   ValidateNotNullOrEmpty $dataTableData
   
   $opt = [System.Data.SqlClient.SqlBulkCopyOptions] 

   # Obtain a TableLock
   # But do not (use) Default (options), KeepIdentity, CheckConstraints, KeepNulls
   #  FireTriggers,  UseInternalTransaction
   $bulkOptions = $opt::none -bxor ("TableLock" -as $opt) 

   $tabName=$dataTableData.TableName
   Write-ScriptVerbose "Bulk copying data table : $tabName"
   $sqlConnection = Get-Connection
   $bulkCopy = new-object Data.SqlClient.SqlBulkCopy $sqlConnection, $bulkOptions,  $global:transaction
   $bulkCopy.DestinationTableName = $dataTableData.TableName
   
   #Map the columns so that the computed columns are skipped in the upload
   foreach($col in $dataTableData.Columns)
   {
      [Void] $bulkCopy.ColumnMappings.Add($col.ColumnName, 
                                 $col.ColumnName)
   }
   [Void] $bulkCopy.WriteToServer($dataTableData)
   
}

###############################################################################
# Short Helper Functions 
###############################################################################

function Get-DefaultIfNull($object, $default)
{
    if($object -eq $null) 
    {
        $default
    } 
    else
    {
        $object
    }
}

function Get-StringDefaultIfNull([String] $object)
{
    Get-DefaultIfNull $object ""
}

function Get-NumericDefaultIfNull($object)
{
    Get-DefaultIfNull $object 0
}

function Get-ProcessId
{
   $result = Get-SqlDataTable "SELECT SERVERPROPERTY(''ProcessID'') AS ProcessId"  | %{ $_.Rows }
   $result.ProcessId
}

function Get-IsWmiVolumeQueryAvailable
{
   if($global:isVistaOrXPSp2OrHigher -eq $null)
   {
      $osVersion = [System.Environment]::OsVersion.Version
      $global:isVistaOrXPSp2OrHigher = ($osVersion.Major -ge 6 -or ($osVersion.Major -ge 5 -and $osVersion.Minor -ge 2))
   }
   Write-ScriptVerbose "This computer is Vista or XP Sp2 or higher value is $global:isVistaOrXPSp2OrHigher"
   $global:isVistaOrXPSp2OrHigher
}

# Trims the volume name to <drive_letter>: format. 
# Reason: Data collection using WMI on different OS returns diffrent volume formats
# E.g. Win32_LogicalDisk on WIN2K3 returns c: and Win32_Volume on WIN2K8 returns c:\

function Get-FormattedVolumeName([String] $volumeName)
{
   [String] $volumeName = Get-StringDefaultIfNull $volumeName
   
   Write-ScriptVerbose "Formatting volume name $volumeName"
   if($volumeName.EndsWith("\"))
   {
      $volumeName = $volumeName.SubString(0,$volumeName.Length - 1)
   }
   
   Write-ScriptVerbose "Formatted volume name to $volumeName"
   $volumeName
}

function Get-MountPointDictionary()
{
	if($global:directoryNameToDeviceId -eq $null)
	{
		$global:directoryNameToDeviceId=@{}
		(Get-Wmiobject Win32_MountPoint) | 
		%{ $directory=$_.Directory.Replace("Win32_Directory.Name=", "").Replace("`"", "").Replace("\\", "\")
		   $deviceId=$_.Volume.Replace("Win32_Volume.DeviceID=`"", "").Replace("`"", "").Replace("\\", "\")
		   $global:directoryNameToDeviceId[$directory]=$deviceId
		}
	}
	return $global:directoryNameToDeviceId
}

# The following function returns a directory name that maps to a volume device
# based on longest match.  It is not exact because a file can have a long
# convoluted path that pass through many mount point references
# However, it will find the most common use case for mount points
function Get-MountPointName([String] $fileName)
{
    [String] $fileName = Get-StringDefaultIfNull $fileName

    $longestMatch = ""
    $dict = Get-MountPointDictionary
    foreach($directory in $dict.Keys)
    {
        if($fileName.StartsWith($directory, [System.StringComparison]::OrdinalIgnoreCase))
        {
            if($directory.Length -gt $longestMatch.Length)
            {
                $longestMatch = $directory
            }
        }
    }
    return $longestMatch
}


function Get-DeviceIdFromMountPointName([String] $mountPointDirectory)
{
    [String] $mountPointDirectory = Get-StringDefaultIfNull $mountPointDirectory

    $dict = Get-MountPointDictionary
    
    $dict[$mountPointDirectory]
} 

function Get-MegabytesFromBytes ([Uint64] $bytes)
{
   [Uint64] $bytes = Get-NumericDefaultIfNull $bytes
      
   Write-ScriptVerbose "Converting $bytes bytes to megabytes"
   $oneMB = 1048576
   [UInt64] ($bytes / $oneMB)  # No fractional MBs
}

function Get-ShouldCollectCpu
{   
   if( ($global:cpuStageDataTable -eq $null) -or ($global:cpuStageDataTable.Rows.Count -eq 0))
   {
      Write-ScriptVerbose "The cpu staging table is null or empty.  Get-ShouldCollectCpu returning true"
      # return True and exit early
      return $true
   }
   else
   {
      $dataRow = $global:cpuStageDataTable.Rows[0]
      
      # return the value of the disjunction
      $dataRow[$cpuInstanceProcessorUsageStartTicks] -eq 0 -or
         $dataRow[$cpuInstanceCollectTimeStartTicks] -eq 0 -or
         $dataRow[$cpuComputerProcessorIdleStartTicks] -eq 0 -or
         $dataRow[$cpuComputerCollectTimeStartTicks] -eq 0
   }
}

###############################################################################
# Staging Functions that construct DataTables based on the different types of 
# data collection
###############################################################################
function Add-StageCpuRow
{
   param ([Int32] $numProcessors, [String] $cpuName, [String] $cpuCaption, [UInt16] $cpuFamily, 
      [UInt16] $architecture, [UInt32] $cpuMaxClockSpeed, [UInt32] $clockSpeed, 
      [UInt32] $l2CacheSize, [UInt32] $l3CacheSize, 
      [UInt64] $instanceProcessorUsage, [Int64] $instanceCollectTime, 
      [UInt64] $computerIdleTime, [UInt64] $computerCollectTime)

   begin
   {
      # This function update the Cpu table in-place by
      # first querying the server for the previous collection
      # information
      if($global:cpuStageDataTable -eq $null)
      {
         $query = "SELECT  
                  $cpuNumProcessorsColumnName,
                  $cpuNameColumnName,
                  $cpuCaptionColumnName,
                  $cpuFamilyIdColumnName,
                  $cpuArchitectureIdColumnName,
                  $cpuMaxClockSpeedColumnName,
                  $cpuClockSpeedColumnName,
                  $cpuL2CacheSizeColumnName,
                  $cpuL3CacheSizeColumnName,
                  $cpuInstanceProcessorUsageStartTicks,
                  $cpuInstanceCollectTimeStartTicks,
                  $cpuComputerProcessorIdleStartTicks,
                  $cpuComputerCollectTimeStartTicks,
                  $cpuInstanceProcessorUsageEndTicks,
                  $cpuInstanceCollectTimeEndTicks,
                  $cpuComputerProcessorIdleEndTicks,
                  $cpuComputerCollectTimeEndTicks
               FROM $global:cpuStageTableName"
         $global:cpuStageDataTable = Get-SqlDataTable $query
                  
         # If the data table is null, then there is no
         # data on the server and the table needs to be initialized
         if($global:cpuStageDataTable -eq $null)
         {
            Write-ScriptVerbose "Database returned no rows for cpu table. Creating table definition"
            $global:cpuStageDataTable = New-Object System.Data.DataTable ($global:cpuStageTableName)
            
            ($cpuNumProcessorsColumnName, [UInt16]), 
            ($cpuNameColumnName,[string]), 
            ($cpuCaptionColumnName,[string]), 
            ($cpuFamilyIdColumnName, [UInt16]), 
            ($cpuArchitectureIdColumnName, [UInt16]), 
            ($cpuMaxClockSpeedColumnName, [UInt32]), 
            ($cpuClockSpeedColumnName, [UInt32]), 
            ($cpuL2CacheSizeColumnName, [UInt32]), 
            ($cpuL3CacheSizeColumnName, [UInt32]), 
            ($cpuInstanceProcessorUsageStartTicks, [UInt64]), 
            ($cpuInstanceCollectTimeStartTicks, [Int64]), 
            ($cpuComputerProcessorIdleStartTicks, [UInt64]), 
            ($cpuComputerCollectTimeStartTicks, [UInt64]), 
            ($cpuInstanceProcessorUsageEndTicks, [UInt64]), 
            ($cpuInstanceCollectTimeEndTicks, [Int64]),
            ($cpuComputerProcessorIdleEndTicks, [UInt64]), 
            ($cpuComputerCollectTimeEndTicks, [UInt64]) | 
            foreach { ,
               $column =  new-object Data.DataColumn ($_)
               $global:cpuStageDataTable.Columns.Add($column) 
            }
         }
         $global:cpuStageDataTable.TableName = $global:cpuStageTableName
      }
      
      
      # If there is one row in the table, it is the data that the query returned
      # update the start values to be the old end values
      if ($global:cpuStageDataTable.Rows.Count -eq 1)
      {
         Write-ScriptVerbose "Stage table contains one row. Swapping end to start values."
         $dataRow = [System.Data.DataRow] $global:cpuStageDataTable.Rows[0]
   
         # The previous end values become the start values
         $dataRow[$cpuInstanceProcessorUsageStartTicks] = $dataRow[$cpuInstanceProcessorUsageEndTicks]
         $dataRow[$cpuInstanceCollectTimeStartTicks] = $dataRow[$cpuInstanceCollectTimeEndTicks]
         $dataRow[$cpuComputerProcessorIdleStartTicks] = $dataRow[$cpuComputerProcessorIdleEndTicks]
         $dataRow[$cpuComputerCollectTimeStartTicks] = $dataRow[$cpuComputerCollectTimeEndTicks]
      } 
      else
      {
         # There were no rows in the table or too many rows
         # Either way, the data needs to be cleared and updated
         # with the new information
         $rowCount = $global:cpuStageDataTable.Rows.Count
         Write-ScriptVerbose "Number of rows in data table is $rowCount"   
         
         Write-ScriptVerbose "Clearing stage table and marking start values with 0"
         [Void] $global:cpuStageDataTable.Clear()
         $dataRow = [System.Data.DataRow] $global:cpuStageDataTable.NewRow()
         $global:cpuStageDataTable.Rows.Add($dataRow)
         
         # There are no start values
         $dataRow[$cpuInstanceProcessorUsageStartTicks] = 0
         $dataRow[$cpuInstanceCollectTimeStartTicks] = 0
         $dataRow[$cpuComputerProcessorIdleStartTicks] = 0
         $dataRow[$cpuComputerCollectTimeStartTicks] = 0
      }
   }
   
   process
   {
      # Powershell 2.0 does not default typed parameters that are $null
      # So, the function has to set the defaults for the null parameters
      [Int32] $numProcessors = Get-NumericDefaultIfNull $numProcessors
      [String] $cpuName = Get-StringDefaultIfNull $cpuName
      [String] $cpuCaption = Get-StringDefaultIfNull $cpuCaption
      [UInt16] $cpuFamily = Get-NumericDefaultIfNull $cpuFamily
      [UInt16] $architecture = Get-NumericDefaultIfNull $architecture
      [UInt32] $cpuMaxClockSpeed = Get-NumericDefaultIfNull $cpuMaxClockSpeed
      [UInt32] $clockSpeed = Get-NumericDefaultIfNull $clockSpeed
      [UInt32] $l2CacheSize = Get-NumericDefaultIfNull $l2CacheSize
      [UInt32] $l3CacheSize = Get-NumericDefaultIfNull $l3CacheSize
      [UInt64] $instanceProcessorUsage = Get-NumericDefaultIfNull $instanceProcessorUsage
      [Int64] $instanceCollectTime = Get-NumericDefaultIfNull $instanceCollectTime
      [UInt64] $computerIdleTime = Get-NumericDefaultIfNull $computerIdleTime
      [UInt64] $computerCollectTime = Get-NumericDefaultIfNull $computerCollectTime
      
   
      # instanceCollectTime comes in as an signed int, make sure it is not neg
      if($instanceCollectTime -lt 0)
      {
         $instanceCollectTime = 0
      }
      
      # numProcessors comes in as an signed int, make sure it is not neg
      if($numProcessors -lt 0)
      {
         $numProcessors = 0
      }
   
      # Add the collected information
      Write-ScriptVerbose "Adding collected information to data table"
      $dataRow[$cpuNumProcessorsColumnName] = $numProcessors
      $dataRow[$cpuNameColumnName] = $cpuName
      $dataRow[$cpuCaptionColumnName] = $cpuCaption
      $dataRow[$cpuFamilyIdColumnName] = $cpuFamily
      $dataRow[$cpuArchitectureIdColumnName] = $architecture
      $dataRow[$cpuMaxClockSpeedColumnName] = $cpuMaxClockSpeed
      $dataRow[$cpuClockSpeedColumnName] = $clockSpeed
      $dataRow[$cpuL2CacheSizeColumnName] = $l2CacheSize
      $dataRow[$cpuL3CacheSizeColumnName] = $l3CacheSize
      $dataRow[$cpuInstanceProcessorUsageEndTicks] = $instanceProcessorUsage
      $dataRow[$cpuInstanceCollectTimeEndTicks] = $instanceCollectTime
      $dataRow[$cpuComputerProcessorIdleEndTicks] = $computerIdleTime
      $dataRow[$cpuComputerCollectTimeEndTicks] = $computerCollectTime
   }
}



function Add-StageVolumeRow
{ 
   param ([String]$deviceId,
         [String] $volumeNameRaw, 
         [UInt64] $capacityBytes, 
         [UInt64] $freeSpaceBytes)
   begin
   {
      # Initialize the stage table
      if($global:volumeStageDataTable -eq $null)
      {   
         Write-ScriptVerbose "Volume data table is null, creating table definition."
         $global:volumeStageDataTable = New-Object System.Data.DataTable ($global:volumeStageTableName)
            
         ($global:volumeDeviceIdColumnName, [String]), 
         ($global:volumeNameColumnName, [String]), 
         ($global:volumeCapacityColumnName, [UInt64]), 
         ($global:volumeFreeSpaceColumnName, [UInt64])| 
         foreach { ,
            $column =  new-object Data.DataColumn ($_)
            $global:volumeStageDataTable.Columns.Add($column) 
         }
      }
   }
   process
   {
      [String] $deviceId = Get-StringDefaultIfNull $deviceId
      [String] $formattedName = Get-FormattedVolumeName $volumeNameRaw
      [UInt64] $freeSpaceMB = Get-MegabytesFromBytes $freeSpaceBytes
      [UInt64] $capacityMB = Get-MegabytesFromBytes $capacityBytes
      
      if ( ($formattedName -eq "") -or ($deviceId -eq ""))
      {
         Write-ScriptWarning "DeviceId is empty string, or volume name formatting results in empty string.  Skipping this row."
         Write-ScriptWarning "Device Id = $deviceId. Volume name raw = $volumeNameRaw."
         return # return early
      }

      Write-ScriptVerbose "Adding collected information to data table"
      $dataRow = [System.Data.DataRow] $global:volumeStageDataTable.NewRow()
      $dataRow[$global:volumeNameColumnName] = $formattedName
      $dataRow[$global:volumeFreeSpaceColumnName] = $freeSpaceMB
      $dataRow[$global:volumeCapacityColumnName] = $capacityMB
      $dataRow[$global:volumeDeviceIdColumnName] = $deviceId
      Write-ScriptVerbose "Adding row to table"
      
      [Void] $global:volumeStageDataTable.Rows.Add($dataRow)
   }
}


function Add-StageSmoRow
{
   param ([Int32] $type, [String] $objUrn, [String] $propertyName, [object] $value)
   begin
   {      
      # Initialize the stage table
      if($global:smoStageDataTable -eq $null)
      {   
         Write-ScriptVerbose "Smo data table is null, creating table definition."
         $global:smoStageDataTable = New-Object System.Data.DataTable ($global:smoStageTableName)
         
         ($global:smoTypeColumnName, [Int32]), 
         ($global:smoUrnColumnName, [String]), 
         ($global:smoPropertyNameColumnName, [String]), 
         ($global:smoPropertyValueColumnName, [Object]) | 
         foreach { ,
            $column =  new-object Data.DataColumn ($_)
            $global:smoStageDataTable.Columns.Add($column) 
         }   
      }
   }
   process
   {  
      # if the type, propertyName, or Urn is null, something is wrong, throw an exception
      ValidateNotNullOrEmpty $type          
      ValidateNotNUllOrEmpty $propertyName
      ValidateNotNUllOrEmpty $objUrn
      
      # value can be null sometimes, which is fine.  Just throw the row out.
      if ( $value -eq $null )
      {
         Write-ScriptWarning "The value for property $propertyName is null. This property will not be added."
         Write-ScriptWarning "(objUrn = $objUrn)) (type = $type)) (propertyName = $propertyName)) (value = $value))"
         return # return early
      }
      
      Write-ScriptVerbose "Adding collected information for $propertyName to data table"
      $dataRow = [System.Data.DataRow] $global:smoStageDataTable.NewRow()
      $dataRow[$global:smoTypeColumnName] = $type
      $dataRow[$global:smoUrnColumnName] = $objUrn
      $dataRow[$global:smoPropertyNameColumnName] = $propertyName
      $dataRow[$global:smoPropertyValueColumnName] = $value
      
      $global:smoStageDataTable.Rows.Add($dataRow)
      
   }
}
   

###############################################################################
# Collection functions
###############################################################################
function Collect-CpuData
{

   &{ # PS Try
      # Get the Instance-level Performance Data.  An instance is identified 
      # by its process-id
      $processId = Get-ProcessId;
      
      Write-ScriptVerbose "Get WMI percent cpu time for process id = $processId"
      # Get the total processor time from the wmi object
      # PercentProcessorTime is bad property name, it is actually counting the 
      # total number of ticks (100NS based)
      # the instance has spent on processors.
      (Get-WmiObject Win32_PerfRawData_PerfProc_Process -filter "IDProcess = ''$processId''") | 
      %{ $instanceProcessorUsage = $_.PercentProcessorTime };
      
      Write-ScriptVerbose "Get current time for collection time"
      # Find the current number of ticks
      $instanceCollectTime = [DateTime]::UtcNow.Ticks
      
      Write-ScriptVerbose "Get WMI machine cpu time and time stamp"
      # Get the Machine-level Performance Data
      (Get-WmiObject Win32_PerfRawData_PerfOS_Processor -filter "Name = ''_Total''") |
      %{ $computerIdleTime = $_.PercentProcessorTime; 
      $computerCollectTime = $_.TimeStamp_Sys100NS };
      
      Write-ScriptVerbose "Get WMI cpu details"
      # Get the processor details
      (Get-WmiObject Win32_Processor) | 
      %{$cpuName = $_.Name;
      $cpuCaption = $_.Caption;
      $cpuFamily = $_.Family; 
      $architecture = $_.Architecture; 
      $cpuMaxClockSpeed = $_.MaxClockSpeed;
      $clockSpeed = $_.CurrentClockSpeed; 
      $l2CacheSize = $_.L2CacheSize; 
      $l3CacheSize = $_.L3CacheSize };
      
      [Int32] $numProcessors = [System.Environment]::ProcessorCount
      
      Write-ScriptVerbose "Add row to cpu information"
      Add-StageCpuRow   $numProcessors $cpuName $cpuCaption $cpuFamily $architecture $cpuMaxClockSpeed $clockSpeed $l2CacheSize $l3CacheSize $instanceProcessorUsage $instanceCollectTime $computerIdleTime $computerCollectTime
      
      $global:cpuStageDataTable
   }
   # PS Catch
   trap [Exception]
   {
      Resolve-Error
      Write-ScriptError "Caught exception while collecting cpu properties.  A WMI query might have failed."
   }   
   
}

function Collect-VolumeData
{
   &{ # PS Try
      if( Get-IsWmiVolumeQueryAvailable )
      {
         # A null DriveLetter indicates that the volume is a mount point
         # Casting DriveLetter to [Boolean] results in False if it is null 
         Write-ScriptVerbose "Collecting volume information using Win32_Volume"
         (Get-Wmiobject Win32_Volume -filter "DriveType = 3") | 
         %{ Add-StageVolumeRow $_.DeviceId $_.Name $_.Capacity $_.FreeSpace }
      }
      else
      {
         # logical disk only collects disk information, not mount point information
         # hence passing in false as is_mount_point parameter
         Write-ScriptVerbose "Collecting volume information using Win32_LogicalDisk"
         (Get-Wmiobject Win32_LogicalDisk -filter "DriveType = 3") | 
         %{ Add-StageVolumeRow $_.DeviceId $_.Name $_.Size $_.FreeSpace }
      }
      
      $global:volumeStageDataTable
   }
   # PS Catch
   trap [Exception]
   {
      Resolve-Error
      Write-ScriptError "Caught exception while collecting volume properties.  A WMI query might have failed."
   }   
}

function Collect-SmoData
{
   
   &{ # PS try
      $sqlConnection = Get-Connection
      $serverConnection = New-Object Microsoft.SqlServer.Management.Common.ServerConnection $sqlConnection
      $server = New-Object Microsoft.SqlServer.Management.Smo.Server($serverConnection);
      
      # remove configurations from this table
      $objectsQuery = "SELECT object_type, sfc_query 
               FROM [msdb].[dbo].[sysutility_mi_smo_objects_to_collect_internal] AS sfc_queries";
      
      $sfcQueries = Get-SqlDataTable $objectsQuery | %{ $_.Rows }
      
      foreach ($sfcQueryRow in $sfcQueries)
      {
         [Int32] $object_type = $sfcQueryRow.object_type;
         $sfcQueryString = $sfcQueryRow.sfc_query.ToString();
   
         Write-ScriptVerbose "Retrieving list of properties to collect"
         $propertiesQuery = "SELECT property_name 
                        FROM [msdb].[dbo].[sysutility_mi_smo_properties_to_collect_internal] 
                        WHERE object_type ="+ $object_type.ToString();
         $properties = Get-SqlDataTable $propertiesQuery | %{ $_.Rows } | foreach { $_.property_name };
         
         Write-ScriptVerbose "Collecting smo information for sfc query $sfcQueryString"
         $oq = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SfcObjectQuery($server);
         $exp = New-Object Microsoft.SqlServer.Management.Sdk.Sfc.SfcQueryExpression($sfcQueryString);
         
         &{ # PS try
         
            # The following call is not itempotent.  The code does not run the same
            # in debug mode. If you are running in debug mode, any value display
            # invalidates the foreach statement.
            $en = $oq.ExecuteIterator($exp, $null, $null);   
            
            foreach($obj in $en)
            {
               $objUrn = $obj.Urn.ToString();
               Write-ScriptVerbose "Collecting smo information for urn $objUrn"
               
               # For each property get the value and insert it into the smo stage data table
               # the statment $obj.$_ retrieves the propety value from the object
               # going through the PS provider.  If the property is not found or throws an
               # exception from the SMO side, the PS provider wraps the property and returns
               # an empty value.
               $properties | 
               %{ 
                  if ($_ -eq "ProcessorUsage")
                  {
                      # for ProcessorUsage, we are in fact collecting the
                      # the data by ourselves in our own staging table.
                      # and we do not want to call SMO as this property
                      # may not exist on downlevel server.
                      # so here, we put a dummy value and later during upload
                      # we replace it with our real value.
                      # Note that we a similar situation for VolumeFreeSpace
                      # but the solution is different. For VolumeFreeSpace property
                      # it is not put in the sysutility_mi_smo_properties_to_collect_internal
                      # and we collect through other means and then do a join on the UCP
                      # side, versus for ProcessorUsage, we put the property in the list
                      # and during MI collection, we replace it with our own value.
                      # The difference is inconsistent and we should change them to behave
                      # the same in future releases.
                      Add-StageSmoRow $object_type $objUrn $_ [object]0
                  }
                  else
                  {
                      Add-StageSmoRow $object_type $objUrn $_ $obj.$_ 
                  }
                  
                  # if this property is FileName, we append volume/mount point info.
                  if($_ -eq "FileName")
                  {
                     Write-ScriptVerbose "Property is FileName, getting volume information"
                     [String] $mountPointName = Get-MountPointName $obj.FileName
                     Add-StageSmoRow $object_type $objUrn "mount_point_name" $mountPointName 
                     
                     [String] $deviceId = Get-DeviceIdFromMountPointName $mountPointName
                     Add-StageSmoRow $object_type $objUrn "volume_device_id" $deviceId
                  }
               }
               
               $psPath = Convert-UrnToPath $objUrn
               ("powershell_path", $psPath),
               ("parent_name", $obj.Parent.Name),              # If no Parent exists, Ps will return null
               ("grandparent_name", $obj.Parent.Parent.Name) | # If no Parent.Parent exists, Ps will return null
                  %{ ,
                     $propertyName = $_[0]                     
                     [String] $value = $_[1]    # Cast to string results in $null values becoming ""
                     if($value -ne "")
                     {
                        Add-StageSmoRow $object_type $objUrn $propertyName $value
                     }
                  }  
            }
         } # PS catch exception
         trap [Exception]
         {
            Resolve-Error
            Write-ScriptError "Caught exception while collecting smo properties."
         }   
      }
      $global:smoStageDataTable
   } # PS catch exception
   trap [Exception]
   {
      Resolve-Error
          Write-ScriptError "Caught exception while collecting smo properties."
   }   
}

###############################################################################
# Functions that mange the server tables by clearing and loading collected data 
###############################################################################
function Clear-AllStagedData
{
   # TRUNCATE TABLE removes all rows from a table without logging the 
   # individual row deletes.

   $cpuClearQuery = "TRUNCATE TABLE $global:cpuStageTableName; "
   $volumeClearQuery = "TRUNCATE TABLE $global:volumeStageTableName; "
   $smoClearQuery = "TRUNCATE TABLE $global:smoStageTableName; "
   
   Invoke-SubmitSqlCommandNonQuery "$cpuClearQuery $volumeClearQuery $smoClearQuery"
}

function Collect-AllStagedData
{

   Collect-CpuData | Out-Null
      
   # Should we collect cpu data again?
   # This will happen if the script is
   # run when there is no data yet in
   # the cpu staging table.
   if(Get-ShouldCollectCpu)
   {
      #Wait for some time to pass
      Write-ScriptVerbose "Waiting $sleepTimeoutSeconds seconds to collect cpu data."
      Start-Sleep -Seconds $sleepTimeoutSeconds
      #Collect the data again
      Collect-CpuData | Out-Null
   }
   
   Collect-SmoData | Out-Null
   Collect-VolumeData | Out-Null
}

function Save-AllStagedData
{
   Invoke-BulkCopyCommand $global:cpuStageDataTable
   Invoke-BulkCopyCommand $global:volumeStageDataTable
   Invoke-BulkCopyCommand $global:smoStageDataTable
}

function Invoke-StageData
{

   &{ # Try 
      
      Collect-AllStagedData
      
      Invoke-BeginTransaction
      
      Clear-AllStagedData
      Save-AllStagedData
      
      Invoke-CommitTransaction
      Remove-Connection
   }
   trap [Exception] # Catch
   {
      Write-ScriptWarning "Error occurred during execution of script."
      Write-ScriptWarning "Transaction will be rolled back."
      
      Resolve-Error
      
      Invoke-RollbackTransaction
      Remove-Connection
      
      # With ErrorActionPreference=Stop the following line will stop the script
      Write-ScriptError "Error.  Transaction was rolled back"
   }
}

if(Get-IsAgentStep)
{
   Invoke-StageData
}'
END
GO


-------------------------------------------------------------------------------
-- Initialize the collection for the managed instance by creating the
-- sysutility_mi jobs that do data collection and upload to the UCP.
--
-- Specifics: 
--  If the job does not exists, create the job with the schedule
--  If a utility job exists
--     keep the job so that schedule and history are retained
--     drop the job steps and then recreate them
-------------------------------------------------------------------------------
IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[sp_sysutility_mi_initialize_collection]') AND type in (N'P', N'PC'))
BEGIN
   RAISERROR('Dropping [dbo].[sp_sysutility_mi_initialize_collection] procedure', 0, 1)  WITH NOWAIT;
   DROP PROCEDURE [dbo].[sp_sysutility_mi_initialize_collection];
END
GO
RAISERROR('Creating [dbo].[sp_sysutility_mi_initialize_collection] procedure', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysutility_mi_initialize_collection]
WITH EXECUTE AS OWNER
AS
BEGIN
   SET NOCOUNT ON;

   DECLARE @null_column sysname = NULL  

   IF ( 0 = (select [dbo].[fn_sysutility_ucp_get_instance_is_mi]()) )
   BEGIN
	  RAISERROR(37006, -1, -1)
     RETURN(1)
   END

   BEGIN TRY
   
   DECLARE @tran_name NVARCHAR(32) = N'sysutility_mi_initialize_collection' -- transaction names can be no more than 32 characters

   BEGIN TRANSACTION @tran_name

      -- Common variables
      DECLARE @job_category sysname       = N'Utility - Managed Instance';
      DECLARE @job_category_id INT        = (SELECT category_id FROM msdb.dbo.syscategories WHERE name=@job_category AND category_class=1)
      DECLARE @server_name sysname        = N'(local)';
      DECLARE @step_id INT;
      DECLARE @step_name sysname;

      -- Collect and upload job variables
      DECLARE @collect_and_upload_job_name sysname              = N'sysutility_mi_collect_and_upload';
      DECLARE @collect_and_upload_job_description nvarchar(max) = N'Collect configuration and performance information';
      DECLARE @collect_and_upload_schedule_name sysname         = N'sysutility_mi_collect_and_upload';
      DECLARE @collect_and_upload_schedule_minutes int          = 15;                                                              
      DECLARE @collect_and_upload_job_id uniqueidentifier       = (SELECT jobs.job_id
                                                                   FROM [msdb].[dbo].[sysjobs] jobs
                                                                   WHERE jobs.name = @collect_and_upload_job_name 
                                                                   AND jobs.category_id = @job_category_id);
                                                                     
      -- start the job one minute past midnight + some random set of minutes between the schedule interval
      -- for agent jobs, a schedule's time is encoded in an integer.  The minutes portion
      -- are stored in the the 100s and 1000s digits.
      DECLARE @collect_and_upload_schedule_start_time int       = CAST((1 + RAND() * (@collect_and_upload_schedule_minutes + 1)) AS INT) * 100; 
      -- end the job one minute before the start time
      DECLARE @collect_and_upload_schedule_end_time int         = @collect_and_upload_schedule_start_time - 100;
      
      -- Dac performance collection job variables
      DECLARE @dac_perf_job_name sysname              = N'sysutility_mi_collect_performance';
      DECLARE @dac_perf_job_description nvarchar(max) = N'Collect performance information';
      DECLARE @dac_perf_schedule_name sysname         = N'sysutility_mi_collect_performance';
      DECLARE @dac_perf_schedule_seconds int          = 15;
      DECLARE @dac_perf_job_id uniqueidentifier       = (SELECT jobs.job_id
                                                         FROM [msdb].[dbo].[sysjobs] jobs
                                                         WHERE jobs.name = @dac_perf_job_name 
                                                         AND jobs.category_id = @job_category_id);

      -------------------------------------------------------------------------
      -- Create the category for the jobs
      -------------------------------------------------------------------------
      IF (@job_category_id IS NULL)
      BEGIN
         RAISERROR('Creating utility job category ... %s', 0, 1, @job_category)  WITH NOWAIT;
         EXEC msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=@job_category
      END
                   
      -------------------------------------------------------------------------
      -- Prepare the jobs
      -------------------------------------------------------------------------
      IF (@collect_and_upload_job_id IS NULL)
      BEGIN
         RAISERROR('Creating utility job ... %s', 0, 1, @collect_and_upload_job_name)  WITH NOWAIT;
         -- The job doesn't exist yet, create the job
         EXEC msdb.dbo.sp_add_job 
            @job_name=@collect_and_upload_job_name,     
            @enabled=0,                               -- create the job disabled
            @notify_level_eventlog=0, 
            @notify_level_email=0, 
            @notify_level_netsend=0, 
            @notify_level_page=0, 
            @delete_level=0,
            @description=@collect_and_upload_job_description, 
            @category_name=@job_category, 
            @job_id = @collect_and_upload_job_id OUTPUT
      
         RAISERROR('Adding job to jobserver ... %s' , 0, 1, @collect_and_upload_job_name)  WITH NOWAIT;
         EXEC msdb.dbo.sp_add_jobserver @job_id = @collect_and_upload_job_id, @server_name = @server_name
      END
      ELSE
      BEGIN
         
         RAISERROR('Disabling utility job ... %s', 0, 1, @collect_and_upload_job_name)  WITH NOWAIT;
         -- Disable the job for now.  Disable is itempotent
         EXEC msdb.dbo.sp_update_job @job_id=@collect_and_upload_job_id, @enabled=0
         
         RAISERROR('Clearing job steps for utility job ... %s', 0, 1, @collect_and_upload_job_name)  WITH NOWAIT;
         -- The job exists, delete all of the job steps prior to recreating them
         -- Passing step_id = 0 to sp_delete_jobstep deletes all job steps for the job
         EXEC msdb.dbo.sp_delete_jobstep @job_id=@collect_and_upload_job_id, @step_id = 0
      END
      
      IF (@dac_perf_job_id IS NULL)
      BEGIN
         RAISERROR('Creating utility job ... %s', 0, 1, @dac_perf_job_name)  WITH NOWAIT;
         -- The job doesn't exist yet, create the job
         EXEC msdb.dbo.sp_add_job 
            @job_name=@dac_perf_job_name, 
            @enabled=0,                                -- create the job disabled
            @notify_level_eventlog=0, 
            @notify_level_email=0, 
            @notify_level_netsend=0, 
            @notify_level_page=0, 
            @delete_level=0,
            @description=@dac_perf_job_description, 
            @category_name=@job_category, 
            @job_id = @dac_perf_job_id OUTPUT
      
         RAISERROR('Adding job to jobserver ... %s' , 0, 1, @dac_perf_job_name)  WITH NOWAIT;
         EXEC msdb.dbo.sp_add_jobserver @job_id = @dac_perf_job_id, @server_name = @server_name
      END
      ELSE
      BEGIN
         RAISERROR('Disabling utility job ... %s', 0, 1, @dac_perf_job_name)  WITH NOWAIT;
         -- Disable the job for now.  Disable is itempotent
         EXEC msdb.dbo.sp_update_job @job_id=@dac_perf_job_id, @enabled=0
         
         RAISERROR('Clearing job steps for utility job ... %s', 0, 1, @dac_perf_job_name)  WITH NOWAIT;
         -- The job exists, delete all of the job steps prior to recreating them
         -- Passing step_id = 0 to sp_delete_jobstep deletes all job steps for the job
         EXEC msdb.dbo.sp_delete_jobstep @job_id=@dac_perf_job_id, @step_id = 0
      END
      
      -------------------------------------------------------------------------
      -- Add the schedules for the jobs
      -------------------------------------------------------------------------

      IF  NOT EXISTS (SELECT name FROM msdb.dbo.sysschedules_localserver_view WHERE name = @collect_and_upload_schedule_name)
      BEGIN
         RAISERROR('Creating schedule ... %s', 0, 1, @collect_and_upload_schedule_name)  WITH NOWAIT;
         EXEC dbo.sp_add_schedule
            @schedule_name = @collect_and_upload_schedule_name,            -- Schedule name
            @enabled=1,                                                    -- Enabled
            @freq_type = 4,                                                -- Daily
            @freq_interval = 1,                                            -- Recurs every 1 day
            @freq_subday_type = 0x4,                                       -- Frequency type is "minutes"
            @freq_subday_interval = @collect_and_upload_schedule_minutes,  -- Occurs every x minutes
            @active_start_time = @collect_and_upload_schedule_start_time,  -- Time to start the job
            @active_end_time = @collect_and_upload_schedule_end_time       -- Time to end the job
      END
      
      -- attach the schedule.  attach_schedule is itempotent if the job already has the schedule attached
      RAISERROR('Attaching schedule %s to job %s ...'  , 0, 1, @collect_and_upload_schedule_name, @collect_and_upload_job_name)  WITH NOWAIT;
      EXEC msdb.dbo.sp_attach_schedule @job_id=@collect_and_upload_job_id,@schedule_name=@collect_and_upload_schedule_name
      
      IF  NOT EXISTS (SELECT name FROM msdb.dbo.sysschedules_localserver_view WHERE name = @dac_perf_schedule_name)
      BEGIN
         RAISERROR('Creating schedule ... %s', 0, 1, @dac_perf_schedule_name)  WITH NOWAIT;
         EXEC dbo.sp_add_schedule
            @schedule_name = @dac_perf_schedule_name,            -- Schedule name
            @enabled=1,                                          -- Enabled
            @freq_type = 4,                                      -- Daily
            @freq_interval = 1,                                  -- Recurs every 1 day
            @freq_subday_type = 0x2,                             -- Frequency type is "seconds"
            @freq_subday_interval = @dac_perf_schedule_seconds   -- Occurs every x seconds
      END   
      
      -- attach the schedule.  attach_schedule is itempotent if the job already has the schedule attached
      RAISERROR('Attaching schedule %s to job %s ...'  , 0, 1, @dac_perf_schedule_name, @dac_perf_job_name)  WITH NOWAIT;
      EXEC msdb.dbo.sp_attach_schedule @job_id=@dac_perf_job_id,@schedule_name=@dac_perf_schedule_name     

      -------------------------------------------------------------------------
      -- Add the steps 
      -------------------------------------------------------------------------
      
      -------------------------------------------------------------------------      
      -- Steps for dac performance job
      -------------------------------------------------------------------------
      
      SET @step_id = 1;
      SET @step_name = N'Collect DAC execution statistics';
      RAISERROR('Adding step %i name %s to job %s', 0, 1, @step_id, @step_name, @dac_perf_job_name)  WITH NOWAIT;
      EXEC msdb.dbo.sp_add_jobstep 
         @job_id=@dac_perf_job_id, 
         @step_name=@step_name, 
         @step_id=1, 
         @cmdexec_success_code=0, 
         @on_success_action=1, 
         @on_fail_action=3, 
         @retry_attempts=0, 
         @retry_interval=0, 
         @os_run_priority=0, @subsystem=N'TSQL', 
         @command=N'EXEC [msdb].[dbo].[sp_sysutility_mi_collect_dac_execution_statistics_internal]', 
         @database_name=N'msdb', 
         @flags=0
            
      -------------------------------------------------------------------------      
      -- Steps for collect and upload job
      -------------------------------------------------------------------------
      
      -- Job step to record the current time on the managed instance.  This value will be included in the output of all of 
      -- the queries executed by the Utility collection set.  It will be used on the UCP to tie together all of the data from 
      -- a single execution of the data collection job. 
      -- 
      -- We create a table in tempdb to hold last batch start time and other transient data that does not 
      -- need to survive a service cycle.  Nothing uses this table except subsequent steps in this job; 
      -- it is safe to drop and recreate it here so that we do not need to worry about build-to-build 
      -- schema changes.
      
      SET @step_id = 1;
      SET @step_name = N'Record batch start time';
      RAISERROR('Adding step %i name %s to job %s', 0, 1, @step_id, @step_name, @collect_and_upload_job_name)  WITH NOWAIT;
      EXEC msdb.dbo.sp_add_jobstep @job_id=@collect_and_upload_job_id, @step_name=@step_name, 
            @step_id=@step_id, 
            @cmdexec_success_code=0, 
            @on_success_action=3, -- Go to next step
            @on_fail_action=2,    -- Quit the job reporting failure.  If something goes wrong here, something is messed up
            @retry_attempts=0, 
            @retry_interval=0, 
            @os_run_priority=0, 
            @subsystem=N'TSQL', 
            @command='
               USE tempdb
               
               IF OBJECT_ID (''[tempdb].[dbo].[sysutility_batch_time_internal]'') IS NOT NULL
               BEGIN
                  DROP TABLE [tempdb].[dbo].[sysutility_batch_time_internal];
               END;
               
               CREATE TABLE [tempdb].[dbo].[sysutility_batch_time_internal] (
                  latest_batch_time datetimeoffset(7) PRIMARY KEY NOT NULL
               );
                  
               -- The DC job needs to access the timestamp in this table, and it may not run under a login that 
               -- is mapped to a user in tempdb, so grant SELECT permissions to public.  The table contains no 
               -- sensitive data (only a single datetimeoffset value), so granting read permission to public 
               -- does create a security problem. 
               GRANT SELECT ON [tempdb].[dbo].[sysutility_batch_time_internal] TO PUBLIC;

               -- Save the start time for the current execution of the managed instance data collection job
               INSERT INTO [tempdb].[dbo].[sysutility_batch_time_internal] (latest_batch_time) VALUES (SYSDATETIMEOFFSET());', 
            @database_name=N'tempdb', 
            @flags=0
      
      DECLARE @psScript NVARCHAR(MAX) = (SELECT [dbo].[fn_sysutility_mi_get_collect_script]());
      SET @step_id = 2;
      SET @step_name = N'Stage Data Collected from PowerShell Script';  
      RAISERROR('Adding step %i name %s to job %s', 0, 1, @step_id, @step_name, @collect_and_upload_job_name)  WITH NOWAIT;
      EXEC msdb.dbo.sp_add_jobstep @job_id=@collect_and_upload_job_id, @step_name=@step_name, 
         @step_id=@step_id, 
         @cmdexec_success_code=0, 
         @on_success_action=3,   -- Go to next step
         @on_fail_action=2,      -- Quit the job reporting failure
         @retry_attempts=0, 
         @retry_interval=0, 
         @os_run_priority=0, 
         @subsystem=N'PowerShell', 
         @command=@psScript, 
         @database_name=N'master', 
         @flags=0

      SET @step_id = 3;
      SET @step_name = N'Upload to Utility Control Point';
      RAISERROR('Adding step %i name %s to job %s', 0, 1, @step_id, @step_name, @collect_and_upload_job_name)  WITH NOWAIT;
      EXEC msdb.dbo.sp_add_jobstep @job_id=@collect_and_upload_job_id, @step_name=@step_name, 
         @step_id=@step_id, 
         @cmdexec_success_code=0, 
         @on_success_action=1, -- Quit the job reporting success
         @on_fail_action=2, -- Quit the job reporting failure
         @retry_attempts=0, 
         @retry_interval=0, 
         @os_run_priority=0, 
         @subsystem=N'TSQL', 
         @command=N'EXEC [msdb].[dbo].[sp_sysutility_mi_upload]', 
         @database_name=N'msdb', 
         @flags=0
            
      -- Capture an initial snapshot of DAC statistics. This is not strictly necessary, but it will ensure that we 
      -- can calculate interval statistics immediately on the first execution of the every-15-second scheduled job. 
      RAISERROR('Collecting dac execution statistics for the first time ...', 0, 1, @collect_and_upload_job_name)  WITH NOWAIT;
      EXEC [msdb].[dbo].[sp_sysutility_mi_collect_dac_execution_statistics_internal]

      -- Enable the jobs
      RAISERROR('Enabling job ... %s', 0, 1, @collect_and_upload_job_name)  WITH NOWAIT;
      EXEC msdb.dbo.sp_update_job @job_id=@collect_and_upload_job_id, @enabled=1
      
      RAISERROR('Enabling job ... %s', 0, 1, @dac_perf_job_name)  WITH NOWAIT;   
      EXEC msdb.dbo.sp_update_job @job_id=@dac_perf_job_id, @enabled=1     
      
      -- Start the jobs
      RAISERROR('Starting job ... %s', 0, 1, @collect_and_upload_job_name)  WITH NOWAIT;
      EXEC msdb.dbo.sp_start_job @job_id=@collect_and_upload_job_id
      
      RAISERROR('Starting job ... %s', 0, 1, @dac_perf_job_name)  WITH NOWAIT;   
      EXEC msdb.dbo.sp_start_job @job_id=@dac_perf_job_id

  COMMIT TRANSACTION @tran_name
  
  END TRY
  
  BEGIN CATCH
        -- Roll back our transaction if it's still open
        IF (@@TRANCOUNT > 0)
        BEGIN
            ROLLBACK TRANSACTION;
        END;
 
        -- Rethrow the error.  Unfortunately, we can't retrow the exact same error number b/c RAISERROR 
        -- does not allow you to use error numbers below 13000.  We rethrow error 14684: 
        -- Caught error#: %d, Level: %d, State: %d, in Procedure: %s, Line: %d, with Message: %s
        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
        RAISERROR (14684, -1, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
    END CATCH;

END
GO

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[sp_sysutility_mi_disable_collection]') AND type in (N'P', N'PC'))
BEGIN
   RAISERROR('Dropping [dbo].[sp_sysutility_mi_disable_collection] procedure', 0, 1)  WITH NOWAIT;
   DROP PROCEDURE [dbo].[sp_sysutility_mi_disable_collection];
END
GO
RAISERROR('Creating [dbo].[sp_sysutility_mi_disable_collection] procedure', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysutility_mi_disable_collection]
WITH EXECUTE AS OWNER
AS
BEGIN
   SET NOCOUNT ON;
   
   BEGIN TRY
   DECLARE @tran_name NVARCHAR(32) = N'sysutility_mi_disable_colle' -- transaction names can be no more than 32 characters

   BEGIN TRANSACTION @tran_name
      DECLARE @job_category sysname       = N'Utility - Managed Instance';
      DECLARE @job_category_id INT        = (SELECT category_id FROM msdb.dbo.syscategories WHERE name=@job_category AND category_class=1)
      
      DECLARE @collect_and_upload_job_name sysname              = N'sysutility_mi_collect_and_upload';
      DECLARE @collect_and_upload_job_id uniqueidentifier       = (SELECT jobs.job_id
                                                                   FROM [msdb].[dbo].[sysjobs] jobs
                                                                   WHERE jobs.name = @collect_and_upload_job_name 
                                                                   AND jobs.category_id = @job_category_id);
                                                                     
      -- Dac performance collection job varaibles
      DECLARE @dac_perf_job_name sysname              = N'sysutility_mi_collect_performance';
      DECLARE @dac_perf_job_id uniqueidentifier       = (SELECT jobs.job_id
                                                         FROM [msdb].[dbo].[sysjobs] jobs
                                                         WHERE jobs.name = @dac_perf_job_name 
                                                         AND jobs.category_id = @job_category_id);

      IF(@collect_and_upload_job_id IS NOT NULL)
      BEGIN
         EXEC msdb.dbo.sp_update_job @job_id=@collect_and_upload_job_id, @enabled=0;
      END
      
      IF(@dac_perf_job_id IS NOT NULL)
      BEGIN
         EXEC msdb.dbo.sp_update_job @job_id=@dac_perf_job_id, @enabled=0;
      END
     		   
   COMMIT TRANSACTION @tran_name
   END TRY
   
   BEGIN CATCH
        -- Roll back our transaction if it's still open
        IF (@@TRANCOUNT > 0)
        BEGIN
            ROLLBACK TRANSACTION;
        END;
 
        -- Rethrow the error.  Unfortunately, we can't retrow the exact same error number b/c RAISERROR 
        -- does not allow you to use error numbers below 13000.  We rethrow error 14684: 
        -- Caught error#: %d, Level: %d, State: %d, in Procedure: %s, Line: %d, with Message: %s
        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');
        RAISERROR (14684, -1, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
    END CATCH;
   
END
GO

/**********************************************************************/
/*                                                                    */
/* Validate the instance can be used as a UCP.                        */
/*                                                                    */
/* Note that this function calls xp_qv, which requires that the       */
/* 'AgentXPs' sp_configure value be enabled. During upgrade this      */ 
/* setting will need to be manually enabled, since upgrade scripts    */
/* are executed while Agent is stopped.                               */
/**********************************************************************/

/*
Function [fn_sysutility_ucp_get_edition_is_ucp_capable_internal]
Returns 1 if this SQL instance's edition allows it to become a UCP. 
*/
IF OBJECT_ID ('dbo.fn_sysutility_ucp_get_edition_is_ucp_capable_internal') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping function [dbo].[fn_sysutility_ucp_get_edition_is_ucp_capable_internal]', 0, 1) WITH NOWAIT;
    DROP FUNCTION dbo.fn_sysutility_ucp_get_edition_is_ucp_capable_internal; 
END;
GO
RAISERROR ('Creating function [dbo].[fn_sysutility_ucp_get_edition_is_ucp_capable_internal]', 0, 1) WITH NOWAIT;
GO
CREATE FUNCTION dbo.fn_sysutility_ucp_get_edition_is_ucp_capable_internal ()
RETURNS bit
AS
BEGIN
   DECLARE @is_instance_ucp_capable bit = 1;
   -- The integer value below corresponds to a SQLBOOT property that identifies whether 
   -- the SKU supports the UCP feature.  
   DECLARE @sqlbootvalue int;
   EXEC @sqlbootvalue = master.dbo.xp_qv '1675385081', @@SERVICENAME;
   IF (@sqlbootvalue != 2)
   BEGIN
      SET @is_instance_ucp_capable = 0;
   END;
   RETURN @is_instance_ucp_capable;
END 
GO


/*
Procedure [sp_sysutility_ucp_validate_prerequisites]
The procedure validates that the local instance can be used as a UCP
*/
IF OBJECT_ID ('dbo.sp_sysutility_ucp_validate_prerequisites') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_ucp_validate_prerequisites]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_ucp_validate_prerequisites 
END;
GO
RAISERROR ('Creating procedure [dbo].[sp_sysutility_ucp_validate_prerequisites]', 0, 1) WITH NOWAIT;
GO

CREATE PROCEDURE [dbo].[sp_sysutility_ucp_validate_prerequisites]
WITH EXECUTE AS OWNER
AS
BEGIN
   IF (dbo.fn_sysutility_ucp_get_edition_is_ucp_capable_internal() = 1)
   BEGIN
      RAISERROR ('Instance is able to be used as a Utility Control Point.', 0, 1) WITH NOWAIT;
   END
   ELSE BEGIN
      DECLARE @edition nvarchar(128);
      SELECT @edition = CONVERT(nvarchar(128), SERVERPROPERTY('Edition'));
      RAISERROR(37004, -1, -1, @edition);
      RETURN(1);
   END;
END 
GO


/**********************************************************************/
/*                                                                    */
/* Run the process to turn the local instance into a UCP              */
/*                                                                    */
/**********************************************************************/
/*
Procedure [sp_sysutility_ucp_create]
The procedure runs the process that turns the local instance into a UCP
*/
IF OBJECT_ID ('dbo.sp_sysutility_ucp_create') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_ucp_create]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_ucp_create 
END;
GO


RAISERROR ('Creating procedure [dbo].[sp_sysutility_ucp_create]', 0, 1) WITH NOWAIT;
GO

 
CREATE PROCEDURE [dbo].[sp_sysutility_ucp_create]
WITH EXECUTE AS OWNER
AS
BEGIN
    /* Validate that the UCP can be created on the local instance. */
    EXEC [dbo].[sp_sysutility_ucp_validate_prerequisites]
END 
GO


/**********************************************************************/
/*                                                                    */
/* Validate the instance can be managed.                              */
/*                                                                    */
/**********************************************************************/
/*
Procedure [sp_sysutility_mi_validate_enrollment_preconditions]
The procedure validates that the local instance can be made managed
*/
IF OBJECT_ID ('dbo.sp_sysutility_mi_validate_enrollment_preconditions') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_mi_validate_enrollment_preconditions]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_mi_validate_enrollment_preconditions 
END;
GO


RAISERROR ('Creating procedure [dbo].[sp_sysutility_mi_validate_enrollment_preconditions]', 0, 1) WITH NOWAIT;
GO

 
CREATE PROCEDURE [dbo].[sp_sysutility_mi_validate_enrollment_preconditions]
WITH EXECUTE AS OWNER
AS
BEGIN
    /* Get the Edition value */
    DECLARE @edition NVARCHAR(64)
    SELECT @edition = Convert(NVARCHAR, SERVERPROPERTY('edition'))

    /* Check SQLBOOT to ensure this instance edition can be used as a UCP. */
    DECLARE @sqlbootvalue int

    EXEC @sqlbootvalue = master.dbo.xp_qv '3090395820', @@SERVICENAME
    IF (@sqlbootvalue = 2)
        RAISERROR ('Instance can be managed by a Utility Control Point.', 0, 1) WITH NOWAIT;
    ELSE
        RAISERROR(37005, -1, -1, @edition)
        RETURN(1)
END 
GO


/**********************************************************************/
/*                                                                    */
/* Run the process to make the local instance managed by a UCP.       */
/*                                                                    */
/**********************************************************************/
/*
Procedure [sp_sysutility_mi_enroll]
The procedure runs the process that makes the local instance managed by a UCP.
*/
IF OBJECT_ID ('dbo.sp_sysutility_mi_enroll') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_mi_enroll]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_mi_enroll
END;
GO


RAISERROR ('Creating procedure [dbo].[sp_sysutility_mi_enroll]', 0, 1) WITH NOWAIT;
GO

 
CREATE PROCEDURE [dbo].[sp_sysutility_mi_enroll]
WITH EXECUTE AS OWNER
AS
BEGIN
    /* Validate that the local instance can be managed by a UCP. */
    EXEC [dbo].[sp_sysutility_mi_validate_enrollment_preconditions]
END 
GO

/**********************************************************************/
/* Object types handled by the UCP                                    */
/*                                                                    */
/* We treat DACs and Databases as synonymous for this purpose         */
/**********************************************************************/
IF(OBJECT_ID(N'[dbo].[sysutility_ucp_supported_object_types_internal]', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table [dbo].[sysutility_ucp_supported_object_types_internal]', 0, 1) WITH NOWAIT;
    CREATE TABLE [sysutility_ucp_supported_object_types_internal] (
       [object_type]   INT,
       [object_name]   NVARCHAR(32),
       
       CONSTRAINT PK_sysutility_ucp_supported_object_types_internal
         PRIMARY KEY([object_type])
    )
    
    INSERT INTO [sysutility_ucp_supported_object_types_internal] VALUES (0, 'Utility')    
    INSERT INTO [sysutility_ucp_supported_object_types_internal] VALUES (1, 'Computer')
    INSERT INTO [sysutility_ucp_supported_object_types_internal] VALUES (2, 'Volume')
    INSERT INTO [sysutility_ucp_supported_object_types_internal] VALUES (3, 'Instance')
    INSERT INTO [sysutility_ucp_supported_object_types_internal] VALUES (4, 'Database')
    INSERT INTO [sysutility_ucp_supported_object_types_internal] VALUES (5, 'FileGroup')
    INSERT INTO [sysutility_ucp_supported_object_types_internal] VALUES (6, 'DataFile')
    INSERT INTO [sysutility_ucp_supported_object_types_internal] VALUES (7, 'LogFile')
            
END
GO

/**********************************************************************/
/* Create the managed instance table                                  */
/*                                                                    */
/*                                                                    */
/**********************************************************************/

IF(OBJECT_ID(N'[dbo].[sysutility_ucp_managed_instances_internal]', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table [dbo].[sysutility_ucp_managed_instances_internal]', 0, 1) WITH NOWAIT;
    CREATE TABLE [sysutility_ucp_managed_instances_internal] (
       instance_id int IDENTITY(1,1),
       instance_name sysname,
       virtual_server_name sysname,
              
       date_created datetimeoffset(7) NOT NULL default SYSDATETIMEOFFSET(),
       created_by sysname NOT NULL default SUSER_SNAME(),
       agent_proxy_account sysname NOT NULL,
       cache_directory nvarchar(520),
       management_state int NOT NULL default (0),
       
       CONSTRAINT [UQ_sysutility_ucp_mi_id] UNIQUE (instance_id ASC),
       CONSTRAINT [PK_sysutility_ucp_mi_name] PRIMARY KEY CLUSTERED (instance_name)
      ); 
END
GO


/**********************************************************************/
/* create the managed instance view                                   */
/*                                                                    */
/**********************************************************************/
IF(OBJECT_ID(N'[dbo].[sysutility_ucp_managed_instances]', 'V') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping view [dbo].[sysutility_ucp_managed_instances]', 0, 1) WITH NOWAIT;
    DROP VIEW [dbo].[sysutility_ucp_managed_instances]
END
GO


RAISERROR ('Creating view [dbo].[sysutility_ucp_managed_instances]...', 0, 1) WITH NOWAIT;
GO
CREATE VIEW [dbo].[sysutility_ucp_managed_instances]
AS
    SELECT     
        instance_id,
        instance_name,
        virtual_server_name,
        date_created,
        created_by,
        agent_proxy_account,
        cache_directory,
        management_state
    FROM [dbo].[sysutility_ucp_managed_instances_internal]
GO

/**********************************************************************/
/* Add managed instance to the store.                               */
/*                                                                    */
/*                                                                    */
/**********************************************************************/
/*
Procedure [sp_sysutility_ucp_add_mi]
This proc creates a new ManagedInstance in dbo.sysutility_ucp_managed_instances_internal table
 
Parameters: 
    @instance_name - 
    @agent_proxy_account - 
    @cache_directory - 
    @management_state - 
    @instance_id
*/


IF OBJECT_ID ('dbo.sp_sysutility_ucp_add_mi') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_ucp_add_mi]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_ucp_add_mi 
END;
GO


RAISERROR ('Creating procedure [dbo].[sp_sysutility_ucp_add_mi]', 0, 1) WITH NOWAIT;
GO

 
CREATE PROCEDURE [dbo].[sp_sysutility_ucp_add_mi]
   @instance_name sysname,
   @virtual_server_name sysname,
   @agent_proxy_account sysname,
   @cache_directory nvarchar(520),
   @management_state int,
   @instance_id int = NULL OUTPUT
WITH EXECUTE AS OWNER
AS
BEGIN
   SET NOCOUNT ON
   
   DECLARE @retval INT
   
   DECLARE @null_column nvarchar(600)
   SET @null_column = NULL

    IF (@instance_name IS NULL OR @instance_name = N'')
        SET @null_column = '@instance_name'
    ELSE IF (@virtual_server_name IS NULL OR @virtual_server_name = N'')
        SET @null_column = '@virtual_server_name'
    ELSE IF (@management_state IS NULL)
        SET @null_column = '@management_state'
    ELSE IF (@agent_proxy_account IS NULL OR @agent_proxy_account = N'')
        SET @null_column = '@agent_proxy_account'    

    -- @cache_directory can be null or empty
    

   IF @null_column IS NOT NULL
   BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_sysutility_ucp_add_mi')
        RETURN(1)
   END
   
   
    IF EXISTS (SELECT * FROM dbo.sysutility_ucp_managed_instances_internal WHERE (instance_name = @instance_name))
    BEGIN
        RAISERROR(34010, -1, -1, 'Managed_Instance', @instance_name)
        RETURN(1)
    END
       
    INSERT INTO [dbo].[sysutility_ucp_managed_instances_internal]
      (instance_name, virtual_server_name, agent_proxy_account, cache_directory, management_state)
    VALUES
      (@instance_name, @virtual_server_name, @agent_proxy_account, @cache_directory, @management_state)
      
       
    SELECT @retval = @@error
    SET @instance_id = SCOPE_IDENTITY()
    RETURN(@retval)
    
END 
GO


/**************************************************************************/
/* create the Utility Processing State table                              */
/* This table is a single-row table containing internal state information */
/* for UCP processing. The two columns that it stores currently are       */
/*   latest_processing_time: the time at which data from the "live" tables */
/*                          is copied over to the "cache" tables".        */
/*   latest_health_state_id: a sequence number that all the health_state  */
/*                          tables use to represent the "latest" health   */
/*                          state calculation.                            */
/*                                                                        */
/**************************************************************************/
IF(OBJECT_ID(N'[dbo].[sysutility_ucp_processing_state_internal]', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table [dbo].[sysutility_ucp_processing_state_internal]', 0, 1) WITH NOWAIT;
    CREATE TABLE [dbo].[sysutility_ucp_processing_state_internal] (
        latest_processing_time DATETIMEOFFSET(7),
        latest_health_state_id INT,
        next_health_state_id INT,
        
        [id] AS 1,
        
        CONSTRAINT CK_sysutility_ucp_processing_state_internal 
           CHECK (latest_health_state_id <= next_health_state_id),
        CONSTRAINT PK_sysutility_ucp_processing_state_internal 
           PRIMARY KEY([id])   -- enforce single row in this table
    );
    INSERT INTO [dbo].[sysutility_ucp_processing_state_internal](latest_processing_time, latest_health_state_id, next_health_state_id)
      VALUES (SYSDATETIMEOFFSET(), 0, 1);
END;
GO

/**********************************************************************/
/* create the Utility registration creation stored procedure           */
/*                                                                    */
/**********************************************************************/
IF OBJECT_ID ('dbo.sp_sysutility_ucp_initialize') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_ucp_initialize]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE [dbo].[sp_sysutility_ucp_initialize]
END;
GO

RAISERROR ('Creating procedure [dbo].[sp_sysutility_ucp_initialize]', 0, 1) WITH NOWAIT;
GO


CREATE PROCEDURE [dbo].[sp_sysutility_ucp_initialize] 
   @utility_name sysname,
   @mdw_database_name sysname,
   @description nvarchar(1024) = N''
WITH EXECUTE AS OWNER
AS
BEGIN
    DECLARE @retval INT
    DECLARE @null_column    sysname
    
    SET @null_column = NULL

    IF (@utility_name IS NULL OR @utility_name = N'')
        SET @null_column = '@utility_name'
    ELSE IF (@mdw_database_name IS NULL OR @mdw_database_name = N'')
        SET @null_column = '@mdw_database_name'



    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_sysutility_ucp_initialize')
        RETURN(1)
    END


    -- Make sure that the Utility wasn't already created
    DECLARE @utilityName sysname
    set @utilityName = (SELECT CAST (current_value as sysname) FROM msdb.dbo.sysutility_ucp_configuration_internal where name = 'UtilityName')
    

    IF (@utilityName IS NOT NULL AND @utilityName != N'')
    BEGIN
        RAISERROR(37003, -1, -1)
        RETURN(1)
    END


    IF NOT EXISTS (SELECT * FROM master.dbo.sysdatabases WHERE name = @mdw_database_name)
    BEGIN
        RAISERROR(37002, -1, -1, @mdw_database_name)
        RETURN(1)
    END

    UPDATE dbo.sysutility_ucp_configuration_internal
       SET current_value = @utility_name WHERE name = N'UtilityName'

    UPDATE dbo.sysutility_ucp_configuration_internal
       SET current_value = @mdw_database_name WHERE name = N'MdwDatabaseName'

    UPDATE dbo.sysutility_ucp_configuration_internal
       SET current_value = SYSDATETIMEOFFSET() WHERE name = N'UtilityDateCreated'

    UPDATE dbo.sysutility_ucp_configuration_internal
       SET current_value = SUSER_SNAME() WHERE name = N'UtilityCreatedBy'

    IF (@description IS NOT NULL AND @description != N'')
    BEGIN
       UPDATE dbo.sysutility_ucp_configuration_internal
          SET current_value = @description WHERE name = N'UtilityDescription'
    END

    DECLARE @utility_version SYSNAME
    set @utility_version = (SELECT CAST(current_value AS SYSNAME) FROM
                              [msdb].[dbo].[sysutility_ucp_configuration_internal]
                              WHERE name = N'UtilityVersion')

    ---- Add the UtilityVersion, UcpName and the UcpFriendlyName registry key values.
    EXEC master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Utility',
                                         N'UtilityVersion',
                                         N'REG_SZ',
                                         @utility_version

    EXEC master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Utility',
                                         N'UcpName',
                                         N'REG_SZ',
                                         @@SERVERNAME

    EXEC master.dbo.xp_instance_regwrite N'HKEY_LOCAL_MACHINE',
                                         N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Utility',
                                         N'UcpFriendlyName',
                                         N'REG_SZ',
                                         @utility_name

END
GO


/**********************************************************************/
/* Create procedure sp_sysutility_ucp_update_utility_configuration           */
/**********************************************************************/
IF OBJECT_ID ('[dbo].sp_sysutility_ucp_update_utility_configuration') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_ucp_update_utility_configuration]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE [dbo].sp_sysutility_ucp_update_utility_configuration 
END;
GO
RAISERROR ('Creating procedure [dbo].[sp_sysutility_ucp_update_utility_configuration]', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysutility_ucp_update_utility_configuration] 
   @name SYSNAME,
   @value SQL_VARIANT
WITH EXECUTE AS OWNER
AS
BEGIN

    DECLARE @retval INT
    DECLARE @null_column    SYSNAME
    
    SET @null_column = NULL

    IF (@name IS NULL OR @name = N'')
        SET @null_column = '@name'
    ELSE IF (@value IS NULL)
        SET @null_column = '@value'
    
    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_sysutility_ucp_update_utility_configuration')
        RETURN(1)
    END

    IF NOT EXISTS (SELECT 1 FROM dbo.sysutility_ucp_configuration_internal WHERE name = @name)
    BEGIN
        RAISERROR(14027, -1, -1, @name)
        RETURN(1)
    END

    UPDATE dbo.sysutility_ucp_configuration_internal SET current_value = @value WHERE name = @name
    
    SELECT @retval = @@error
    RETURN(@retval)
END
GO



/**********************************************************************/
/* Create procedure fn_sysutility_ucp_accepts_upload_schema_version   */
/*   This procedure is used to identify whether an MI's upload schema */
/*   is compatible with this UCP.  If a breaking change is introduced */
/*   that would cause an incompatibility between an MI and UCP.  This */
/*   function should be updated accordingly along with the            */
/*   mi_configuration view                                            */
/*                                                                    */
/* REASON CODES:                                                      */
/* -1  : Schema version is too low, upgrade MI                        */
/*  0  : Schema is accepted                                           */
/*  1  : Schema version is too high, upgrade UCP                      */
/**********************************************************************/
IF OBJECT_ID ('[dbo].fn_sysutility_ucp_accepts_upload_schema_version') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[fn_sysutility_ucp_accepts_upload_schema_version]', 0, 1) WITH NOWAIT;
    DROP FUNCTION [dbo].fn_sysutility_ucp_accepts_upload_schema_version 
END;
GO
RAISERROR ('Creating procedure [dbo].[fn_sysutility_ucp_accepts_upload_schema_version]', 0, 1) WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysutility_ucp_accepts_upload_schema_version] 
(
    @upload_schema_version INT
)
RETURNS INT
AS
BEGIN

   DECLARE @accepted_min_version INT = 100;
   DECLARE @accepted_max_version INT = 100;
   
   -- Assume that the version is compatable
   DECLARE @retvalue INT = 0;
   
   IF(@upload_schema_version < @accepted_min_version)
      SET @retvalue = -1
   ELSE IF(@upload_schema_version > @accepted_max_version)
      SET @retvalue = 1
      
   RETURN @retvalue
   
END
GO




/**********************************************************************/
/* Create the resource health policies table                          */
/**********************************************************************/

IF(OBJECT_ID(N'[dbo].[sysutility_ucp_health_policies_internal]', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table [dbo].[sysutility_ucp_health_policies_internal]', 0, 1) WITH NOWAIT;
    CREATE TABLE [dbo].[sysutility_ucp_health_policies_internal] (
        health_policy_id INT IDENTITY(1,1),
        policy_name SYSNAME NOT NULL,
        rollup_object_urn NVARCHAR(4000) NOT NULL,
        rollup_object_type INT NOT NULL,
        target_type INT NOT NULL,
        resource_type INT NOT NULL,
        utilization_type INT NOT NULL,
        utilization_threshold FLOAT NOT NULL,
        is_global_policy BIT DEFAULT 0
        
       CONSTRAINT [PK_sysutility_ucp_policies_internal_id] PRIMARY KEY CLUSTERED (health_policy_id ASC),
      ); 
      CREATE NONCLUSTERED INDEX [NCI_sysutility_resource_health_policies_urn_types] ON
                                [dbo].[sysutility_ucp_health_policies_internal]([rollup_object_type],
                                                                                     [target_type],
                                                                                     [resource_type],
                                                                                     [utilization_type],
                                                                                     [policy_name]) INCLUDE([rollup_object_urn]);
      
END
GO

/**********************************************************************/
/* Create the resource health policies view                           */
/**********************************************************************/
IF object_id(N'dbo.sysutility_ucp_policies', 'V') IS NOT NULL
    DROP VIEW dbo.sysutility_ucp_policies
GO
CREATE VIEW dbo.sysutility_ucp_policies 
AS
SELECT
    rhp.health_policy_id AS health_policy_id,
    p.policy_id AS policy_id,
    rhp.policy_name AS policy_name,
    rhp.rollup_object_type AS rollup_object_type,
    rhp.rollup_object_urn AS rollup_object_urn,
    rhp.target_type AS target_type,
    rhp.resource_type AS resource_type,
    rhp.utilization_type AS utilization_type,
    rhp.utilization_threshold AS utilization_threshold,
    rhp.is_global_policy AS is_global_policy
FROM [msdb].[dbo].[sysutility_ucp_health_policies_internal] rhp
INNER JOIN msdb.dbo.syspolicy_policies p ON p.name = rhp.policy_name
GO

/**********************************************************************/
/* Create procedure sp_sysutility_ucp_add_policy           */
/* Creates the resource health policy record for specified as input details */
/**********************************************************************/
IF OBJECT_ID ('dbo.sp_sysutility_ucp_add_policy') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_ucp_add_policy]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE sp_sysutility_ucp_add_policy 
END;
GO
RAISERROR ('Creating procedure [dbo].[sp_sysutility_ucp_add_policy]', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysutility_ucp_add_policy] 
   @policy_name SYSNAME,
   @rollup_object_type INT,
   @rollup_object_urn NVARCHAR(4000),
   @target_type INT,
   @resource_type INT,
   @utilization_type INT,
   @utilization_threshold FLOAT,
   @resource_health_policy_id INT = NULL OUTPUT
WITH EXECUTE AS OWNER
AS
BEGIN

    DECLARE @retval INT
    DECLARE @null_column    SYSNAME
    
    SET @null_column = NULL

    IF (@policy_name IS NULL OR @policy_name = N'')
        SET @null_column = '@policy_name'
    ELSE IF (@rollup_object_type IS NULL OR @rollup_object_type < 1 OR @rollup_object_type > 3)
        SET @null_column = '@rollup_object_type'
    ELSE IF (@rollup_object_urn IS NULL OR @rollup_object_urn = N'')
        SET @null_column = '@rollup_object_urn'
    ELSE IF (@target_type IS NULL OR @target_type < 1 OR @target_type > 6)
        SET @null_column = '@target_type'
    ELSE IF (@resource_type IS NULL OR @resource_type < 1 OR @resource_type > 5)
        SET @null_column = '@resource_type'
    ELSE IF (@utilization_type IS NULL OR @utilization_type < 1 OR @utilization_type > 2)
        SET @null_column = '@utilization_type'
    ELSE IF (@utilization_threshold IS NULL OR @utilization_threshold < 0 OR @utilization_threshold > 100)
        SET @null_column = '@utilization_threshold'       
    
    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_sysutility_ucp_add_policy')
        RETURN(1)
    END

    IF NOT EXISTS (SELECT * FROM dbo.syspolicy_policies WHERE name = @policy_name)
    BEGIN
        RAISERROR(14027, -1, -1, @policy_name)
        RETURN(1)
    END

    INSERT INTO dbo.sysutility_ucp_health_policies_internal(policy_name, rollup_object_type, rollup_object_urn, target_type, resource_type, utilization_type, utilization_threshold)
    VALUES(@policy_name, @rollup_object_type, @rollup_object_urn, @target_type, @resource_type, @utilization_type, @utilization_threshold)
    
    SELECT @retval = @@error
    SET @resource_health_policy_id = SCOPE_IDENTITY()
    RETURN(@retval)
END
GO

/**********************************************************************/
/* Create procedure sp_sysutility_ucp_delete_policy        */
/* Deletes the resource health policy record for the id specified as input */
/**********************************************************************/
IF OBJECT_ID ('dbo.sp_sysutility_ucp_delete_policy') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_ucp_delete_policy]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE sp_sysutility_ucp_delete_policy 
END;
GO
RAISERROR ('Creating procedure [dbo].[sp_sysutility_ucp_delete_policy]', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysutility_ucp_delete_policy] 
   @resource_health_policy_id INT
WITH EXECUTE AS OWNER
AS
BEGIN

    DECLARE @retval INT
    DECLARE @null_column    SYSNAME
    
    SET @null_column = NULL

    IF (@resource_health_policy_id IS NULL OR @resource_health_policy_id = 0)
        SET @null_column = '@resource_health_policy_id'

    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_sysutility_ucp_delete_policy')
        RETURN(1)
    END

    IF NOT EXISTS (SELECT * FROM dbo.sysutility_ucp_health_policies_internal WHERE health_policy_id = @resource_health_policy_id AND is_global_policy = 0)
    BEGIN
        RAISERROR(22981, -1, -1)
        RETURN(1)
    END

    DELETE dbo.sysutility_ucp_health_policies_internal
    WHERE health_policy_id = @resource_health_policy_id
    
    SELECT @retval = @@error
    RETURN(@retval)
END
GO

/**********************************************************************/
/* Create procedure sp_sysutility_ucp_update_policy         */
/* Updates the resource health policy record with input utilization threshold */
/**********************************************************************/
IF OBJECT_ID ('dbo.sp_sysutility_ucp_update_policy') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_ucp_update_policy]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE sp_sysutility_ucp_update_policy 
END;
GO
RAISERROR ('Creating procedure [dbo].[sp_sysutility_ucp_update_policy]', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysutility_ucp_update_policy] 
   @resource_health_policy_id INT
   , @utilization_threshold INT
WITH EXECUTE AS OWNER
AS
BEGIN

    DECLARE @retval INT
    DECLARE @null_column    SYSNAME
    
    SET @null_column = NULL

    IF (@resource_health_policy_id IS NULL OR @resource_health_policy_id = 0)
        SET @null_column = '@resource_health_policy_id'
    ELSE IF (@utilization_threshold IS NULL OR @utilization_threshold < 0 OR @utilization_threshold > 100)
        SET @null_column = '@utilization_threshold'    

    IF @null_column IS NOT NULL
    BEGIN
        RAISERROR(14043, -1, -1, @null_column, 'sp_sysutility_ucp_update_policy')
        RETURN(1)
    END

    IF NOT EXISTS (SELECT * FROM dbo.sysutility_ucp_health_policies_internal WHERE health_policy_id = @resource_health_policy_id)
    BEGIN
        RAISERROR(22981, -1, -1)
        RETURN(1)
    END
    
    UPDATE dbo.sysutility_ucp_health_policies_internal
    SET utilization_threshold = @utilization_threshold
    WHERE health_policy_id = @resource_health_policy_id
    
    SELECT @retval = @@error
    RETURN(@retval)
END
GO

/**********************************************************************/
/* Create the sysutility_ucp_policy_check_conditions_internal table         */
/* This table contains metadata information to build the check condition */
/* Following are the values supported by each of the below attributes */
/* target_type: Computer - 1, DataFile - 2, LogFile - 3, Server - 4, DeployedDac - 5, Volume - 6 */ 
/* resource_type: StorageSpace - 1, StorageIO - 2, Processor - 3, Memory - 4, NetworkIO - 5 */
/* utilization_type: UnderUtilization - 1, OverUtilization - 2 */
/* operator_type: = AND - 1, OR - 2, - 3, != - 4, < - 5, > - 6, <= - 7, >= - 8 */ 
/**********************************************************************/
IF(OBJECT_ID(N'[dbo].[sysutility_ucp_policy_check_conditions_internal]', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table [dbo].[sysutility_ucp_policy_check_conditions_internal]', 0, 1) WITH NOWAIT;
    CREATE TABLE [dbo].[sysutility_ucp_policy_check_conditions_internal] (
        target_type INT NOT NULL,
        resource_type INT NOT NULL,
        utilization_type INT NOT NULL,
        facet_name SYSNAME NOT NULL,
        attribute_name SYSNAME NOT NULL,
        operator_type INT NOT NULL,
        property_name SYSNAME NOT NULL
        
       CONSTRAINT [PK_sysutility_ucp_policy_check_condition_internal_type] PRIMARY KEY CLUSTERED (resource_type, target_type, utilization_type, facet_name, attribute_name  ASC),
      ); 
END
GO

/**********************************************************************/
/* Create the sysutility_ucp_policy_check_conditions                   */
/**********************************************************************/
IF object_id(N'dbo.sysutility_ucp_policy_check_conditions', 'V') IS NOT NULL
    DROP VIEW dbo.sysutility_ucp_policy_check_conditions
GO
CREATE VIEW dbo.sysutility_ucp_policy_check_conditions 
AS
SELECT
    cc.target_type AS target_type,
    cc.resource_type AS resource_type,
    cc.utilization_type AS utilization_type,
    cc.facet_name AS facet_name,
    cc.attribute_name AS attribute_name,
    cc.operator_type AS operator_type,
    cc.property_name AS property_name
FROM msdb.[dbo].[sysutility_ucp_policy_check_conditions_internal] cc
GO

DELETE FROM msdb.dbo.sysutility_ucp_policy_check_conditions_internal
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_check_conditions_internal VALUES(1, 3, 1, 'Computer', 'ProcessorUtilization', 8, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_check_conditions_internal VALUES(1, 3, 2, 'Computer', 'ProcessorUtilization', 7, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_check_conditions_internal VALUES(2, 1, 1, 'IDataFilePerformanceFacet', 'SpaceUtilization', 8, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_check_conditions_internal VALUES(2, 1, 2, 'IDataFilePerformanceFacet', 'SpaceUtilization', 7, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_check_conditions_internal VALUES(3, 1, 1, 'ILogFilePerformanceFacet', 'SpaceUtilization', 8, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_check_conditions_internal VALUES(3, 1, 2, 'ILogFilePerformanceFacet', 'SpaceUtilization', 7, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_check_conditions_internal VALUES(4, 3, 1, 'Server', 'ProcessorUsage', 8, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_check_conditions_internal VALUES(4, 3, 2, 'Server', 'ProcessorUsage', 7, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_check_conditions_internal VALUES(5, 3, 1, 'DeployedDac', 'ProcessorUtilization', 8, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_check_conditions_internal VALUES(5, 3, 2, 'DeployedDac', 'ProcessorUtilization', 7, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_check_conditions_internal VALUES(6, 1, 1, 'Volume', 'TotalSpaceUtilization', 8, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_check_conditions_internal VALUES(6, 1, 2, 'Volume', 'TotalSpaceUtilization', 7, 'UtilizationThreshold')
GO

/**********************************************************************/
/* Create the sysutility_ucp_policy_target_conditions_internal table        */
/* This table contains metadata information to build the target set condition */
/* Following are the values supported by each of the below attributes */
/* rollup_object_type: DeployedDac - 1, ManagedInstance - 2, Computer - 3 */
/* target_type: Computer - 1, DataFile - 2, LogFile - 3, Server - 4, DeployedDac - 5, Volume - 6 */ 
/* resource_type: StorageSpace - 1, StorageIO - 2, Processor - 3, Memory - 4, NetworkIO - 5 */
/* utilization_type: UnderUtilization - 1, OverUtilization - 2 */
/* operator_type: = AND - 1, OR - 2, - 3, != - 4, < - 5, > - 6, <= - 7, >= - 8 */ 
/**********************************************************************/
IF(OBJECT_ID(N'[dbo].[sysutility_ucp_policy_target_conditions_internal]', 'U') IS NULL)
BEGIN
    RAISERROR ( 'Creating table [dbo].[sysutility_ucp_policy_target_conditions_internal]', 0, 1) WITH NOWAIT;
    CREATE TABLE [dbo].[sysutility_ucp_policy_target_conditions_internal] (
        rollup_object_type INT NOT NULL,
        target_type INT NOT NULL,
        resource_type INT NOT NULL,
        utilization_type INT NOT NULL,
        facet_name SYSNAME NOT NULL,
        attribute_name SYSNAME NOT NULL,
        operator_type INT NOT NULL,
        property_name SYSNAME NOT NULL
        
       CONSTRAINT [PK_sysutility_ucp_policy_target_condition_internal_type] PRIMARY KEY CLUSTERED (rollup_object_type, resource_type, target_type, utilization_type, facet_name, attribute_name  ASC),
      ); 
END
GO

/**********************************************************************/
/* Create the sysutility_ucp_policy_target_conditions                  */
/**********************************************************************/
IF object_id(N'dbo.sysutility_ucp_policy_target_conditions', 'V') IS NOT NULL
    DROP VIEW dbo.sysutility_ucp_policy_target_conditions
GO
CREATE VIEW dbo.sysutility_ucp_policy_target_conditions 
AS
SELECT
    tc.rollup_object_type AS rollup_object_type,
    tc.target_type AS target_type,
    tc.resource_type AS resource_type,
    tc.utilization_type AS utilization_type, 
    tc.facet_name AS facet_name,
    tc.attribute_name AS attribute_name,
    tc.operator_type as operator_type,
    tc.property_name as property_name
FROM msdb.[dbo].[sysutility_ucp_policy_target_conditions_internal] tc
GO

DELETE FROM msdb.dbo.sysutility_ucp_policy_target_conditions_internal
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(1, 5, 3, 0, 'DeployedDac', 'ServerInstanceName', 3, 'DacServerInstanceName')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(1, 5, 3, 0, 'DeployedDac',	'Name', 3, 'DacName')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(1, 5, 3, 1, 'DeployedDac', 'ProcessorUtilization', 5, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(1, 5, 3, 2, 'DeployedDac', 'ProcessorUtilization', 6, 'UtilizationThreshold')
GO
					
INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(1, 2, 1, 0, 'Server', 'InstanceName', 3, 'DacInstanceName')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(1, 2, 1, 0, 'Server', 'NetName', 3, 'DacComputerName')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(1, 2, 1, 0, 'Database', 'Name', 3, 'DacDatabaseName')
GO
					
INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(1, 3, 1, 0, 'Server', 'InstanceName', 3, 'DacInstanceName')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(1, 3, 1, 0, 'Server', 'NetName', 3, 'DacComputerName')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(1, 3, 1, 0, 'Database', 'Name', 3, 'DacDatabaseName')
GO
					
INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(2, 4, 3, 0, 'Server', 'InstanceName', 3, 'ServerInstanceName')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(2, 4, 3, 0, 'Server', 'NetName', 3, 'ServerNetName')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(2, 4, 3, 1, 'Server', 'ProcessorUsage', 5, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(2, 4, 3, 2, 'Server', 'ProcessorUsage', 6, 'UtilizationThreshold')
GO
					
INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(2, 2, 1, 0, 'Server', 'InstanceName', 3, 'ServerInstanceName')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(2, 2, 1, 0, 'Server', 'NetName', 3, 'ServerNetName')
GO
					
INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(2, 3, 1, 0, 'Server', 'InstanceName', 3, 'ServerInstanceName')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(2, 3, 1, 0, 'Server', 'NetName', 3, 'ServerNetName')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(3, 1, 3, 0, 'Computer', 'Name', 3, 'ComputerName')
GO
					
INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(3, 1, 3, 1, 'Computer', 'ProcessorUtilization', 5, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(3, 1, 3, 2, 'Computer', 'ProcessorUtilization', 6, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(3, 6, 1, 0, 'Computer', 'Name', 3, 'ComputerName')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(3, 6, 1, 1, 'Volume', 'TotalSpaceUtilization', 5, 'UtilizationThreshold')
GO

INSERT INTO msdb.dbo.sysutility_ucp_policy_target_conditions_internal VALUES(3, 6, 1, 2, 'Volume', 'TotalSpaceUtilization', 6, 'UtilizationThreshold')
GO

/**********************************************************************/
/* Create table sysutility_ucp_policy_violations_internal             */
/* This table stores violations for health polices from latest policy evaluation */
/**********************************************************************/
IF(OBJECT_ID(N'dbo.sysutility_ucp_policy_violations_internal', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table dbo.sysutility_ucp_policy_violations_internal', 0, 1) WITH NOWAIT;
    CREATE TABLE sysutility_ucp_policy_violations_internal
    (
        health_policy_id INT NOT NULL,
        policy_id INT NOT NULL, 
        policy_name SYSNAME NULL,
        history_id INT NOT NULL,
        detail_id INT NOT NULL,
        target_query_expression NVARCHAR(MAX) NULL,
        target_query_expression_with_id NVARCHAR(MAX) NULL,
        execution_date DATETIME NULL,
        result INT NULL,

        CONSTRAINT [PK_sysutility_ucp_policy_violations_internal] 
          PRIMARY KEY CLUSTERED (policy_id, history_id, detail_id)
    )
END
GO

/**********************************************************************/
/* Create the health policy violations view                           */
/* This view fetches violations for health polices from latest policy evaluation */
/**********************************************************************/
IF object_id(N'dbo.sysutility_ucp_policy_violations', 'V') IS NOT NULL
   DROP VIEW dbo.sysutility_ucp_policy_violations
GO

CREATE VIEW dbo.sysutility_ucp_policy_violations 
AS
    SELECT pv.health_policy_id
        , pv.policy_id
        , pv.policy_name
        , pv.history_id
        , pv.detail_id
        , pv.target_query_expression
        , pv.target_query_expression_with_id
        , pv.execution_date
        , pv.result
    FROM dbo.sysutility_ucp_policy_violations_internal pv
GO

/**********************************************************************/
/* Create procedure sp_sysutility_ucp_get_policy_violations           */
/* This SP is used to fetch the violations for health polices from    */
/* latest policy evaluation and cache them in the intermediate table  */
/**********************************************************************/

IF OBJECT_ID ('dbo.sp_sysutility_ucp_get_policy_violations') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure dbo.sp_sysutility_ucp_get_policy_violations', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_ucp_get_policy_violations 
END;
GO

RAISERROR ('Creating procedure dbo.sp_sysutility_ucp_get_policy_violations', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE dbo.sp_sysutility_ucp_get_policy_violations 
WITH EXECUTE AS OWNER
AS
BEGIN
    -- Clear the existing policy violations        
    TRUNCATE TABLE dbo.sysutility_ucp_policy_violations_internal
    
    -- Cache the latest policy violations for non-volatile resources
    -- The health state for non-volatile resource is determined based on 
    -- the latest policy violation against the target (file, volume) type.
    INSERT INTO dbo.sysutility_ucp_policy_violations_internal
    SELECT p.health_policy_id
        , p.policy_id
        , p.policy_name
        , d.history_id
        , d.detail_id
        , d.target_query_expression
        , d.target_query_expression_with_id
        , d.execution_date
        , d.result
    FROM msdb.dbo.sysutility_ucp_policies p
    INNER JOIN msdb.dbo.syspolicy_policy_execution_history_internal h 
        ON h.policy_id = p.policy_id
    INNER JOIN msdb.dbo.syspolicy_policy_execution_history_details_internal d 
        ON d.history_id = h.history_id
    WHERE p.resource_type = 1 -- Filter non-volatile resources (currently storage type only)   
        -- PBM stores the end_date in local time so convert the 'latest_processing_time' datetimeoffset to local datetime before compare
        AND h.end_date >= (SELECT CONVERT(DATETIME, latest_processing_time) FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal]) 
        AND h.is_full_run = 1
        AND h.result = 0
        AND d.result = 0; 
        
    -- Get the policy evaluation count for volatile resources over the trailing window. 
    -- The health state for volatile resource is determined based on the policy 
    -- violation against the target (cpu) type over a trailing window and should
    -- exeed the occurrence frequency percent. E.g. a tartget can be considered
    -- as over utilized if its violating the policy for last 3 out of 4 evaluations
    -- (1 hour trailing window and 70 % occurrence frequency)    
    SELECT p.policy_id
          , MAX(h.end_date) execution_date
          , CASE WHEN 0 = COUNT(*) THEN 1 ELSE COUNT(*) END AS evaluation_count
          , p.utilization_type
          , p.health_policy_id
          , p.policy_name
          , pc.occurence_frequency
    INTO #policy_evaluations 
    FROM msdb.dbo.sysutility_ucp_policies p
    INNER JOIN msdb.dbo.syspolicy_policy_execution_history_internal h 
        ON p.policy_id = h.policy_id
    INNER JOIN msdb.dbo.sysutility_ucp_policy_configuration pc
        ON p.utilization_type = pc.utilization_type
    WHERE h.end_date >= DATEADD(MI, -60*pc.trailing_window, CURRENT_TIMESTAMP) 
        AND h.is_full_run = 1  
        AND p.resource_type = 3 -- Filter volatile resources (currently cpu type only)
    GROUP BY p.policy_id
        , p.utilization_type
        , p.health_policy_id
        , p.policy_name
        , pc.occurence_frequency;


    -- Get the policy violation count for the target types over the trailing window
    -- Note: 
    -- 1. If the trailing window is size increased, this computation will continue to
    -- use the exiting violations in the history against the newly configured window size. 
    -- It will only be effective after the full trailing window size is reached.
    -- 2. If the occurrence frequency is changed, it will be effective in the next run of the
    -- health state computation.
    SELECT p.policy_id
        , d.target_query_expression
        , COUNT(*) AS violation_count
        , MAX(h.history_id) as history_id
        , MAX(d.detail_id) AS detail_id
    INTO #policy_violations 
    FROM msdb.dbo.sysutility_ucp_policies p
    INNER JOIN msdb.dbo.syspolicy_policy_execution_history_internal h 
        ON p.policy_id = h.policy_id
    INNER JOIN msdb.dbo.syspolicy_policy_execution_history_details_internal d
        ON d.history_id = h.history_id 
    INNER JOIN msdb.dbo.sysutility_ucp_policy_configuration pc
        ON p.utilization_type = pc.utilization_type
    WHERE h.end_date >= DATEADD(MI, -60*pc.trailing_window, CURRENT_TIMESTAMP)			
        AND h.is_full_run = 1	
        AND h.result = 0
        AND d.result = 0
        AND p.resource_type = 3 -- Filter volatile resources (currently cpu type only)
    GROUP BY p.policy_id, d.target_query_expression;
    
    INSERT INTO dbo.sysutility_ucp_policy_violations_internal
    SELECT pe.health_policy_id
      , pe.policy_id
      , pe.policy_name
      , pv.history_id
      , pv.detail_id
      , pv.target_query_expression
      , N'' AS target_query_expression_with_id
      , pe.execution_date
      , 0 AS result
    FROM #policy_evaluations pe
    INNER JOIN #policy_violations pv 
        ON pe.policy_id = pv.policy_id
    WHERE pe.occurence_frequency <= ((pv.violation_count * 100) / pe.evaluation_count);
	        
END
GO    

--**********************************************************************
-- Create function fn_encode_sqlname_for_powershell             
 
-- Function description:
-- Encodes the sql names making it suitable for powershell representation
-- This is required as some of the characters conflict with PS commands
-- More details: http://msdn.microsoft.com/en-us/library/cc281841.aspx

 --Parameter description:
 --1. @sql_name - the sql name that needs encoding
--*********************************************************************
IF(OBJECT_ID(N'dbo.fn_encode_sqlname_for_powershell', 'FN') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping function dbo.fn_encode_sqlname_for_powershell', 0, 1) WITH NOWAIT;
    DROP FUNCTION dbo.fn_encode_sqlname_for_powershell
END
GO
RAISERROR ('Creating function dbo.fn_encode_sqlname_for_powershell', 0, 1) WITH NOWAIT;
GO
CREATE FUNCTION dbo.fn_encode_sqlname_for_powershell
(
	@sql_name SYSNAME
)
RETURNS SYSNAME
AS
BEGIN
	DECLARE @encoded_name SYSNAME = @sql_name

	SET @encoded_name = REPLACE(@encoded_name, N'%', N'%25')
	SET @encoded_name = REPLACE(@encoded_name, N'\', N'%5C')
	SET @encoded_name = REPLACE(@encoded_name, N'/', N'%2F')
	SET @encoded_name = REPLACE(@encoded_name, N':', N'%3A')
	SET @encoded_name = REPLACE(@encoded_name, N'<', N'%3C')
	SET @encoded_name = REPLACE(@encoded_name, N'>', N'%3E')
	SET @encoded_name = REPLACE(@encoded_name, N'*', N'%2A')
	SET @encoded_name = REPLACE(@encoded_name, N'?', N'%3F')
	SET @encoded_name = REPLACE(@encoded_name, N'[', N'%5B')
	SET @encoded_name = REPLACE(@encoded_name, N']', N'%5D')
	SET @encoded_name = REPLACE(@encoded_name, N'|', N'%7C')

	RETURN @encoded_name
END
GO

--**********************************************************************
-- Create procedure sp_sysutility_ucp_delete_policy_history                 
-- Procedure description:
-- This SP purges the utility resource health policy evaluation results history and details
-- For volatile resources, it deletes the records older than the specified trailing window time frame
-- For non-volatile resources, it deletes the records older than the current processing time
-- Since this SP deletes (many) records, it could possibly affect the performance due to impact on indexes  
-- We should consider droping and recreating the indexes if the purged records are huge in number.
--**********************************************************************
IF OBJECT_ID ('dbo.sp_sysutility_ucp_delete_policy_history') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure dbo.sp_sysutility_ucp_delete_policy_history', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_ucp_delete_policy_history 
END;
RAISERROR ('Creating procedure dbo.sp_sysutility_ucp_delete_policy_history', 0, 1) WITH NOWAIT;
GO

CREATE PROCEDURE dbo.sp_sysutility_ucp_delete_policy_history 
WITH EXECUTE AS OWNER
AS
BEGIN
    SET NOCOUNT ON; 
    
    DECLARE @over_utilization_trailing_window INT = 1
    DECLARE @under_utilization_trailing_window INT = 1

    DECLARE @rows_affected bigint;
    DECLARE @delete_batch_size int;

    -- As we delete the master record in the history table which cascades
    -- to foreign key records in details table; keep the delete batch size to 100.
    SET @delete_batch_size = 100;
    SET @rows_affected = -1;

    -- Get the configured over utilization trailing window
    SELECT @over_utilization_trailing_window = CAST(ci.current_value AS INT)
    FROM msdb.dbo.sysutility_ucp_configuration_internal ci
    WHERE ci.name = 'OverUtilizationTrailingWindow'

    -- Get the configured under utilization trailing window
    SELECT @under_utilization_trailing_window = CAST(ci.current_value AS INT)
    FROM msdb.dbo.sysutility_ucp_configuration_internal ci
    WHERE ci.name = 'UnderUtilizationTrailingWindow'

    -- Purge volatile resource policy evaluation history against over utilization trailing window
    DECLARE @max_end_date datetime;
    SET @max_end_date = DATEADD(HH, -@over_utilization_trailing_window, CURRENT_TIMESTAMP);
    SET @rows_affected = -1;
    WHILE (@rows_affected != 0)
    BEGIN
        -- We use sp_executesql here because the values of @delete_batch_size and @max_end_date could 
        -- influence plan selection. These are variables that have unknown values when the plan for the 
        -- proc is compiled.  By deferring compilation until the variables have taken on their final values, 
        -- we give the optimizer information that it needs to choose the best possible plan.  We could also 
        -- use an OPTION(RECOMPILE) hint to accomplish the same thing, but the sp_executesql approach avoids 
        -- paying the plan compile cost for each loop iteration. 
        EXEC sp_executesql N'
            DELETE TOP (@delete_batch_size) h
            FROM msdb.dbo.syspolicy_policy_execution_history_internal h
            INNER JOIN msdb.dbo.sysutility_ucp_policies p ON p.policy_id = h.policy_id
            WHERE p.resource_type = 3        -- processor resource type
                AND p.utilization_type = 2   -- over-utilization
                AND h.end_date < @max_end_date', 

            N'@delete_batch_size int, @max_end_date datetime', 
            @delete_batch_size = @delete_batch_size, @max_end_date = @max_end_date;

        SET @rows_affected = @@ROWCOUNT;
    END;
    
    -- Purge volatile resource policy evaluation history against under utilization trailing window
    SET @max_end_date = DATEADD(HH, -@under_utilization_trailing_window, CURRENT_TIMESTAMP);
    SET @rows_affected = -1;
    WHILE (@rows_affected != 0)
    BEGIN    
        EXEC sp_executesql N'
            DELETE TOP (@delete_batch_size) h
            FROM msdb.dbo.syspolicy_policy_execution_history_internal h
            INNER JOIN msdb.dbo.sysutility_ucp_policies p ON p.policy_id = h.policy_id
            WHERE p.resource_type = 3        -- processor resource type
                AND p.utilization_type = 1   -- under-utilization
                AND h.end_date < @max_end_date', 

            N'@delete_batch_size int, @max_end_date datetime', 
            @delete_batch_size = @delete_batch_size, @max_end_date = @max_end_date;

        SET @rows_affected = @@ROWCOUNT;
    END;
    
    -- Purge non-volatile resource policy evaluation history older than the current processing_time recorded 
    -- The latest policy evaluation results are not purged to avoid potential conflicts with the health 
    -- state computation running simultaneoulsy in the caching (master) job during the same time schedule. 
    SET @rows_affected = -1;
    -- PBM stores the end_date in local time so convert the 'latest_processing_time' datetimeoffset to a local datetime
    SELECT @max_end_date = CONVERT(DATETIME, latest_processing_time) FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal];
    WHILE (@rows_affected != 0)
    BEGIN     
        EXEC sp_executesql N'
            DELETE TOP (@delete_batch_size) h
            FROM msdb.dbo.syspolicy_policy_execution_history_internal h
            INNER JOIN msdb.dbo.sysutility_ucp_policies p ON p.policy_id = h.policy_id
            WHERE p.resource_type = 1    -- storage space resource type
                AND h.end_date < @max_end_date', 

            N'@delete_batch_size int, @max_end_date datetime',  
            @delete_batch_size = @delete_batch_size, @max_end_date = @max_end_date; 
            
        SET @rows_affected = @@ROWCOUNT;
    END;            
    
END
GO    
    
--**********************************************************************
-- Create function fn_sysutility_ucp_get_global_health_policy  
 
-- Function description:        
-- Identifies the global policy for a given object type 
-- 1. Check if there is a global policy for that object type in a target type
-- 2. Check if there is a global policy for that object type at utility level  
   
-- Parameter description:
-- 1. @rollup_object_type - type of the object (0: global, 1: dac, 2: server)
-- 2. @target_type - target resource object type (e.g. file, logfile, volume, computer etc)
-- 3. @resource_type - type of resource monitored (1: storage space, 2: storage I/O, 3: processor, 4: memory, 5: network I/O)
-- 4. @utilization_type - type of the utilization (1: under utilization, 2: over utilization)
--**********************************************************************
IF(OBJECT_ID(N'dbo.[fn_sysutility_ucp_get_global_health_policy]', 'FN') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping function dbo.[fn_sysutility_ucp_get_global_health_policy]', 0, 1) WITH NOWAIT;
    DROP FUNCTION dbo.[fn_sysutility_ucp_get_global_health_policy]
END
GO
RAISERROR ('Creating function dbo.[fn_sysutility_ucp_get_global_health_policy]', 0, 1) WITH NOWAIT;
GO
CREATE FUNCTION dbo.[fn_sysutility_ucp_get_global_health_policy](
    @rollup_object_type INT
    , @target_type INT
    , @resource_type INT
    , @utilization_type INT )
RETURNS INT 
AS
BEGIN
	DECLARE @health_policy_id INT
	
    -- Check if there is a global policy for that object type in a target type
	SELECT @health_policy_id = hp.health_policy_id
	FROM msdb.dbo.sysutility_ucp_policies hp
	WHERE hp.rollup_object_type = @rollup_object_type
	    AND hp.target_type = @target_type
	    AND hp.resource_type = @resource_type
	    AND hp.utilization_type = @utilization_type
	    AND hp.is_global_policy = 1
    
    -- If not found, check if there is a global policy for that object type at utility level. 
    -- This is the last resort, must find the global policy here.
    IF @health_policy_id = 0 OR @health_policy_id IS NULL
    BEGIN
		SELECT @health_policy_id = hp.health_policy_id
		FROM msdb.dbo.sysutility_ucp_policies hp
		WHERE hp.rollup_object_type = 0
		    AND hp.target_type = @target_type
		    AND hp.resource_type = @resource_type
		    AND hp.utilization_type = @utilization_type
		    AND hp.is_global_policy = 1    
    END	
    
	RETURN @health_policy_id
END
GO

--**********************************************************************


--**********************************************************************
-- Create function fn_sysutility_ucp_get_applicable_policy  
 
-- Function description:        
-- Identifies the applciable policy for a given object represented by URN 
-- 1. Checking if there is an overridden policy for that object
-- 2. Checking if there is a global policy for that object type   
   
-- Parameter description:
-- 1. @rollup_object_urn - urn of the object
-- 2. @rollup_object_type - type of the object (0: global, 1: dac, 2: server)
-- 3. @target_type - target resource object type (e.g. file, logfile, volume, computer etc)
-- 4. @resource_type - type of resource monitored (1: storage space, 2: storage I/O, 3: processor, 4: memory, 5: network I/O)
-- 5. @utilization_type - type of the utilization (1: under utilization, 2: over utilization)
--**********************************************************************
IF(OBJECT_ID(N'dbo.[fn_sysutility_ucp_get_applicable_policy]', 'FN') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping function dbo.[fn_sysutility_ucp_get_applicable_policy]', 0, 1) WITH NOWAIT;
    DROP FUNCTION dbo.[fn_sysutility_ucp_get_applicable_policy]
END
GO
RAISERROR ('Creating function dbo.[fn_sysutility_ucp_get_applicable_policy]', 0, 1) WITH NOWAIT;
GO
CREATE FUNCTION dbo.[fn_sysutility_ucp_get_applicable_policy](
    @rollup_object_urn NVARCHAR(4000)
    , @rollup_object_type INT
    , @target_type INT
    , @resource_type INT
    , @utilization_type INT )
RETURNS INT 
AS
BEGIN
   DECLARE @health_policy_id INT
	
    -- Check if there is an overridden policy for the rollup object
    SELECT @health_policy_id = hp.health_policy_id
    FROM msdb.dbo.sysutility_ucp_policies hp
    WHERE hp.rollup_object_urn = @rollup_object_urn
        AND hp.rollup_object_type = @rollup_object_type
        AND hp.target_type = @target_type
        AND hp.resource_type = @resource_type
        AND hp.utilization_type = @utilization_type
    
    -- If no overridden policy exist, get the global policy
    -- Check if the specific rollup_object has the global policy
    IF @health_policy_id = 0 OR @health_policy_id IS NULL
    BEGIN
		SELECT @health_policy_id = msdb.dbo.fn_sysutility_ucp_get_global_health_policy(@rollup_object_type
                                                                                , @target_type
                                                                                , @resource_type
                                                                                , @utilization_type)
    END
    
	RETURN @health_policy_id
     
END
GO

/**********************************************************************/
/* Create function fn_sysutility_ucp_get_aggregated_failure_count              */
/**********************************************************************/
IF(OBJECT_ID(N'[dbo].[fn_sysutility_ucp_get_aggregated_failure_count]', 'FN') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping function [fn_sysutility_ucp_get_aggregated_failure_count]', 0, 1) WITH NOWAIT;
    DROP FUNCTION [dbo].[fn_sysutility_ucp_get_aggregated_failure_count]
END
GO
RAISERROR ('Creating function [fn_sysutility_ucp_get_aggregated_failure_count]', 0, 1) WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysutility_ucp_get_aggregated_failure_count](@policy_name SYSNAME, @target_query_expression NVARCHAR(max))
RETURNS INT 
AS
BEGIN
   DECLARE @count INT
   SET @count = 0;

    SELECT @count = COUNT(hs.result) 
    FROM msdb.dbo.sysutility_ucp_policy_violations hs
    INNER JOIN msdb.dbo.syspolicy_policies p ON hs.policy_id = p.policy_id
    WHERE (hs.target_query_expression_with_id LIKE +'%'+@target_query_expression+'%' ESCAPE '\'
    OR hs.target_query_expression LIKE +'%'+@target_query_expression+'%')
    AND hs.result = 0
    AND p.name = @policy_name

   RETURN @count
END
GO

/**********************************************************************/
/* Create function fn_sysutility_ucp_get_policy_violations          */
/**********************************************************************/
IF(OBJECT_ID(N'[dbo].[fn_sysutility_ucp_get_policy_violations]', 'TF') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping function [fn_sysutility_ucp_get_policy_violations]', 0, 1) WITH NOWAIT;
    DROP FUNCTION [dbo].[fn_sysutility_ucp_get_policy_violations]
END
GO
RAISERROR ('Creating function [fn_sysutility_ucp_get_policy_violations]', 0, 1) WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysutility_ucp_get_policy_violations](@policy_name SYSNAME, @target_query_expression NVARCHAR(max))
RETURNS @data TABLE 
( health_state_id BIGINT ) 
AS
BEGIN

   INSERT INTO @data
    SELECT hs.detail_id
    FROM msdb.dbo.sysutility_ucp_policy_violations hs
    INNER JOIN msdb.dbo.syspolicy_policies p ON hs.policy_id = p.policy_id
    WHERE (hs.target_query_expression_with_id LIKE +'%'+@target_query_expression+'%' ESCAPE '\'
    OR hs.target_query_expression LIKE +'%'+@target_query_expression+'%')
    AND hs.result = 0
    AND p.name = @policy_name
    
   RETURN 
END
GO

/********************************************************************************/
/********************************************************************************/
/* Create function [fn_sysutility_ucp_get_file_space_utilization_history] which returns */
/* a table containing storage utilization history                               */
--  @object_type : 
--         0 --> Utility-level
--         1 --> Server (computer) level
--         2 --> Volume level
--         3 --> Instance level
--         4 --> Database level
--         5 --> Filegroup level
--         6 --> DataFile level
--         7 --> LogFile level
--  @virtual_server_name: virtual computer name 
--                        (logical name for the failover cluster if this is part of a cluster)
--                        otherwise, the name of the standalone computer
--  @volume_device_id: device_id of the volume
--  @server_instance_name: SQL instance name (server-qualified name)
--  @database_name: Name of the database
--  @filegroup_name: name of the filegroup
--  @dbfile_name: Name of the datafile/logfile
--  @start_time	: starting time for interval
--  @end_time	: end time for the interval
--  @aggregation_interval: This attribute is helpful setting starttime for the timespan.
--         1 --> No aggregation (raw data)
--         2 --> Hourly aggregation
--         3 --> Daily aggregation
--
-- NOTE: total_space_bytes may be NULL for database, filegroup, instance objects.
--    We treat that as equivalent to 0 for the purposes of this function, although
--    it's not entirely clear that that's appropriate.
/********************************************************************************/
IF(OBJECT_ID(N'[dbo].[fn_sysutility_ucp_get_file_space_utilization_history]') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping function [fn_sysutility_ucp_get_file_space_utilization_history]', 0, 1) WITH NOWAIT;
    DROP FUNCTION [dbo].[fn_sysutility_ucp_get_file_space_utilization_history]
END
GO

RAISERROR ('Creating function [fn_sysutility_ucp_get_file_space_utilization_history]', 0, 1) WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysutility_ucp_get_file_space_utilization_history]( 
   @object_type TINYINT, 
   @virtual_server_name SYSNAME, 
   @volume_device_id SYSNAME, 
   @server_instance_name SYSNAME, 
   @database_name SYSNAME, 
   @filegroup_name SYSNAME, 
   @database_file_name SYSNAME,
   @start_time DATETIMEOFFSET(7),
   @end_time DATETIMEOFFSET(7),
   @aggregation_interval TINYINT
   )
RETURNS TABLE AS RETURN (
    SELECT	CASE WHEN ISNULL(total_space_bytes, 0) = 0 THEN 0 ELSE (used_space_bytes * 100)/total_space_bytes END AS storage_utilization_percent,
		    CONVERT(BIGINT, used_space_bytes) AS storage_utilization_in_bytes, 
		    CONVERT(BIGINT, ISNULL(total_space_bytes, 0)) AS storage_capacity_in_bytes, 
		    processing_time as sample_time
    FROM dbo.syn_sysutility_ucp_space_utilization 
    WHERE @object_type = object_type AND
          @aggregation_interval = aggregation_type AND
          (processing_time BETWEEN @start_time AND @end_time) AND
          ISNULL(@virtual_server_name, '') = virtual_server_name AND
          ISNULL(@volume_device_id, '') = volume_device_id AND
          ISNULL(@server_instance_name, '') = server_instance_name AND
          ISNULL(@database_name, '') = database_name AND
          ISNULL(@filegroup_name, '') = [filegroup_name] AND
          ISNULL(@database_file_name, '') = [dbfile_name] 
    )       
GO

/********************************************************************************/
/* Create function [fn_sysutility_ucp_get_cpu_utilization_history] which returns */
/* a table containing processor utilization history                               */
--  @object_type : 
--          1 --> Server (computer) level
--          2 --> Instance level
--          3 --> Database level (DAC)
--  @physical_server_name: (physical) computer name
--  @server_instance_name: SQL instance name (server-qualified name)
--  @dac_name: Name of the DAC instance (also the database_name)
--  @start_time	: starting time for interval
--  @end_time	: end time for the interval
--  @aggregation_interval: This attribute is helpful setting starttime for the timespan.
--         1 --> No aggregation (raw data)
--         2 --> Hourly aggregation
--         3 --> Daily aggregation
/********************************************************************************/
IF(OBJECT_ID(N'[dbo].[fn_sysutility_ucp_get_cpu_utilization_history]') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping function [fn_sysutility_ucp_get_cpu_utilization_history]', 0, 1) WITH NOWAIT;
    DROP FUNCTION [dbo].[fn_sysutility_ucp_get_cpu_utilization_history]
END
GO

RAISERROR ('Creating function [fn_sysutility_ucp_get_cpu_utilization_history]', 0, 1) WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysutility_ucp_get_cpu_utilization_history]( 
   @object_type TINYINT, 
   @physical_server_name SYSNAME, 
   @server_instance_name SYSNAME, 
   @dac_name SYSNAME,
   @start_time DATETIMEOFFSET(7),
   @end_time DATETIMEOFFSET(7),
   @aggregation_interval TINYINT
   )
RETURNS TABLE 
AS
RETURN (	
    SELECT percent_total_cpu_utilization AS processor_utilization_percent, 
           processing_time AS sample_time
    FROM dbo.syn_sysutility_ucp_cpu_utilization 
    WHERE @object_type = object_type AND
          @aggregation_interval = aggregation_type AND
          (processing_time BETWEEN @start_time AND @end_time) AND
          ISNULL(@physical_server_name, '') = physical_server_name AND
          ISNULL(@server_instance_name, '') = server_instance_name AND
          ISNULL(@dac_name, '') = database_name
    )
GO

--------------------------------------------------------------------------------------------------
-- View that exposes the latest properties for all installed DACs. 
-- This view wraps a synonym that gets redirected to a MDW table after sysutility_mdw is created.
--------------------------------------------------------------------------------------------------
IF OBJECT_ID(N'[dbo].[sysutility_ucp_deployed_dacs]', 'V') IS NOT NULL
BEGIN 
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_deployed_dacs]', 0, 1) WITH NOWAIT;
   DROP VIEW [dbo].[sysutility_ucp_deployed_dacs];
END;
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_deployed_dacs]', 0, 1) WITH NOWAIT;
GO
CREATE VIEW dbo.sysutility_ucp_deployed_dacs
AS
SELECT
   dacs.dac_id,    -- todo (VSTS #345036): This column will be removed
   dacs.dac_name,
   dacs.dac_deploy_date AS dac_deployed_date,
   dacs.dac_description AS dac_description,
   dacs.dac_percent_total_cpu_utilization AS dac_percent_total_cpu_utilization,
   dacs.server_instance_name AS dac_server_instance_name,
   dacs.physical_server_name AS dac_physical_server_name,
   dacs.batch_time AS dac_collection_time,
   dacs.processing_time AS dac_processing_time,
   dacs.urn,
   dacs.powershell_path
FROM dbo.syn_sysutility_ucp_dacs as dacs
--- The join operator removes those DACs in the managed instances which are unenrolled during
--- the time between two consecutive data collection. 
--- See VSTS #473462 for more information 
INNER JOIN dbo.sysutility_ucp_managed_instances as mis
ON dacs.server_instance_name = mis.instance_name;

GO

/**********************************************************************/
/* create the computer object view that utilizes the data collected   */
/* in the MDW                                                         */
/*                                                                    */
/* We keep track of two names here - the "physical_server_name" and   */
/* the "virtual_server_name". In most cases, these two have the same  */
/* value. However, in the case of failover-cluster-instances, the     */
/* virtual_server_name refers to the logical name for the cluster,    */
/* while the physical_server_name refers to the name of the computer  */
/* within that cluster.                                               */
/* In general, we use physical_server_name across the board, except   */
/* for "shared" resources like space-utilization, where the virtual   */
/* name is more appropriate                                           */
/**********************************************************************/
IF OBJECT_ID(N'dbo.sysutility_ucp_computers', 'V') IS NOT NULL
BEGIN
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_computers]', 0, 1) WITH NOWAIT;
	DROP VIEW dbo.sysutility_ucp_computers;
END
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_computers]', 0, 1) WITH NOWAIT;
GO
CREATE VIEW dbo.sysutility_ucp_computers
AS
   SELECT  
       server_table.id AS computer_id    -- todo (VSTS #345036): This column will be removed
       , server_table.virtual_server_name AS virtual_server_name
       , server_table.physical_server_name AS physical_server_name
       , server_table.is_clustered_server AS is_clustered
       , server_table.percent_total_cpu_utilization AS processor_utilization
       , server_table.cpu_name AS cpu_name
       , server_table.cpu_max_clock_speed AS cpu_max_clock_speed
       , server_table.processing_time AS processing_time
       , urn
       , powershell_path       
   FROM    [dbo].[syn_sysutility_ucp_computers] as server_table
GO



-- =============================================
-- Create View : sysutility_ucp_volumes
-- =============================================
IF OBJECT_ID(N'dbo.sysutility_ucp_volumes', 'V') IS NOT NULL
BEGIN
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_volumes]', 0, 1) WITH NOWAIT;
   DROP VIEW dbo.sysutility_ucp_volumes
END
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_volumes]', 0, 1) WITH NOWAIT;
GO

CREATE VIEW dbo.sysutility_ucp_volumes
AS
SELECT
    [ID] AS volume_id    -- todo (VSTS #345036): This column will be removed
    , physical_server_name AS physical_server_name
    , virtual_server_name AS virtual_server_name
    , volume_name
    , volume_device_id
    , powershell_path
    , total_space_available AS total_space
    , total_space_utilized AS total_space_used
    , percent_total_space_utilization AS total_space_utilization  
FROM dbo.syn_sysutility_ucp_volumes;
GO


-- =============================================
-- Create View : sysutility_ucp_utility_space_utilization
-- =============================================
IF OBJECT_ID(N'dbo.sysutility_ucp_utility_space_utilization', 'V') IS NOT NULL
BEGIN
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_utility_space_utilization]', 0, 1) WITH NOWAIT;
   DROP VIEW dbo.sysutility_ucp_utility_space_utilization
END;
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_utility_space_utilization]', 0, 1) WITH NOWAIT;
GO
---
--- Gets the total utility space utilization. 
--- The funny left-outer-join is to account for cases where there is no "utility-wide" entry yet 
--- typically, right at bootstrap time
---
CREATE VIEW dbo.sysutility_ucp_utility_space_utilization
AS
   SELECT ISNULL(S2.total_space_bytes, 0) AS total_utility_storage,
          ISNULL(S2.used_space_bytes, 0) AS total_utilized_space 	
   FROM (SELECT 1 AS x) AS S1
         LEFT OUTER JOIN 
        (SELECT total_space_bytes, used_space_bytes 
          FROM dbo.syn_sysutility_ucp_space_utilization
          WHERE object_type = 0 AND -- utility-wide information
                aggregation_type = 0 AND -- detail-information
                processing_time = (SELECT latest_processing_time FROM msdb.dbo.sysutility_ucp_processing_state_internal)
        ) AS S2 ON (1=1)        
GO

-- =============================================
-- Create View : [sysutility_ucp_instances]
-- All of the properties are auto-generated using the below select statement
-- select ', [' +property_name+']' from [msdb].[dbo].[sysutility_mi_smo_property_configurations_internal] where object_type = 1
-- =============================================
IF OBJECT_ID(N'dbo.sysutility_ucp_instances', 'V') IS NOT NULL
BEGIN
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_instances]', 0, 1) WITH NOWAIT;
   DROP VIEW [dbo].[sysutility_ucp_instances]
END
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_instances]', 0, 1) WITH NOWAIT;
GO
   CREATE VIEW dbo.sysutility_ucp_instances
   AS
   SELECT [urn]
, [powershell_path]   
, [processing_time]
, [batch_time] AS [collection_time]
, [AuditLevel]
, [BackupDirectory]
, [BrowserServiceAccount]
, [BrowserStartMode]
, [BuildClrVersionString]
, [BuildNumber]
, [Collation]
, [CollationID]
, [ComparisonStyle]
, [ComputerNamePhysicalNetBIOS]
, [DefaultFile]
, [DefaultLog]
, [Edition]
, [EngineEdition]
, [ErrorLogPath]
, [FilestreamShareName]
, [InstallDataDirectory]
, [InstallSharedDirectory]
, [InstanceName]
, [IsCaseSensitive]
, [IsClustered]
, [IsFullTextInstalled]
, [IsSingleUser]
, [Language]
, [MailProfile]
, [MasterDBLogPath]
, [MasterDBPath]
, [MaxPrecision]
, [Name]
, [NamedPipesEnabled]
, [NetName]
, [NumberOfLogFiles]
, [OSVersion]
, [PerfMonMode]
, [PhysicalMemory]
, [Platform]
, [Processors]
, [ProcessorUsage]
, [Product]
, [ProductLevel]
, [ResourceVersionString]
, [RootDirectory]
, [ServerType]
, [ServiceAccount]
, [ServiceInstanceId]
, [ServiceName]
, [ServiceStartMode]
, [SqlCharSet]
, [SqlCharSetName]
, [SqlDomainGroup]
, [SqlSortOrder]
, [SqlSortOrderName]
, [Status]
, [TapeLoadWaitTime]
, [TcpEnabled]
, [VersionMajor]
, [VersionMinor]
, [VersionString] 
   FROM dbo.syn_sysutility_ucp_smo_servers;
GO

-- =============================================
-- Create View : [sysutility_ucp_databases]
-- All of the properties are auto-generated using the below select statement
-- select ', [' +property_name+']' from [msdb].[dbo].[sysutility_mi_smo_properties_to_collect_internal] where object_type = 2
-- =============================================
IF OBJECT_ID(N'[dbo].[sysutility_ucp_databases]', 'V') IS NOT NULL
BEGIN
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_databases]', 0, 1) WITH NOWAIT;
   DROP VIEW [dbo].[sysutility_ucp_databases]
END;
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_databases]', 0, 1) WITH NOWAIT;
GO

CREATE VIEW dbo.sysutility_ucp_databases
AS
   SELECT	S.urn
        , S.parent_urn
        , S.Collation
        , S.CompatibilityLevel
        , S.CreateDate
        , S.EncryptionEnabled
        , S.Name
        , S.server_instance_name
        , S.powershell_path
        , S.RecoveryModel
        , [S].[Trustworthy]
        , [S].processing_time
        , S.state 
      FROM [dbo].[syn_sysutility_ucp_databases] AS S
GO

-- =============================================
-- Create View : [sysutility_ucp_filegroups]
-- All of the properties are auto-generated using the below select statement
-- select ', [' +property_name+']' from [msdb].[dbo].[sysutility_mi_smo_properties_to_collect_internal] where object_type = 4
-- =============================================
IF OBJECT_ID(N'[dbo].[sysutility_ucp_filegroups]', 'V') IS NOT NULL
BEGIN
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_filegroups]', 0, 1) WITH NOWAIT;
   DROP VIEW [dbo].[sysutility_ucp_filegroups]
END
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_filegroups]', 0, 1) WITH NOWAIT;
GO

CREATE VIEW dbo.sysutility_ucp_filegroups
AS
   SELECT  [S].[urn]
        , [S].[parent_urn]
        , [S].[Name]
        , [S].[server_instance_name]
        , [S].[database_name]
        , [S].[powershell_path]
        , [S].[processing_time]
        FROM [dbo].[syn_sysutility_ucp_filegroups] S
GO

-- =============================================
-- Create View : [sysutility_ucp_datafiles]
-- All of the properties are auto-generated using the below select statement
-- select ', [' +property_name+']' from [msdb].[dbo].[sysutility_mi_smo_properties_to_collect_internal] where object_type = 5
-- =============================================
IF OBJECT_ID(N'[dbo].[sysutility_ucp_datafiles]', 'V') IS NOT NULL
BEGIN
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_datafiles]', 0, 1) WITH NOWAIT;
   DROP VIEW [dbo].[sysutility_ucp_datafiles]
END
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_datafiles]', 0, 1) WITH NOWAIT;
GO
CREATE VIEW dbo.sysutility_ucp_datafiles
AS
SELECT  [S].[urn]
        , [S].[parent_urn]
        , [S].[Growth]
        , [S].[GrowthType]
        , [S].[MaxSize]
        , [S].[Name]
        , [S].[Size]
        , [S].[UsedSpace]
        , [S].[FileName]
        , [S].[VolumeFreeSpace]
        , [S].[server_instance_name]
        , [S].[database_name]
        , [S].[filegroup_name]
        , [S].[powershell_path]
        , [S].[volume_name]
        , [S].[volume_device_id]
        , [S].[physical_server_name]
        , [S].[available_space] -- in bytes
        , CASE WHEN [S].[available_space] = 0.0 THEN 0.0 ELSE ([S].[UsedSpace] * 100)/[S].[available_space] END AS percent_utilization
        , [S].[processing_time]
FROM [dbo].[syn_sysutility_ucp_datafiles] S
GO

-- =============================================
-- Create View : [sysutility_ucp_logfiles]
-- All of the properties are auto-generated using the below select statement
-- select ', [' +property_name+']' from [msdb].[dbo].[sysutility_mi_smo_properties_to_collect_internal] where object_type = 3
-- =============================================
IF OBJECT_ID(N'[dbo].[sysutility_ucp_logfiles]', 'V') IS NOT NULL
BEGIN
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_logfiles]', 0, 1) WITH NOWAIT;
   DROP VIEW [dbo].[sysutility_ucp_logfiles]
END
GO
RAISERROR ('Creating view [dbo].[sysutility_ucp_logfiles]', 0, 1) WITH NOWAIT;
GO
CREATE VIEW dbo.sysutility_ucp_logfiles
AS
SELECT  [S].[urn]
        , [S].[parent_urn]
        , [S].[Growth]
        , [S].[GrowthType]
        , [S].[MaxSize]
        , [S].[Name]
        , [S].[Size]
        , [S].[UsedSpace]
        , [S].[FileName]
        , [S].[VolumeFreeSpace]
        , [S].[server_instance_name]
        , [S].[database_name]
        , [S].[powershell_path]
        , [S].[volume_name]
        , [S].[volume_device_id]
        , [S].[physical_server_name]
        , [S].[available_space] -- in bytes
        , CASE WHEN [S].[available_space] = 0.0 THEN 0.0 ELSE ([S].[UsedSpace] * 100)/[S].[available_space] END AS percent_utilization
        , [S].[processing_time]
FROM [dbo].[syn_sysutility_ucp_logfiles] S
GO

-- =============================================
-- Create View : [sysutility_ucp_database_files]
-- Simple (union) view over sysutility_ucp_datafiles and sysutility_ucp_logfiles
-- =============================================
IF OBJECT_ID(N'[dbo].[sysutility_ucp_database_files]', N'V') IS NOT NULL
BEGIN
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_database_files]', 0, 1) WITH NOWAIT;
   DROP VIEW [dbo].[sysutility_ucp_database_files]
END
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_database_files]', 0, 1) WITH NOWAIT;
GO
CREATE VIEW dbo.sysutility_ucp_database_files
AS
        SELECT [S].[server_instance_name], [S].[database_name], [S].[filegroup_name], [S].[Name] AS [Name],
               [S].[volume_name], [S].[volume_device_id], [S].[FileName], [S].[Growth], [S].[GrowthType],
               [S].[processing_time], [S].[powershell_path],
               1 AS [file_type],
               [S].[MaxSize], [S].[Size], [S].[UsedSpace], [S].[available_space], [S].[percent_utilization]
        FROM [dbo].[sysutility_ucp_datafiles] AS S
        UNION ALL
        SELECT [S].[server_instance_name], [S].[database_name], N'' AS [filegroup_name], [S].[Name] AS [Name],
               [S].[volume_name], [S].[volume_device_id], [S].[FileName], [S].[Growth], [S].[GrowthType],
               [S].[processing_time], [S].[powershell_path],
               2 AS [file_type],
               [S].[MaxSize], [S].[Size], [S].[UsedSpace], [S].[available_space], [S].[percent_utilization]
        FROM [dbo].[sysutility_ucp_logfiles] AS S  
GO

/****************************************************************************/
/*                                                                          */
/* Creating a utilization view on top of several object views in CMR which  */
/* collects the data and log file related data for all databases in the     */
/* utility                                                                   */                                                                   
/*                                                                          */
/****************************************************************************/

IF (OBJECT_ID(N'[dbo].[sysutility_ucp_mi_database_file_space_utilizations]', 'V') IS NOT NULL)
BEGIN 
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_mi_database_file_space_utilizations]', 0, 1) WITH NOWAIT;
   DROP VIEW [dbo].[sysutility_ucp_mi_database_file_space_utilizations]
END
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_mi_database_file_space_utilizations]', 0, 1) WITH NOWAIT;
GO

CREATE VIEW [dbo].[sysutility_ucp_mi_database_file_space_utilizations] AS
    SELECT	df.server_instance_name, 
            df.database_name, 
            df.filegroup_name,
            df.Name,
            df.volume_name,
            df.volume_device_id,
            df.FileName AS databasefile_name, 
            df.percent_utilization AS current_utilization, 
            df.UsedSpace AS used_space, 
            df.available_space AS available_space,
            10 AS under_utilization,  
            70 AS over_utilization,
            df.file_type,
            df.GrowthType AS growth_type	
    FROM	msdb.dbo.sysutility_ucp_database_files AS df
GO



/****************************************************************************/
/*                                                                          */
/* Creating a utilization view on top of several object views in CMR which  */
/* collects the data and log file related data for all deployed dacs in the */
/* utility                                                                   */                                                                   
/*                                                                          */
/****************************************************************************/

IF (OBJECT_ID(N'[dbo].[sysutility_ucp_dac_database_file_space_utilizations]', 'V') IS NOT NULL)
BEGIN 
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_dac_database_file_space_utilizations]', 0, 1) WITH NOWAIT;
   DROP VIEW [dbo].[sysutility_ucp_dac_database_file_space_utilizations]
END
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_dac_database_file_space_utilizations]', 0, 1) WITH NOWAIT;
GO

CREATE VIEW [dbo].[sysutility_ucp_dac_database_file_space_utilizations] AS
    SELECT	dd.dac_server_instance_name AS server_instance_name, 
            dd.dac_name AS dac_name,
            df.[filegroup_name],
            df.[Name],
            df.volume_name,
            df.volume_device_id,
            df.FileName AS databasefile_name, 
            df.percent_utilization AS current_utilization, 
            df.UsedSpace AS used_space, 
            df.available_space,
            10 AS under_utilization, 
            70 AS over_utilization,
            df.file_type,
            df.GrowthType AS growth_type
    FROM	msdb.dbo.sysutility_ucp_deployed_dacs AS dd,
            msdb.dbo.sysutility_ucp_database_files AS df
    WHERE dd.dac_server_instance_name = df.server_instance_name
      AND dd.dac_name = df.database_name         
GO


/****************************************************************************/
/*                                                                          */
/* Creating a utilization view on top of several object views in CMR which  */
/* collects the volume related data for all databases in the utility         */                                                                   
/*                                                                          */
/****************************************************************************/

IF (OBJECT_ID(N'[dbo].[sysutility_ucp_mi_volume_space_utilizations]', 'V') IS NOT NULL)
BEGIN 
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_mi_volume_space_utilizations]', 0, 1) WITH NOWAIT;
   DROP VIEW [dbo].[sysutility_ucp_mi_volume_space_utilizations]
END
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_mi_volume_space_utilizations]', 0, 1) WITH NOWAIT;
GO

CREATE VIEW [dbo].[sysutility_ucp_mi_volume_space_utilizations] AS(
-- TODO VSTS 280036(rnagpal) : Temporarily Keeping under_utilization to 10 and over_utilization to 70 for now
-- since we might reintroduce them in near future which will not require any interface change for the 
-- Utility object model / UI. Presently, we are not exposing under and over utilization thresholds in UI
-- so they are not exposed. If that remains the same till KJ CTP2, i'll remove them.
SELECT	vol.physical_server_name AS physical_server_name, 
		svr.Name as server_instance_name,
		vol.volume_name AS volume_name, 
		vol.volume_device_id AS volume_device_id, 
		vol.total_space_utilization AS current_utilization, 
		vol.total_space_used AS used_space,
		vol.total_space AS available_space,
		10 AS under_utilization, 
		70 AS over_utilization
FROM	msdb.dbo.sysutility_ucp_volumes AS vol,
		msdb.dbo.sysutility_ucp_instances AS svr
WHERE	vol.physical_server_name = svr.ComputerNamePhysicalNetBIOS)
GO


/****************************************************************************/
/*                                                                          */
/* Creating a utilization view on top of several object views in CMR which  */
/* collects the volume related data for all deployed dacs in the utility     */                                                                   
/*                                                                          */
/****************************************************************************/

IF (OBJECT_ID(N'[dbo].[sysutility_ucp_dac_volume_space_utilizations]', 'V') IS NOT NULL)
BEGIN 
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_dac_volume_space_utilizations]', 0, 1) WITH NOWAIT;
   DROP VIEW [dbo].[sysutility_ucp_dac_volume_space_utilizations]
END
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_dac_volume_space_utilizations]', 0, 1) WITH NOWAIT;
GO

CREATE VIEW [dbo].[sysutility_ucp_dac_volume_space_utilizations] AS(
-- TODO VSTS 280036(rnagpal) : Temporarily Keeping under_utilization to 10 and over_utilization to 70 for now
-- since we might reintroduce them in near future which will not require any interface change for the 
-- Utility object model / UI. Presently, we are not exposing under and over utilization thresholds in UI
-- so they are not exposed. If that remains the same till KJ CTP2, i'll remove them.
SELECT	vol.physical_server_name AS physical_server_name,
		dd.dac_name AS dac_name,
		dd.dac_server_instance_name AS server_instance_name, 
		vol.volume_name AS volume_name, 
		vol.volume_device_id AS volume_device_id, 
		vol.total_space_utilization AS current_utilization, 
		vol.total_space_used AS used_space,
		vol.total_space AS available_space,
		10 AS under_utilization, 
		70 AS over_utilization
		
FROM	msdb.dbo.sysutility_ucp_volumes AS vol,
		msdb.dbo.sysutility_ucp_deployed_dacs AS dd
		
WHERE	vol.physical_server_name = dd.dac_physical_server_name)
GO


/****************************************************************************/
/*                                                                          */
/* Creating a utilization view on top of several object views in CMR which  */
/* collects the server related data for all instances in the utility         */                                                                   
/*                                                                          */
/****************************************************************************/
-- TODO VSTS 280036(rnagpal) : Temporarily Keeping under_utilization to 10 and over_utilization to 70 for now
-- since we might reintroduce them in near future which will not require any interface change for the 
-- Utility object model / UI. Presently, we are not exposing under and over utilization thresholds in UI
-- so they are not exposed. If that remains the same till KJ CTP2, i'll remove them.
IF (OBJECT_ID(N'[dbo].[sysutility_ucp_mi_cpu_utilizations]', 'V') IS NOT NULL)
BEGIN 
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_mi_cpu_utilizations]', 0, 1) WITH NOWAIT;
   DROP VIEW [dbo].[sysutility_ucp_mi_cpu_utilizations];
END
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_mi_cpu_utilizations]', 0, 1) WITH NOWAIT;
GO

CREATE VIEW [dbo].[sysutility_ucp_mi_cpu_utilizations]
AS
SELECT svr.Name AS server_instance_name, 
   10 AS under_utilization, 
   CAST(svr.ProcessorUsage AS INT) AS current_utilization, 
   70 AS over_utilization
FROM	msdb.dbo.sysutility_ucp_instances AS svr;
GO


/****************************************************************************/
/*                                                                          */
/* Creating a utilization view on top of several object views in CMR which  */
/* collects the computer related data for all computers in the utility       */                                                                   
/*                                                                          */
/****************************************************************************/
-- TODO VSTS 280036(rnagpal) : Temporarily Keeping under_utilization to 10 and over_utilization to 70 for now
-- since we might reintroduce them in near future which will not require any interface change for the 
-- Utility object model / UI. Presently, we are not exposing under and over utilization thresholds in UI
-- so they are not exposed. If that remains the same till KJ CTP2, i'll remove them.
IF (OBJECT_ID(N'[dbo].[sysutility_ucp_computer_cpu_utilizations]', 'V') IS NOT NULL)
BEGIN 
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_computer_cpu_utilizations]', 0, 1) WITH NOWAIT;
   DROP VIEW [dbo].[sysutility_ucp_computer_cpu_utilizations];
END
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_computer_cpu_utilizations]', 0, 1) WITH NOWAIT;
GO

CREATE VIEW [dbo].[sysutility_ucp_computer_cpu_utilizations]
AS
SELECT comp.physical_server_name AS physical_server_name, 
   10 AS under_utilization, 
   comp.processor_utilization AS current_utilization, 
   70 AS over_utilization
FROM	msdb.dbo.sysutility_ucp_computers AS comp
GO



/****************************************************************************/
/*                                                                          */
/* Creating a utilization view on top of several object views in CMR which  */
/* collects the DAC related data for all DACs in the utility                 */                                                                   
/*                                                                          */
/****************************************************************************/
-- TODO VSTS 280036(rnagpal) : Temporarily Keeping under_utilization to 10 and over_utilization to 70 for now
-- since we might reintroduce them in near future which will not require any interface change for the 
-- Utility object model / UI. Presently, we are not exposing under and over utilization thresholds in UI
-- so they are not exposed. If that remains the same till KJ CTP2, i'll remove them.
IF (OBJECT_ID(N'[dbo].[sysutility_ucp_dac_cpu_utilizations]', 'V') IS NOT NULL)
BEGIN 
   RAISERROR ('Dropping view [dbo].[sysutility_ucp_dac_cpu_utilizations]', 0, 1) WITH NOWAIT;
   DROP VIEW [dbo].[sysutility_ucp_dac_cpu_utilizations];
END
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_dac_cpu_utilizations]', 0, 1) WITH NOWAIT;
GO

CREATE VIEW [dbo].[sysutility_ucp_dac_cpu_utilizations]
AS
SELECT
   dac.dac_name AS dac_name, 
   dac.dac_server_instance_name AS server_instance_name, 
   10 AS under_utilization, 
   dac.dac_percent_total_cpu_utilization AS current_utilization, 
   70 AS over_utilization
 FROM	msdb.dbo.sysutility_ucp_deployed_dacs AS dac
GO



/********************************************************************
Procedure sp_sysutility_ucp_provision_utility_object_internal
   Helper proc that grants permissions (based on object type) to a role. 
********************************************************************/
IF  OBJECT_ID(N'dbo.sp_sysutility_ucp_provision_utility_object_internal') IS NOT NULL
BEGIN
   RAISERROR('Dropping procedure dbo.sp_sysutility_ucp_provision_utility_object_internal', 0, 1)  WITH NOWAIT;
   DROP PROCEDURE dbo.sp_sysutility_ucp_provision_utility_object_internal;
END
GO
RAISERROR('Creating procedure dbo.sp_sysutility_ucp_provision_utility_object_internal', 0, 1)  WITH NOWAIT;
GO

CREATE PROCEDURE dbo.sp_sysutility_ucp_provision_utility_object_internal
   @object_name sysname, @role_name sysname
WITH EXECUTE AS CALLER
AS
BEGIN
   DECLARE @sql_stmt nvarchar(max);
   DECLARE @grant_type NVARCHAR(20);
   DECLARE @object_type char(2);
   DECLARE @database_name sysname;
   DECLARE @quoted_object_name_with_dbo nvarchar(max);
   
   SET @database_name = DB_NAME();
   SET @quoted_object_name_with_dbo = 'dbo.' + QUOTENAME(@object_name);

   SELECT @object_type = [type] FROM sys.objects WHERE object_id = OBJECT_ID(@quoted_object_name_with_dbo);

   -- TSQL or CLR procs and non-inline functions
   --    P  - stored proc (TSQL)
   --    PC - stored proc (SQLCLR)
   --    FN - scalar function (TSQL)
   --    FS - scalar function (SQLCLR)
   IF (@object_type IN ('P', 'PC', 'FN', 'FS'))
   BEGIN
      SET @grant_type = 'EXECUTE';
   END

   -- Views, inline functions, tables
   --    V  - view
   --    IF - inline function (TSQL)
   --    U  - user table
   --    S  - system table
   --    TF - table-valued function (TSQL)
   --    FT - table-valued function (SQLCLR)
   ELSE IF (@object_type IN ('V', 'IF', 'U', 'S', 'FT', 'TF'))
   BEGIN
      SET @grant_type = 'SELECT';
   END;
   ELSE BEGIN
      -- The object '%s' does not exist in database '%s' or is invalid for this operation.
      RAISERROR (15009, 16, 1, @quoted_object_name_with_dbo, @database_name);
      RETURN;
   END;

   SELECT @sql_stmt = N'GRANT '+ @grant_type +' ON ' + @quoted_object_name_with_dbo + ' TO ' + QUOTENAME(@role_name);
   RAISERROR ('Executing: %s', 0, 1, @sql_stmt) WITH NOWAIT
   EXEC sp_executesql @sql_stmt;
END;
GO



/**********************************************************************/
/* Create function fn_sysutility_ucp_get_aggregated_health       
    
   This function determines the aggregated health state based on the  
   input arguments over and under utilization policy violations.     
        
   The aggregated health state computation algorithm is as follows:   
   If both over and under utililization policy violations are 0 then its steady state (green) 
   If there is over utilization policy violation, then its over utilized state (red) 
   If there is under utilization policy violation (with no over utilization) then its under utilized state (orange)*/
/**********************************************************************/
IF(OBJECT_ID(N'dbo.fn_sysutility_ucp_get_aggregated_health') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping function fn_sysutility_ucp_get_aggregated_health', 0, 1) WITH NOWAIT;
    DROP FUNCTION dbo.fn_sysutility_ucp_get_aggregated_health
END
GO
RAISERROR ('Creating function fn_sysutility_ucp_get_aggregated_health', 0, 1) WITH NOWAIT;
GO
CREATE FUNCTION [dbo].[fn_sysutility_ucp_get_aggregated_health]
(
	@is_over_utilized INT,
	@is_under_utilized INT
)
RETURNS TABLE
AS
RETURN ((SELECT 
CASE WHEN 0 = @is_over_utilized AND 0 = @is_under_utilized THEN 1 
ELSE CASE WHEN @is_over_utilized > 0 THEN 3 
ELSE 2 END END AS val))
GO	


--**********************************************************************
-- Create view sysutility_ucp_instance_policies             
 
-- View Description:
-- Returns the resource health policy details for the managed instances 
-- Powershell path - SQLSERVER:\SQL\<server_instance_name>        
-- smo_server_urn - Server[@Name='<server_instance_name>']
-- utility_server_urn - Utility[@Name='<utility_name>']/Server[@Name='<server_instance_name>']        

 --Attribute description:
 -- server_instance_name - the name of the server instance
 -- smo_server_urn - the urn of the server in smo hiearchy (Server as the root)
 -- utility_server_urn - the urn of the server in utility hiearchy (Utility as the root)
 -- powershell_path - the path representing the server hiearchy in powershell
 -- policy_id - resource health policy global / local (if exists)
 -- resource_type - resource type on which the policy is configured
 -- target_type - target type against which the policy is evaluated
 -- utilization_type - type of the policy utilization (over/under) configuration
--*********************************************************************
IF (OBJECT_ID(N'[dbo].[sysutility_ucp_instance_policies]', 'V') IS NOT NULL) 
BEGIN
    RAISERROR ('Dropping view [dbo].[sysutility_ucp_instance_policies]', 0, 1) WITH NOWAIT;
	DROP VIEW dbo.sysutility_ucp_instance_policies
END	
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_instance_policies]...', 0, 1) WITH NOWAIT;
GO
CREATE VIEW dbo.sysutility_ucp_instance_policies AS
(    
    SELECT sp.server_instance_name 
        , sp.smo_server_urn
        , sp.utility_server_urn
        , sp.powershell_path
        , ISNULL(lp.policy_id, sp.policy_id) AS policy_id -- if exists get local (overridden) policy, else return global policy 
        , ISNULL(lp.is_global_policy, 1) AS is_global_policy
        , sp.resource_type
        , sp.target_type
        , sp.utilization_type
    FROM (
            -- fetch the global policies 
            SELECT sv.Name AS server_instance_name
                , sv.urn AS smo_server_urn
                , N'Utility[@Name=''' + CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')) + N''']/' + sv.urn AS utility_server_urn
                , sv.powershell_path AS powershell_path
                , gp.policy_id
                , gp.resource_type
                , gp.target_type
                , gp.utilization_type
            FROM msdb.dbo.sysutility_ucp_instances sv
                , msdb.dbo.sysutility_ucp_policies gp
            WHERE gp.rollup_object_type = 2  
                AND gp.is_global_policy = 1    
        ) sp
        LEFT JOIN msdb.dbo.sysutility_ucp_policies lp -- fetch the local policies (if exists)
        ON lp.rollup_object_urn = sp.utility_server_urn 
            AND lp.rollup_object_type = 2
            AND lp.is_global_policy = 0
            AND lp.resource_type = sp.resource_type
            AND lp.target_type = sp.target_type
            AND lp.utilization_type = sp.utilization_type
)
GO	

--**********************************************************************
-- Create view sysutility_ucp_computer_policies             
 
-- View Description:
-- Returns the resource health policy details for the computers
-- Powershell path - SQLSERVER:\Utility\<server_instance_name>\Computers\<computer_name>
-- computer_urn - Utility[@Name='<utility_name>']/Computer[@Name='<computer_name>']

 --Attribute description:
 -- physical_server_name - the name of the computer
 -- computer_urn - the urn of the computer in utility hiearchy 
 -- powershell_path - the path representing the computer hiearchy in powershell
 -- policy_id - resource health policy global / local (if exists)
 -- resource_type - resource type on which the policy is configured
 -- target_type - target type against which the policy is evaluated
 -- utilization_type - type of the policy utilization (over/under) configuration
--*********************************************************************
IF (OBJECT_ID(N'[dbo].[sysutility_ucp_computer_policies]', 'V') IS NOT NULL) 
BEGIN
    RAISERROR ('Dropping view [dbo].[sysutility_ucp_computer_policies]', 0, 1) WITH NOWAIT;
	DROP VIEW dbo.sysutility_ucp_computer_policies
END	
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_computer_policies]...', 0, 1) WITH NOWAIT;
GO
CREATE VIEW dbo.sysutility_ucp_computer_policies AS
(    
    SELECT cp.physical_server_name
        , cp.computer_urn
        , cp.powershell_path
        , ISNULL(lp.policy_id, cp.policy_id) AS policy_id -- if exists get local (overridden) policy, else return global policy 
        , ISNULL(lp.is_global_policy, 1) AS is_global_policy
        , cp.resource_type
        , cp.target_type
        , cp.utilization_type
    FROM 
        (
            -- fetch the global policies 
            -- Should we be using "virtual_server_name" or "physical_server_name" here?
            SELECT co.physical_server_name AS physical_server_name
                , co.urn AS computer_urn
                , co.powershell_path AS powershell_path
                , gp.policy_id
                , gp.resource_type
                , gp.target_type
                , gp.utilization_type     
            FROM msdb.dbo.sysutility_ucp_computers co
                , msdb.dbo.sysutility_ucp_policies gp
            WHERE gp.rollup_object_type = 3  
                AND gp.is_global_policy = 1    
        ) cp
        LEFT JOIN msdb.dbo.sysutility_ucp_policies lp -- fetch the local policies (if exists)
        ON lp.rollup_object_urn = cp.computer_urn
            AND lp.rollup_object_type = 3
            AND lp.is_global_policy = 0
            AND lp.resource_type = cp.resource_type
            AND lp.target_type = cp.target_type
            AND lp.utilization_type = cp.utilization_type
)
GO

--**********************************************************************
-- Create view sysutility_ucp_dac_policies             
 
-- View Description:
-- Returns the resource health policy details for the dac's
-- Powershell path - SQLSERVER:\Utility\<server_instance_name>\DeployedDacs\<dac_name>.<dac_server_instance_name>
-- dac_urn -  Utility[@Name='<utility_name>']/DeployedDac[@Name='<dac_name>' and @ServerInstanceName='<dac_server_instnace>']

 --Attribute description:
 -- dac_name - the name of the data-tier application
 -- dac_server_instance_name - the server\instance on which the dac is hosted
 -- dac_urn - the urn of the dac in utility hiearchy 
 -- powershell_path - the path representing the dac hiearchy in powershell
 -- policy_id - resource health policy global / local (if exists)
 -- resource_type - resource type on which the policy is configured
 -- target_type - target type against which the policy is evaluated
 -- utilization_type - type of the policy utilization (over/under) configuration
--*********************************************************************
IF (OBJECT_ID(N'[dbo].[sysutility_ucp_dac_policies]', 'V') IS NOT NULL) 
BEGIN
    RAISERROR ('Dropping view [dbo].[sysutility_ucp_dac_policies]', 0, 1) WITH NOWAIT;
	DROP VIEW dbo.sysutility_ucp_dac_policies
END	
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_dac_policies]...', 0, 1) WITH NOWAIT;
GO
CREATE VIEW dbo.sysutility_ucp_dac_policies AS
(    
    SELECT dp.dac_name
        , dp.dac_server_instance_name
        , dp.dac_urn
        , dp.powershell_path
        , ISNULL(lp.policy_id, dp.policy_id) AS policy_id -- if exists get local (overridden) policy, else return global policy 
        , ISNULL(lp.is_global_policy, 1) AS is_global_policy
        , dp.resource_type
        , dp.target_type
        , dp.utilization_type
    FROM 
        (
            -- fetch the global policies 
            SELECT dd.dac_name
                , dd.dac_server_instance_name 
                , dd.urn AS dac_urn
                , dd.powershell_path AS powershell_path
                , gp.policy_id
                , gp.resource_type
                , gp.target_type
                , gp.utilization_type
            FROM msdb.dbo.sysutility_ucp_deployed_dacs dd
                , msdb.dbo.sysutility_ucp_policies gp
            WHERE gp.rollup_object_type = 1  
                AND gp.is_global_policy = 1    
        ) dp
        LEFT JOIN msdb.dbo.sysutility_ucp_policies lp -- fetch the local policies (if exists)
        ON lp.rollup_object_urn = dp.dac_urn 
            AND lp.rollup_object_type = 1
            AND lp.is_global_policy = 0
            AND lp.resource_type = dp.resource_type
            AND lp.target_type = dp.target_type
            AND lp.utilization_type = dp.utilization_type
)
GO	

--**********************************************************************
-- Create view sysutility_ucp_dac_policy_type             
 
-- This view selects the existing dac's and indicates whther the corresponding
-- resource health policy is global or overridden

 --Attributes:
 --1. dac_name - the name of the dac (rollup object)
 --2. dac_server_instance_name - the name of the smo server in the form server_name\instance_name
 --3. is_global_policy - Indicates whether the policy type is global or overridden
--*********************************************************************
IF (OBJECT_ID(N'[dbo].[sysutility_ucp_dac_policy_type]', 'V') IS NOT NULL) 
BEGIN
    RAISERROR ('Dropping view [dbo].[sysutility_ucp_dac_policy_type]', 0, 1) WITH NOWAIT;
	DROP VIEW dbo.sysutility_ucp_dac_policy_type
END	
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_dac_policy_type]...', 0, 1) WITH NOWAIT;
GO
CREATE VIEW dbo.sysutility_ucp_dac_policy_type AS
(    

    -- Target types
    -- computer_type = 1
    -- volume_type = 6

    -- Resource types
    -- processor_type = 3
    -- space_type = 1

    SELECT DISTINCT dd.dac_server_instance_name
	    , dd.dac_name
        , CASE WHEN ((0 < ip.is_policy_overridden) OR (0 < cp.is_policy_overridden)) THEN 1 ELSE 0 END AS is_policy_overridden
    FROM msdb.dbo.sysutility_ucp_deployed_dacs dd 
        , (SELECT dac_name, dac_server_instance_name, SUM(CASE WHEN 0 < is_global_policy THEN 0 ELSE 1 END) AS is_policy_overridden
           FROM msdb.dbo.sysutility_ucp_dac_policies 
           GROUP BY dac_name, dac_server_instance_name) ip 
        , (SELECT physical_server_name, SUM(CASE WHEN 0 < is_global_policy THEN 0 ELSE 1 END) AS is_policy_overridden
           FROM msdb.dbo.sysutility_ucp_computer_policies  
           GROUP BY physical_server_name) cp     
    WHERE ip.dac_name = dd.dac_name
        AND ip.dac_server_instance_name = dd.dac_server_instance_name
        AND cp.physical_server_name = dd.dac_physical_server_name	
) 
GO

--**********************************************************************
-- Create view sysutility_ucp_instance_policy_type
 
-- This view selects the existing server's and indicates whther the corresponding
-- resource health policy is global or overridden

 --Attributes:
 --1. server_instance_name - the name of the smo server in the form server_name\instance_name
 --2. is_global_policy - Indicates whether the policy type is global or overridden
--*********************************************************************
IF (OBJECT_ID(N'[dbo].[sysutility_ucp_instance_policy_type]', 'V') IS NOT NULL) 
BEGIN
    RAISERROR ('Dropping view [dbo].[sysutility_ucp_instance_policy_type]', 0, 1) WITH NOWAIT;
	DROP VIEW dbo.sysutility_ucp_instance_policy_type
END	
GO

RAISERROR ('Creating view [dbo].[sysutility_ucp_instance_policy_type]...', 0, 1) WITH NOWAIT;
GO
CREATE VIEW dbo.sysutility_ucp_instance_policy_type AS
(  
    -- Target types
    -- computer_type = 1
    -- volume_type = 6

    -- Resource types
    -- processor_type = 3
    -- space_type = 1

    SELECT sv.Name AS server_instance_name
        , CASE WHEN ((0 < ip.is_policy_overridden) OR (0 < cp.is_policy_overridden)) THEN 1 ELSE 0 END AS is_policy_overridden
    FROM msdb.dbo.sysutility_ucp_instances sv
        , (SELECT server_instance_name , SUM(CASE WHEN 0 < is_global_policy THEN 0 ELSE 1 END) AS is_policy_overridden
           FROM msdb.dbo.sysutility_ucp_instance_policies 
           GROUP BY server_instance_name) ip 
        , (SELECT physical_server_name, SUM(CASE WHEN 0 < is_global_policy THEN 0 ELSE 1 END) AS is_policy_overridden
           FROM msdb.dbo.sysutility_ucp_computer_policies  
           GROUP BY physical_server_name) cp 
    WHERE ip.server_instance_name = sv.Name
        AND cp.physical_server_name = sv.ComputerNamePhysicalNetBIOS
) 
GO

/**********************************************************************/
/* Create table sysutility_ucp_mi_file_space_health_internal         */
/* This table stores the file-space health states for data/log file groups of MI */
/**********************************************************************/

IF(OBJECT_ID(N'dbo.sysutility_ucp_mi_file_space_health_internal', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table dbo.sysutility_ucp_mi_file_space_health_internal', 0, 1) WITH NOWAIT;

    CREATE TABLE dbo.sysutility_ucp_mi_file_space_health_internal
    (
        server_instance_name SYSNAME NOT NULL
        , database_name SYSNAME NOT NULL
        , fg_name SYSNAME NOT NULL
        , over_utilized_count INT NOT NULL DEFAULT(0)
        , under_utilized_count INT NOT NULL DEFAULT(0)
        , file_type INT NOT NULL 
        , set_number INT NOT NULL DEFAULT(0)
        , processing_time DATETIMEOFFSET(7) NOT NULL DEFAULT(SYSDATETIMEOFFSET())

        CONSTRAINT [PK_sysutility_ucp_mi_file_space_health_internal_name] 
          PRIMARY KEY CLUSTERED (set_number, server_instance_name, database_name, fg_name)
    )
END
GO

/**********************************************************************/
/* Create table sysutility_ucp_mi_database_health_internal          */
/* This table stores the file-space health states for databases of MI */
/**********************************************************************/

IF(OBJECT_ID(N'dbo.sysutility_ucp_mi_database_health_internal', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table dbo.sysutility_ucp_mi_database_health_internal', 0, 1) WITH NOWAIT;

    CREATE TABLE dbo.sysutility_ucp_mi_database_health_internal
    (
        server_instance_name SYSNAME NOT NULL
        , database_name SYSNAME NOT NULL
        , over_utilized_count INT NOT NULL DEFAULT(0)
        , under_utilized_count INT NOT NULL DEFAULT(0)
        , set_number INT NOT NULL DEFAULT(0)
        , processing_time DATETIMEOFFSET(7) NOT NULL DEFAULT(SYSDATETIMEOFFSET())

        CONSTRAINT [PK_sysutility_ucp_mi_database_health_internal_name] 
          PRIMARY KEY CLUSTERED (set_number, server_instance_name, database_name)
    )
END
GO    

/**********************************************************************/
/* Create table sysutility_ucp_dac_file_space_health_internal        */
/* This table stores the file-space health states for data/log file groups of DAC */
/**********************************************************************/

IF(OBJECT_ID(N'dbo.sysutility_ucp_dac_file_space_health_internal', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table dbo.sysutility_ucp_dac_file_space_health_internal', 0, 1) WITH NOWAIT;

    CREATE TABLE dbo.sysutility_ucp_dac_file_space_health_internal
    (
        dac_name SYSNAME NOT NULL
        , dac_server_instance_name SYSNAME NOT NULL
        , fg_name SYSNAME NOT NULL
        , over_utilized_count INT NOT NULL DEFAULT(0)
        , under_utilized_count INT NOT NULL DEFAULT(0)
        , file_type INT NOT NULL 
        , set_number INT NOT NULL DEFAULT(0)
        , processing_time DATETIMEOFFSET(7) NOT NULL DEFAULT(SYSDATETIMEOFFSET())

        CONSTRAINT [PK_sysutility_ucp_dac_file_space_health_internal_name] 
          PRIMARY KEY CLUSTERED (set_number, dac_server_instance_name, dac_name, fg_name)
    )
END
GO	

/**********************************************************************/
/* Create table sysutility_ucp_mi_volume_space_health_internal       */
/* This table stores the volume-space health states for computer MI's */
/**********************************************************************/

IF(OBJECT_ID(N'dbo.sysutility_ucp_mi_volume_space_health_internal', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table dbo.sysutility_ucp_mi_volume_space_health_internal', 0, 1) WITH NOWAIT;

    CREATE TABLE dbo.sysutility_ucp_mi_volume_space_health_internal
    (
        physical_server_name SYSNAME NOT NULL
        , server_instance_name SYSNAME NOT NULL
        , volume_device_id SYSNAME NOT NULL
        , health_state INT NOT NULL
        , set_number INT NOT NULL DEFAULT(0)
        , processing_time DATETIMEOFFSET(7) NOT NULL DEFAULT(SYSDATETIMEOFFSET())

        CONSTRAINT [PK_sysutility_ucp_mi_volume_space_health_internal_name] 
          PRIMARY KEY CLUSTERED (set_number, server_instance_name, volume_device_id)
    )
END
GO	

/**********************************************************************/
/* Create table sysutility_ucp_computer_cpu_health_internal           */
/* This table stores the processor health states for computer         */
/**********************************************************************/

IF(OBJECT_ID(N'dbo.sysutility_ucp_computer_cpu_health_internal', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table dbo.sysutility_ucp_computer_cpu_health_internal', 0, 1) WITH NOWAIT;

    CREATE TABLE dbo.sysutility_ucp_computer_cpu_health_internal
    (
        physical_server_name SYSNAME NOT NULL
        , health_state INT NOT NULL
        , set_number INT NOT NULL DEFAULT(0)
        , processing_time DATETIMEOFFSET(7) NOT NULL DEFAULT(SYSDATETIMEOFFSET())

        CONSTRAINT [PK_sysutility_ucp_computer_cpu_health_internal_name] 
          PRIMARY KEY CLUSTERED (set_number, physical_server_name)
    )
END
GO	

/**********************************************************************/
/* Create view sysutility_ucp_computer_cpu_health                     */
/* This view returns the latest processor health states for computers */
/**********************************************************************/

IF(OBJECT_ID(N'dbo.sysutility_ucp_computer_cpu_health', 'V') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping view dbo.sysutility_ucp_computer_cpu_health', 0, 1) WITH NOWAIT;
    DROP VIEW dbo.sysutility_ucp_computer_cpu_health
END
GO
	CREATE VIEW dbo.sysutility_ucp_computer_cpu_health 
	AS
	SELECT t.physical_server_name,
			t.health_state,
			t.processing_time
	FROM msdb.dbo.sysutility_ucp_computer_cpu_health_internal AS t
	WHERE t.set_number = (SELECT latest_health_state_id FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])
GO	

/**********************************************************************/
/* Create view sysutility_ucp_mi_volume_space_health            */
/* This view returns the latest volume space health states for computers */
/**********************************************************************/

IF(OBJECT_ID(N'dbo.sysutility_ucp_mi_volume_space_health', 'V') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping view dbo.sysutility_ucp_mi_volume_space_health', 0, 1) WITH NOWAIT;
    DROP VIEW dbo.sysutility_ucp_mi_volume_space_health
END
GO
	CREATE VIEW dbo.sysutility_ucp_mi_volume_space_health 
	AS
	SELECT t.physical_server_name,
			t.server_instance_name,
			t.volume_device_id,
			t.health_state,
			t.processing_time
	FROM msdb.dbo.sysutility_ucp_mi_volume_space_health_internal AS t
	WHERE t.set_number = (SELECT latest_health_state_id FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])
GO	
       
/**********************************************************************/
/* Create view sysutility_ucp_mi_file_space_health              */
/* This view returns the latest file space health states for MI's     */
/**********************************************************************/
   
IF(OBJECT_ID(N'dbo.sysutility_ucp_mi_file_space_health', 'V') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping view dbo.sysutility_ucp_mi_file_space_health', 0, 1) WITH NOWAIT;
    DROP VIEW dbo.sysutility_ucp_mi_file_space_health
END
GO
	CREATE VIEW dbo.sysutility_ucp_mi_file_space_health 
	AS
	SELECT t.server_instance_name,
			t.database_name,
			t.fg_name,
			t.file_type,
			(SELECT val FROM dbo.fn_sysutility_ucp_get_aggregated_health(t.over_utilized_count, t.under_utilized_count)) health_state,
			t.processing_time
	FROM msdb.dbo.sysutility_ucp_mi_file_space_health_internal AS t
	WHERE t.set_number = (SELECT latest_health_state_id FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])
GO

/**********************************************************************/
/* Create view sysutility_ucp_mi_database_health               */
/* This view returns the latest database health states for MI's       */
/**********************************************************************/

IF(OBJECT_ID(N'dbo.sysutility_ucp_mi_database_health', 'V') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping view dbo.sysutility_ucp_mi_database_health', 0, 1) WITH NOWAIT;
    DROP VIEW dbo.sysutility_ucp_mi_database_health
END
GO
	CREATE VIEW dbo.sysutility_ucp_mi_database_health 
	AS
	SELECT t.server_instance_name,
			t.database_name,
			(SELECT val FROM dbo.fn_sysutility_ucp_get_aggregated_health(t.over_utilized_count, t.under_utilized_count)) health_state,
			t.processing_time
	FROM msdb.dbo.sysutility_ucp_mi_database_health_internal AS t
	WHERE t.set_number = (SELECT latest_health_state_id FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])
GO

/**********************************************************************/
/* Create view sysutility_ucp_dac_database_file_space_health             */
/* This view returns the latest filespace health states for DAC's     */
/**********************************************************************/

IF(OBJECT_ID(N'dbo.sysutility_ucp_dac_database_file_space_health', 'V') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping view dbo.sysutility_ucp_dac_database_file_space_health', 0, 1) WITH NOWAIT;
    DROP VIEW dbo.sysutility_ucp_dac_database_file_space_health
END
GO
	CREATE VIEW dbo.sysutility_ucp_dac_database_file_space_health 
	AS
	SELECT  t.dac_name
			, t.dac_server_instance_name
			, t.fg_name
			, t.file_type
			, (SELECT val FROM dbo.fn_sysutility_ucp_get_aggregated_health(t.over_utilized_count, t.under_utilized_count)) health_state
			, t.processing_time
	FROM msdb.dbo.sysutility_ucp_dac_file_space_health_internal AS t
	WHERE t.set_number = (SELECT latest_health_state_id FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])
GO    

/**********************************************************************/
/* Create table sysutility_ucp_aggregated_dac_health_internal                  */
/* This table stores the aggregated health statistics for DAC's                */
/**********************************************************************/

IF(OBJECT_ID(N'dbo.sysutility_ucp_aggregated_dac_health_internal', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table dbo.sysutility_ucp_aggregated_dac_health_internal', 0, 1) WITH NOWAIT;

	CREATE TABLE dbo.sysutility_ucp_aggregated_dac_health_internal
	(
		dac_count INT NOT NULL DEFAULT(0)
		, dac_healthy_count INT NOT NULL DEFAULT(0)
		, dac_unhealthy_count INT NOT NULL DEFAULT(0)
		, dac_over_utilize_count INT NOT NULL DEFAULT(0)
		, dac_under_utilize_count INT NOT NULL DEFAULT(0)
		, dac_on_over_utilized_computer_count INT NOT NULL DEFAULT(0)
		, dac_on_under_utilized_computer_count INT NOT NULL DEFAULT(0)
		, dac_with_files_on_over_utilized_volume_count INT NOT NULL DEFAULT(0)
		, dac_with_files_on_under_utilized_volume_count INT NOT NULL DEFAULT(0)
		, dac_with_over_utilized_file_count INT NOT NULL DEFAULT(0)
		, dac_with_under_utilized_file_count INT NOT NULL DEFAULT(0)
		, dac_with_over_utilized_processor_count INT NOT NULL DEFAULT(0)
		, dac_with_under_utilized_processor_count INT NOT NULL DEFAULT(0)
		, set_number INT NOT NULL DEFAULT(0)
	)
END
GO

/**********************************************************************/
/* Create table sysutility_ucp_aggregated_mi_health_internal                   */
/* This table stores the aggregated health statistics for MI's                 */
/**********************************************************************/

IF(OBJECT_ID(N'dbo.sysutility_ucp_aggregated_mi_health_internal', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table dbo.sysutility_ucp_aggregated_mi_health_internal', 0, 1) WITH NOWAIT;

	CREATE TABLE sysutility_ucp_aggregated_mi_health_internal
	(
		mi_count INT NOT NULL DEFAULT(0)
		, mi_healthy_count INT NOT NULL DEFAULT(0)
		, mi_unhealthy_count INT NOT NULL DEFAULT(0)
		, mi_over_utilize_count INT NOT NULL DEFAULT(0)
		, mi_under_utilize_count INT NOT NULL DEFAULT(0)
		, mi_on_over_utilized_computer_count INT NOT NULL DEFAULT(0)
		, mi_on_under_utilized_computer_count INT NOT NULL DEFAULT(0)
		, mi_with_files_on_over_utilized_volume_count INT NOT NULL DEFAULT(0)
		, mi_with_files_on_under_utilized_volume_count INT NOT NULL DEFAULT(0)
		, mi_with_over_utilized_file_count INT NOT NULL DEFAULT(0)
		, mi_with_under_utilized_file_count INT NOT NULL DEFAULT(0)
		, mi_with_over_utilized_processor_count INT NOT NULL DEFAULT(0)
		, mi_with_under_utilized_processor_count INT NOT NULL DEFAULT(0)
		, set_number INT NOT NULL DEFAULT(0)
	)
END
GO

/**********************************************************************/
/* Create table sysutility_ucp_dac_health_internal                           */
/* This table stores the resource health states for DAC's             */
/**********************************************************************/

IF(OBJECT_ID(N'dbo.sysutility_ucp_dac_health_internal', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table dbo.sysutility_ucp_dac_health_internal', 0, 1) WITH NOWAIT;

	CREATE TABLE sysutility_ucp_dac_health_internal
	(
		dac_name SYSNAME NOT NULL
		, dac_server_instance_name SYSNAME NOT NULL
		, is_volume_space_over_utilized INT NOT NULL DEFAULT(0)
		, is_volume_space_under_utilized INT NOT NULL DEFAULT(0)
		, is_computer_processor_over_utilized INT NOT NULL DEFAULT(0)
		, is_computer_processor_under_utilized INT NOT NULL DEFAULT(0)
		, is_file_space_over_utilized INT NOT NULL DEFAULT(0)
		, is_file_space_under_utilized INT NOT NULL DEFAULT(0)
		, is_dac_processor_over_utilized INT NOT NULL DEFAULT(0)
		, is_dac_processor_under_utilized INT NOT NULL DEFAULT(0)
		, is_policy_overridden BIT NOT NULL DEFAULT(0)
		, set_number INT NOT NULL DEFAULT(0)
		, processing_time DATETIMEOFFSET(7) NOT NULL DEFAULT(SYSDATETIMEOFFSET())

		CONSTRAINT [PK_sysutility_ucp_dac_health_internal_name] 
		  PRIMARY KEY CLUSTERED (set_number, dac_server_instance_name, dac_name)
	)
END
GO

/**********************************************************************/
/* Create table sysutility_ucp_mi_health_internal                           */
/* This table stores the resource health states for MI's              */
/**********************************************************************/

IF(OBJECT_ID(N'dbo.sysutility_ucp_mi_health_internal', 'U') IS NULL)
BEGIN
   RAISERROR ('Creating table dbo.sysutility_ucp_mi_health_internal', 0, 1) WITH NOWAIT;

	CREATE TABLE sysutility_ucp_mi_health_internal
	(
		mi_name SYSNAME NOT NULL
		, is_volume_space_over_utilized INT NOT NULL DEFAULT(0)
		, is_volume_space_under_utilized INT NOT NULL DEFAULT(0) 
		, is_computer_processor_over_utilized INT NOT NULL DEFAULT(0)
		, is_computer_processor_under_utilized INT NOT NULL DEFAULT(0)
		, is_file_space_over_utilized INT NOT NULL DEFAULT(0)
		, is_file_space_under_utilized INT NOT NULL DEFAULT(0)
		, is_mi_processor_over_utilized INT NOT NULL DEFAULT(0)
		, is_mi_processor_under_utilized INT NOT NULL DEFAULT(0)
		, is_policy_overridden BIT NOT NULL DEFAULT(0)
		, set_number INT NOT NULL DEFAULT(0)
		, processing_time DATETIMEOFFSET(7) NOT NULL DEFAULT(SYSDATETIMEOFFSET())

		CONSTRAINT [PK_sysutility_ucp_mi_health_internal_name] 
		  PRIMARY KEY CLUSTERED (set_number, mi_name)
	)
END
GO

/**********************************************************************/
/* Create the sysutility_ucp_aggregated_dac_health               */
/* This view returns the latest health statistics for DAC's           */
/**********************************************************************/
IF object_id(N'dbo.sysutility_ucp_aggregated_dac_health', 'V') IS NOT NULL
BEGIN
   RAISERROR ('Dropping view dbo.sysutility_ucp_mi_health_internal', 0, 1) WITH NOWAIT;
	DROP VIEW dbo.sysutility_ucp_aggregated_dac_health
END;
GO

RAISERROR ('Creating view dbo.sysutility_ucp_aggregated_dac_health', 0, 1) WITH NOWAIT;
GO
CREATE VIEW dbo.sysutility_ucp_aggregated_dac_health 
AS
SELECT t.dac_count
	   , t.dac_healthy_count
	   , t.dac_unhealthy_count
	   , t.dac_over_utilize_count
	   , t.dac_under_utilize_count
	   , t.dac_on_over_utilized_computer_count
	   , t.dac_on_under_utilized_computer_count
	   , t.dac_with_files_on_over_utilized_volume_count
	   , t.dac_with_files_on_under_utilized_volume_count
	   , t.dac_with_over_utilized_file_count
	   , t.dac_with_under_utilized_file_count
	   , t.dac_with_over_utilized_processor_count
	   , t.dac_with_under_utilized_processor_count
FROM msdb.dbo.sysutility_ucp_aggregated_dac_health_internal AS t
WHERE t.set_number = (SELECT latest_health_state_id FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])        
GO

/**********************************************************************/
/* Create the sysutility_ucp_aggregated_mi_health                */
/* This view returns the latest health statistics for MI's            */
/**********************************************************************/
IF object_id(N'dbo.sysutility_ucp_aggregated_mi_health', 'V') IS NOT NULL
BEGIN
   RAISERROR ('Dropping view dbo.sysutility_ucp_aggregated_mi_health', 0, 1) WITH NOWAIT;
	DROP VIEW dbo.sysutility_ucp_aggregated_mi_health
END;
GO

RAISERROR ('Creating view dbo.sysutility_ucp_aggregated_mi_health', 0, 1) WITH NOWAIT;
GO
CREATE VIEW dbo.sysutility_ucp_aggregated_mi_health 
AS
SELECT t.mi_count
	   , t.mi_healthy_count
	   , t.mi_unhealthy_count
	   , t.mi_over_utilize_count
	   , t.mi_under_utilize_count
	   , t.mi_on_over_utilized_computer_count
	   , t.mi_on_under_utilized_computer_count
	   , t.mi_with_files_on_over_utilized_volume_count
	   , t.mi_with_files_on_under_utilized_volume_count
	   , t.mi_with_over_utilized_file_count
	   , t.mi_with_under_utilized_file_count
	   , t.mi_with_over_utilized_processor_count
	   , t.mi_with_under_utilized_processor_count
FROM msdb.dbo.sysutility_ucp_aggregated_mi_health_internal AS t
WHERE t.set_number = (SELECT latest_health_state_id FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])
GO

/**********************************************************************/
/* Create the sysutility_ucp_dac_health                        */
/* This view returns the latest resource health states for DAC's      */
/**********************************************************************/
IF object_id(N'dbo.sysutility_ucp_dac_health', 'V') IS NOT NULL
BEGIN
   RAISERROR ('Dropping view dbo.sysutility_ucp_dac_health', 0, 1) WITH NOWAIT;
	DROP VIEW dbo.sysutility_ucp_dac_health
END;
GO

RAISERROR ('Creating view dbo.sysutility_ucp_dac_health', 0, 1) WITH NOWAIT;
GO
CREATE VIEW dbo.sysutility_ucp_dac_health 
AS
SELECT t.dac_name
	   , t.dac_server_instance_name
	   , (SELECT val FROM dbo.fn_sysutility_ucp_get_aggregated_health(t.is_volume_space_over_utilized, t.is_volume_space_under_utilized)) volume_space_health_state  
	   , (SELECT val FROM dbo.fn_sysutility_ucp_get_aggregated_health(t.is_computer_processor_over_utilized, t.is_computer_processor_under_utilized)) computer_processor_health_state  
	   , (SELECT val FROM dbo.fn_sysutility_ucp_get_aggregated_health(t.is_file_space_over_utilized, t.is_file_space_under_utilized)) file_space_health_state  
	   , (SELECT val FROM dbo.fn_sysutility_ucp_get_aggregated_health(t.is_dac_processor_over_utilized, t.is_dac_processor_under_utilized)) dac_processor_health_state  
	   , CASE WHEN (is_volume_space_over_utilized > 0) THEN CONVERT(BIT, 1) ELSE CONVERT(BIT, 0) END AS contains_over_utilized_volumes
	   , CASE WHEN (is_volume_space_under_utilized > 0) THEN CONVERT(BIT, 1) ELSE CONVERT(BIT, 0) END AS contains_under_utilized_volumes 
	   , CASE WHEN (is_file_space_over_utilized > 0) THEN CONVERT(BIT, 1) ELSE CONVERT(BIT, 0) END AS contains_over_utilized_filegroups
	   , CASE WHEN (is_file_space_under_utilized > 0) THEN CONVERT(BIT, 1) ELSE CONVERT(BIT, 0) END AS contains_under_utilized_filegroups
	   , t.is_policy_overridden
	   , t.processing_time
FROM msdb.dbo.sysutility_ucp_dac_health_internal AS t
WHERE t.set_number = (SELECT latest_health_state_id FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])
GO

/**********************************************************************/
/* Create the sysutility_ucp_mi_health                         */
/* This view returns the latest resource health states for MI's       */
/**********************************************************************/
IF object_id(N'dbo.sysutility_ucp_mi_health', 'V') IS NOT NULL
BEGIN
   RAISERROR ('Dropping view dbo.sysutility_ucp_mi_health', 0, 1) WITH NOWAIT;
	DROP VIEW dbo.sysutility_ucp_mi_health
END;
GO

RAISERROR ('Creating view dbo.sysutility_ucp_mi_health', 0, 1) WITH NOWAIT;
GO
CREATE VIEW dbo.sysutility_ucp_mi_health 
AS
SELECT t.mi_name
	   , (SELECT val FROM dbo.fn_sysutility_ucp_get_aggregated_health(t.is_volume_space_over_utilized, t.is_volume_space_under_utilized)) volume_space_health_state
	   , (SELECT val FROM dbo.fn_sysutility_ucp_get_aggregated_health(t.is_computer_processor_over_utilized, t.is_computer_processor_under_utilized)) computer_processor_health_state
	   , (SELECT val FROM dbo.fn_sysutility_ucp_get_aggregated_health(t.is_file_space_over_utilized, t.is_file_space_under_utilized)) file_space_health_state
	   , (SELECT val FROM dbo.fn_sysutility_ucp_get_aggregated_health(t.is_mi_processor_over_utilized, t.is_mi_processor_under_utilized)) mi_processor_health_state
	   , CASE WHEN (is_volume_space_over_utilized > 0) THEN CONVERT(BIT, 1) ELSE CONVERT(BIT, 0) END AS contains_over_utilized_volumes
	   , CASE WHEN (is_volume_space_under_utilized > 0) THEN CONVERT(BIT, 1) ELSE CONVERT(BIT, 0) END AS contains_under_utilized_volumes 
	   , CASE WHEN (is_file_space_over_utilized > 0) THEN CONVERT(BIT, 1) ELSE CONVERT(BIT, 0) END AS contains_over_utilized_databases
	   , CASE WHEN (is_file_space_under_utilized > 0) THEN CONVERT(BIT, 1) ELSE CONVERT(BIT, 0) END AS contains_under_utilized_databases
	   , t.is_policy_overridden
	   , t.processing_time
FROM msdb.dbo.sysutility_ucp_mi_health_internal AS t
WHERE t.set_number = (SELECT latest_health_state_id FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])
GO

/**********************************************************************/
-- Table sysutility_ucp_filegroups_with_policy_violations_internal                           */
-- This table stores an entry for each filegroup that has at least one file
-- that violates the specified policy. 
-- LogFiles are rolled up into an entry (with filegroup_name = N'')   
/**********************************************************************/

IF(OBJECT_ID(N'dbo.sysutility_ucp_filegroups_with_policy_violations_internal', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table dbo.sysutility_ucp_filegroups_with_policy_violations_internal', 0, 1) WITH NOWAIT;
    CREATE TABLE sysutility_ucp_filegroups_with_policy_violations_internal
    (
        server_instance_name SYSNAME,
        database_name SYSNAME,
        [filegroup_name] SYSNAME,  -- N'' for log files
        policy_id INT, -- id of the offending policy
        set_number INT NOT NULL,

        CONSTRAINT [PK_sysutility_ucp_filegroups_with_policy_violations_internal] 
          PRIMARY KEY CLUSTERED (set_number, policy_id, server_instance_name, database_name, [filegroup_name])
    )
END
GO

--*********************************************************************
-- Procedure sp_sysutility_ucp_calculate_filegroups_with_policy_violations 
-- Description: This function identifies filegroups where *every* single file 
--   violates a policy (the same policy for all files in the filegroups). 
--   Logfiles are also captured here under a special entry (with filegroup_name = N'')
--**********************************************************************      
IF OBJECT_ID ('dbo.sp_sysutility_ucp_calculate_filegroups_with_policy_violations') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure dbo.sp_sysutility_ucp_calculate_filegroups_with_policy_violations', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_ucp_calculate_filegroups_with_policy_violations 
END;
GO

RAISERROR ('Creating procedure dbo.sp_sysutility_ucp_calculate_filegroups_with_policy_violations', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE dbo.sp_sysutility_ucp_calculate_filegroups_with_policy_violations 
   @new_set_number INT
WITH EXECUTE AS OWNER
AS
BEGIN
   INSERT INTO sysutility_ucp_filegroups_with_policy_violations_internal(
        server_instance_name, 
        database_name, 
        [filegroup_name],
        policy_id,
        set_number)
      SELECT fg1.server_instance_name,
             fg1.database_name,
             fg1.[filegroup_name],
             fg1.policy_id,
             @new_set_number
      FROM (SELECT pv.policy_id,
                   f.server_instance_name, f.database_name, f.[filegroup_name], 
                   COUNT(*) as policy_violations
            FROM dbo.sysutility_ucp_database_files AS f,
                 dbo.sysutility_ucp_policy_violations AS pv
            WHERE f.powershell_path = pv.target_query_expression 
            GROUP BY pv.policy_id, f.server_instance_name, f.database_name, f.[filegroup_name]) as fg1,
            (SELECT f.server_instance_name, f.database_name, f.[filegroup_name],
                    COUNT(*) as file_count
             FROM dbo.sysutility_ucp_database_files AS f
             GROUP BY f.server_instance_name, f.database_name, f.[filegroup_name]) AS fg2
      WHERE fg1.server_instance_name = fg2.server_instance_name AND
            fg1.database_name = fg2.database_name AND
            fg1.[filegroup_name] = fg2.[filegroup_name] AND
            fg1.policy_violations = fg2.file_count     
END
GO

--*********************************************************************

--*********************************************************************
-- Create procedure sp_sysutility_ucp_calculate_dac_file_space_health 
-- Description: Computes the file space (data/log file group)health state for the DAC's
-- The computed result is consumed by the UI for health state drill down and further
-- compute the rollup health state for the DAC's
--**********************************************************************
IF(OBJECT_ID(N'dbo.sp_sysutility_ucp_calculate_dac_file_space_health', 'P') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping procedure dbo.sp_sysutility_ucp_calculate_dac_file_space_health', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_ucp_calculate_dac_file_space_health
END
GO
      
RAISERROR ('Creating procedure dbo.sp_sysutility_ucp_calculate_dac_file_space_health', 0, 1) WITH NOWAIT;
GO

CREATE PROCEDURE dbo.sp_sysutility_ucp_calculate_dac_file_space_health 
  @new_set_number INT
WITH EXECUTE AS OWNER
AS
BEGIN    
    DECLARE @under_utilize_type INT = 1
    DECLARE @over_utilize_type INT = 2;

    -- space_resource_type = 1
    -- datafile_target_type = 2
    -- logfile_target_type = 3
    
    INSERT INTO msdb.dbo.sysutility_ucp_dac_file_space_health_internal(
       dac_name, dac_server_instance_name, 
       fg_name, set_number, processing_time
       , over_utilized_count
       , under_utilized_count
       , file_type)        

    -- Insert the dac filegroup utilization details
    SELECT dd.dac_name
        , dd.dac_server_instance_name
        , fg.Name AS file_group_name
        , @new_set_number
        , dd.dac_processing_time
        , SUM(CASE WHEN df.policy_id IS NOT NULL AND dp.utilization_type = 2 THEN 1 ELSE 0 END) AS over_utilized_count
        , SUM(CASE WHEN df.policy_id IS NOT NULL AND dp.utilization_type = 1 THEN 1 ELSE 0 END) AS under_utilized_count
        , fg.file_type
    FROM msdb.dbo.sysutility_ucp_deployed_dacs AS dd
        INNER JOIN (SELECT 1 AS file_type, server_instance_name, database_name, [Name], processing_time
                    FROM msdb.dbo.sysutility_ucp_filegroups 
                    UNION ALL
                    SELECT 2 AS file_type, server_instance_name, Name as database_name, N'' AS [Name], processing_time
                    FROM msdb.dbo.sysutility_ucp_databases) AS fg
          ON dd.dac_server_instance_name = fg.server_instance_name AND 
             dd.dac_name = fg.database_name
        INNER JOIN msdb.dbo.sysutility_ucp_dac_policies AS dp 
          ON dp.dac_name = dd.dac_name AND 
             dp.dac_server_instance_name = dd.dac_server_instance_name
        LEFT JOIN msdb.dbo.sysutility_ucp_filegroups_with_policy_violations_internal AS df 
          ON df.server_instance_name = dd.dac_server_instance_name AND 
             df.database_name = dd.dac_name AND 
             fg.Name = df.filegroup_name AND 
             dp.policy_id = df.policy_id AND
             df.set_number = @new_set_number
    WHERE dp.resource_type = 1
        AND dp.target_type = fg.file_type + 1 -- target_type = 2 (datafile); 3 (logfile)
    GROUP BY dd.dac_name, dd.dac_server_instance_name, fg.Name , fg.file_type, dd.dac_processing_time            
END
GO

--*********************************************************************
-- Create procedure sp_sysutility_ucp_calculate_mi_file_space_health 
-- Description: Computes the file space (data/log file group)health state for the MI's
-- The computed result is consumed by the UI for health state drill down and further 
-- compute the rollup health state for the MI's
--**********************************************************************
IF(OBJECT_ID(N'dbo.sp_sysutility_ucp_calculate_mi_file_space_health', 'P') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping procedure dbo.sp_sysutility_ucp_calculate_mi_file_space_health', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_ucp_calculate_mi_file_space_health
END
GO

RAISERROR ('Creating procedure dbo.sp_sysutility_ucp_calculate_mi_file_space_health', 0, 1) WITH NOWAIT;
GO

CREATE PROCEDURE dbo.sp_sysutility_ucp_calculate_mi_file_space_health 
    @new_set_number INT
WITH EXECUTE AS OWNER
AS
BEGIN
    DECLARE @under_utilize_type INT = 1
    DECLARE @over_utilize_type INT = 2;


    -- space_resource_type = 1
    -- datafile_target_type = 2
    -- logfile_target_type = 3

    INSERT INTO msdb.dbo.sysutility_ucp_mi_file_space_health_internal(
           server_instance_name
           , database_name
           , fg_name
           , set_number
           , processing_time
           , over_utilized_count
           , under_utilized_count
           , file_type)                         
      -- Insert the server filegroup utilization details
      SELECT fg.server_instance_name
             , fg.database_name
             , fg.Name AS file_group_name
             , @new_set_number
             , fg.processing_time
             , SUM(CASE WHEN df.policy_id IS NOT NULL AND ip.utilization_type = 2 THEN 1 ELSE 0 END) AS over_utilized_count
             , SUM(CASE WHEN df.policy_id IS NOT NULL AND ip.utilization_type = 1 THEN 1 ELSE 0 END) AS under_utilized_count
             , fg.file_type
      FROM (SELECT 1 AS file_type, fg.server_instance_name, fg.database_name, fg.Name, fg.processing_time
            FROM msdb.dbo.sysutility_ucp_filegroups AS fg  
            UNION ALL
            SELECT 2 AS file_type, db.server_instance_name, db.Name AS database_name, N'' AS Name, db.processing_time
            FROM msdb.dbo.sysutility_ucp_databases AS db) AS fg
        INNER JOIN msdb.dbo.sysutility_ucp_instance_policies AS ip ON fg.server_instance_name = ip.server_instance_name
        LEFT JOIN msdb.dbo.sysutility_ucp_filegroups_with_policy_violations_internal AS df 
            ON fg.server_instance_name = df.server_instance_name AND 
               fg.database_name = df.database_name AND
               fg.Name = df.[filegroup_name] AND 
               df.set_number = @new_set_number AND
               ip.policy_id = df.policy_id
    WHERE ip.resource_type = 1
        AND ip.target_type = file_type + 1 -- target_type = 2 (datafile), 3 (logfile)
    GROUP BY fg.server_instance_name, fg.database_name, fg.Name, fg.file_type, fg.processing_time        
   
    -- Compute the database health state for the MI's based on the file-space computation.
    
     -- Insert the server database utilization details
    INSERT INTO msdb.dbo.sysutility_ucp_mi_database_health_internal(server_instance_name, database_name, set_number, processing_time
           , over_utilized_count
           , under_utilized_count)
    SELECT fs.server_instance_name
        , fs.database_name AS database_name
        , @new_set_number
        , svr.processing_time
        , SUM(fs.over_utilized_count) AS over_utilized_count
        , SUM(fs.under_utilized_count) AS under_utilized_count
    FROM  msdb.dbo.sysutility_ucp_mi_file_space_health_internal AS fs
        , msdb.dbo.sysutility_ucp_instances AS svr
    WHERE svr.Name = fs.server_instance_name AND
          fs.set_number = @new_set_number        
    GROUP BY fs.server_instance_name, fs.database_name, svr.processing_time        
    
END
GO

--*********************************************************************
-- Create procedure sp_sysutility_ucp_calculate_computer_health 
-- Description: Computes the volume space health state for the computer.
-- The computed result is consumed by the DAC / MI to determine rollup health state 
--**********************************************************************        
IF(OBJECT_ID(N'dbo.sp_sysutility_ucp_calculate_computer_health', 'P') IS NOT NULL)
BEGIN
    RAISERROR ('Dropping procedure dbo.sp_sysutility_ucp_calculate_computer_health', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_ucp_calculate_computer_health
END
GO

RAISERROR ('Creating procedure dbo.sp_sysutility_ucp_calculate_computer_health', 0, 1) WITH NOWAIT;
GO

CREATE PROCEDURE dbo.sp_sysutility_ucp_calculate_computer_health
   @new_set_number INT 
WITH EXECUTE AS OWNER
AS
BEGIN
    DECLARE @under_utilize_type INT = 1
    DECLARE @over_utilize_type INT = 2

    DECLARE @computer_object_type INT = 3
    DECLARE @target_type INT = 6
    DECLARE @space_resource_type INT = 1;

    -- Compute the volume space health state for the computer.

    -- CTE to identify the computer volumes violating the under / over utilization policy
    WITH volume_utilization (physical_server_name, volume_device_id, utilization_type)
    AS
    (
        SELECT vo.physical_server_name, vo.volume_device_id, cp.utilization_type 
        FROM msdb.dbo.sysutility_ucp_computer_policies cp
            , msdb.dbo.sysutility_ucp_volumes vo
            , msdb.dbo.sysutility_ucp_policy_violations pv
        WHERE cp.physical_server_name = vo.physical_server_name
            AND cp.resource_type = @space_resource_type
            AND cp.target_type = @target_type
            AND pv.policy_id = cp.policy_id
            AND pv.target_query_expression = vo.powershell_path
    )
    -- Insert new record
    INSERT INTO msdb.dbo.sysutility_ucp_mi_volume_space_health_internal(physical_server_name, server_instance_name, volume_device_id, set_number, processing_time
           ,health_state)
    SELECT CAST(svr.ComputerNamePhysicalNetBIOS AS SYSNAME), 
           CAST(svr.Name AS SYSNAME), 
           vol.volume_device_id, 
           @new_set_number, 
           svr.processing_time,
		   CASE WHEN (@over_utilize_type = ISNULL(vu.utilization_type, 0))
			 THEN 3 -- over utilized
			 WHEN (@under_utilize_type = ISNULL(vu.utilization_type, 0))
			 THEN 2 -- under utilized
			 ELSE 1 -- healthy
		   END 
    FROM msdb.dbo.sysutility_ucp_instances AS svr
      INNER JOIN msdb.dbo.sysutility_ucp_volumes AS vol ON vol.physical_server_name = svr.ComputerNamePhysicalNetBIOS
      LEFT JOIN volume_utilization vu ON vol.physical_server_name = vu.physical_server_name AND vol.volume_device_id = vu.volume_device_id
   
   -- Computes the processor health state for the computer.

    -- Cache view data into temp table
    SELECT *
    INTO #computer_policies     
    FROM dbo.sysutility_ucp_computer_policies

    -- Get the computer cpu utilization based on processor violating the health policy 
    -- Mark the computer as unhealthy if processor violate the policy
    SELECT cp.physical_server_name as physical_server_name
        , SUM(CASE WHEN cp.utilization_type = 1 THEN 1 ELSE 0 END) AS under_utilized_count
        , SUM(CASE WHEN cp.utilization_type = 2 THEN 1 ELSE 0 END) AS over_utilized_count
    INTO #computer_cpu_utilization        
    FROM #computer_policies cp 
    INNER JOIN dbo.sysutility_ucp_policy_violations pv
        ON cp.policy_id = pv.policy_id AND cp.powershell_path = pv.target_query_expression
    WHERE cp.resource_type = 3      -- processor_resource_type
        AND cp.target_type = 1      -- computer_target_type
    GROUP BY cp.physical_server_name   

    -- Insert new record
    INSERT INTO msdb.dbo.sysutility_ucp_computer_cpu_health_internal(physical_server_name, set_number, processing_time, health_state)
    SELECT c.physical_server_name
        , @new_set_number
        , c.processing_time,
    CASE WHEN 0 < ISNULL(cu.over_utilized_count, 0) THEN 
        3 -- over utilized
    WHEN 0 < ISNULL(cu.under_utilized_count, 0) THEN 
        2 -- under utilized
    ELSE 1 -- healthy 
    END AS health_state
    FROM msdb.dbo.sysutility_ucp_computers AS c
    LEFT JOIN #computer_cpu_utilization cu
    ON c.physical_server_name = cu.physical_server_name

END
GO

--*********************************************************************
-- Create procedure sp_sysutility_ucp_calculate_dac_health 
-- Description: This is the top level procedure that computes all
-- the resource health states for the DAC.
--**********************************************************************   
IF OBJECT_ID ('dbo.sp_sysutility_ucp_calculate_dac_health') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure dbo.sp_sysutility_ucp_calculate_dac_health', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_ucp_calculate_dac_health 
END;
GO

RAISERROR ('Creating procedure dbo.sp_sysutility_ucp_calculate_dac_health', 0, 1) WITH NOWAIT;
GO

CREATE PROCEDURE dbo.sp_sysutility_ucp_calculate_dac_health 
   @new_set_number INT
WITH EXECUTE AS OWNER
AS
BEGIN

    -- Compute dac filegroup/log files health state
    EXEC msdb.dbo.sp_sysutility_ucp_calculate_dac_file_space_health @new_set_number;

    -- Compute dac health state
	
    -- Insert new records
    SELECT dd.dac_server_instance_name
        , dd.dac_name
        , SUM(CASE WHEN hs.health_state = 2 THEN 1 ELSE 0 END) AS under_utilized_count
        , SUM(CASE WHEN hs.health_state = 3 THEN 1 ELSE 0 END) AS over_utilized_count
    INTO #dac_volume_file_space_utilization 
    FROM msdb.dbo.sysutility_ucp_deployed_dacs AS dd
    INNER JOIN msdb.dbo.sysutility_ucp_mi_volume_space_health_internal AS hs 
        ON hs.server_instance_name = dd.dac_server_instance_name
    INNER JOIN (
        SELECT server_instance_name, database_name, volume_device_id FROM sysutility_ucp_datafiles 
        UNION ALL
        SELECT server_instance_name, database_name, volume_device_id FROM sysutility_ucp_logfiles
    ) AS df 
        ON df.volume_device_id = hs.volume_device_id
        AND dd.dac_server_instance_name = df.server_instance_name 
        AND dd.dac_name = df.database_name
    WHERE hs.set_number = @new_set_number 
    GROUP BY dd.dac_server_instance_name, dd.dac_name;
			  
    SELECT dd.dac_server_instance_name
        , dd.dac_name
        , SUM(CASE WHEN hs.health_state = 2 THEN 1 ELSE 0 END) AS under_utilized_count
        , SUM(CASE WHEN hs.health_state = 3 THEN 1 ELSE 0 END) AS over_utilized_count
    INTO #dac_computer_cpu_utilization 
    FROM msdb.dbo.sysutility_ucp_computer_cpu_health_internal AS hs
    INNER JOIN msdb.dbo.sysutility_ucp_deployed_dacs AS dd 
        ON hs.physical_server_name = dd.dac_physical_server_name
    WHERE hs.set_number = @new_set_number 
    GROUP BY dd.dac_server_instance_name, dd.dac_name;
    
    SELECT hs.dac_server_instance_name
        , hs.dac_name
        , SUM(CASE WHEN health_state.val = 2 THEN 1 ELSE 0 END) AS under_utilized_count
        , SUM(CASE WHEN health_state.val = 3 THEN 1 ELSE 0 END) AS over_utilized_count
    INTO #dac_file_space_utilization 
    FROM msdb.dbo.sysutility_ucp_dac_file_space_health_internal hs
    CROSS APPLY dbo.fn_sysutility_ucp_get_aggregated_health(hs.over_utilized_count, hs.under_utilized_count) health_state
    WHERE hs.set_number = @new_set_number 
    GROUP BY hs.dac_server_instance_name, hs.dac_name;

    -- Cache view data into temp table
    SELECT * 
    INTO #dac_policies
    FROM dbo.sysutility_ucp_dac_policies  
    
    -- Get the database cpu utilization based on processor violating the health policy 
    -- Mark the database as unhealthy if processor violate the policy    
    SELECT dp.dac_name
        , dp.dac_server_instance_name
        , SUM(CASE WHEN dp.utilization_type = 1 THEN 1 ELSE 0 END) AS under_utilized_count
        , SUM(CASE WHEN dp.utilization_type = 2 THEN 1 ELSE 0 END) AS over_utilized_count
    INTO #dac_cpu_utilizations       
    FROM #dac_policies AS dp
    INNER JOIN dbo.sysutility_ucp_policy_violations pv
        ON dp.policy_id = pv.policy_id AND dp.powershell_path = pv.target_query_expression
    WHERE dp.resource_type = 3      -- processor_resource_type
        AND dp.target_type = 5      -- database_target_type
    GROUP BY dp.dac_name, dp.dac_server_instance_name    
    INSERT INTO msdb.dbo.sysutility_ucp_dac_health_internal(dac_name, dac_server_instance_name, set_number
	       , processing_time
	       , is_volume_space_over_utilized
	       , is_volume_space_under_utilized
	       , is_computer_processor_over_utilized
	       , is_computer_processor_under_utilized
	       , is_file_space_over_utilized
	       , is_file_space_under_utilized
	       , is_dac_processor_over_utilized
	       , is_dac_processor_under_utilized
	       , is_policy_overridden)
    SELECT dd.dac_name
        , dd.dac_server_instance_name
        , @new_set_number
	    , dd.dac_processing_time
	    , vu.over_utilized_count AS dac_volume_space_over_utilized_count
	    , vu.under_utilized_count AS dac_volume_space_under_utilized_count
	    , cu.over_utilized_count AS dac_computer_cpu_over_utilized_count
	    , cu.under_utilized_count AS dac_computer_cpu_under_utilized_count 
        , su.over_utilized_count AS dac_file_space_over_utilized_count
        , su.under_utilized_count AS dac_file_space_under_utilized_count
	    , ISNULL(du.over_utilized_count ,0) AS dac_cpu_over_utilized_count
	    , ISNULL(du.under_utilized_count ,0) AS dac_cpu_under_utilized_count
	    , pt.is_policy_overridden
    FROM msdb.dbo.sysutility_ucp_deployed_dacs dd
    LEFT JOIN #dac_cpu_utilizations du 
        ON dd.dac_name = du.dac_name AND dd.dac_server_instance_name = du.dac_server_instance_name
    INNER JOIN #dac_volume_file_space_utilization AS vu 
        ON dd.dac_name = vu.dac_name AND dd.dac_server_instance_name = vu.dac_server_instance_name            
    INNER JOIN #dac_computer_cpu_utilization AS cu 
        ON dd.dac_name = cu.dac_name AND dd.dac_server_instance_name = cu.dac_server_instance_name
    INNER JOIN #dac_file_space_utilization AS su 
        ON dd.dac_name = su.dac_name AND dd.dac_server_instance_name = su.dac_server_instance_name
    INNER JOIN msdb.dbo.sysutility_ucp_dac_policy_type pt
        ON dd.dac_name = pt.dac_name AND dd.dac_server_instance_name = pt.dac_server_instance_name;

END
GO    

--*********************************************************************
-- Create procedure sp_sysutility_ucp_calculate_aggregated_dac_health 
-- Description: This is the top level procedure that computes the health
-- statistics for the DAC. This uses the pre-computed health states for DAC's
-- The resulting health statistics are used in dashboard display
--**********************************************************************       
IF OBJECT_ID ('dbo.sp_sysutility_ucp_calculate_aggregated_dac_health') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure dbo.sp_sysutility_ucp_calculate_aggregated_dac_health', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_ucp_calculate_aggregated_dac_health 
END;
GO

RAISERROR ('Creating procedure dbo.sp_sysutility_ucp_calculate_aggregated_dac_health', 0, 1) WITH NOWAIT;
GO

CREATE PROCEDURE dbo.sp_sysutility_ucp_calculate_aggregated_dac_health 
   @new_set_number INT
WITH EXECUTE AS OWNER
AS
BEGIN
       
    -- DacCount
    DECLARE @dac_count INT = 0
    SELECT @dac_count = COUNT(*) 
    FROM msdb.dbo.sysutility_ucp_dac_health_internal hs
    WHERE hs.set_number = @new_set_number

    -- DacOverUtilizeCount
    DECLARE @dac_over_utilize_count INT = 0
    SELECT @dac_over_utilize_count = COUNT(*)
    FROM msdb.dbo.sysutility_ucp_dac_health_internal hs
    WHERE hs.set_number = @new_set_number AND
          (0 != hs.is_dac_processor_over_utilized OR
           0 != hs.is_computer_processor_over_utilized OR
           0 != hs.is_file_space_over_utilized OR
           0 != hs.is_volume_space_over_utilized)

    -- DacUnderUtilizeCount
    DECLARE @dac_under_utilize_count INT = 0
    SELECT @dac_under_utilize_count = COUNT(*)
    FROM msdb.dbo.sysutility_ucp_dac_health_internal hs
    WHERE hs.set_number = @new_set_number AND
          (0 != hs.is_dac_processor_under_utilized OR
           0 != hs.is_computer_processor_under_utilized OR
           0 != hs.is_file_space_under_utilized OR
           0 != hs.is_volume_space_under_utilized)
           AND 0 = hs.is_dac_processor_over_utilized 
           AND 0 = hs.is_computer_processor_over_utilized 
           AND 0 = hs.is_file_space_over_utilized 
           AND 0 = hs.is_volume_space_over_utilized
    	   
    -- DacUnhealthyCount
    DECLARE @dac_unhealthy_count INT = 0
    SELECT @dac_unhealthy_count = @dac_over_utilize_count + @dac_under_utilize_count;

    -- DacHealthyCount
    DECLARE @dac_healthy_count INT = 0
    SELECT @dac_healthy_count = COUNT(*)
    FROM msdb.dbo.sysutility_ucp_dac_health_internal hs
    WHERE hs.set_number = @new_set_number 
    AND 0 = hs.is_dac_processor_under_utilized 
    AND 0 = hs.is_computer_processor_under_utilized 
    AND 0 = hs.is_file_space_under_utilized 
    AND 0 = hs.is_volume_space_under_utilized
    AND 0 = hs.is_dac_processor_over_utilized 
    AND 0 = hs.is_computer_processor_over_utilized 
    AND 0 = hs.is_file_space_over_utilized 
    AND 0 = hs.is_volume_space_over_utilized        

    -- Insert new record
    INSERT INTO msdb.dbo.sysutility_ucp_aggregated_dac_health_internal(set_number
            , dac_count
            , dac_healthy_count
            , dac_unhealthy_count
            , dac_over_utilize_count
            , dac_under_utilize_count
            , dac_on_over_utilized_computer_count
            , dac_on_under_utilized_computer_count
            , dac_with_files_on_over_utilized_volume_count
            , dac_with_files_on_under_utilized_volume_count
            , dac_with_over_utilized_file_count
            , dac_with_under_utilized_file_count
            , dac_with_over_utilized_processor_count
            , dac_with_under_utilized_processor_count)
    SELECT @new_set_number
            , @dac_count 
            , @dac_healthy_count 
            , @dac_unhealthy_count 
            , @dac_over_utilize_count 
            , @dac_under_utilize_count 
            , ISNULL(SUM(CASE WHEN 0 < hs.is_computer_processor_over_utilized THEN 1 ELSE 0 END), 0)
            , ISNULL(SUM(CASE WHEN 0 < hs.is_computer_processor_under_utilized THEN 1 ELSE 0 END), 0)
            , ISNULL(SUM(CASE WHEN 0 < hs.is_volume_space_over_utilized THEN 1 ELSE 0 END), 0)
            , ISNULL(SUM(CASE WHEN 0 < hs.is_volume_space_under_utilized THEN 1 ELSE 0 END), 0)
            , ISNULL(SUM(CASE WHEN 0 < hs.is_file_space_over_utilized THEN 1 ELSE 0 END), 0)
            , ISNULL(SUM(CASE WHEN 0 < hs.is_file_space_under_utilized THEN 1 ELSE 0 END), 0)
            , ISNULL(SUM(CASE WHEN 0 < hs.is_dac_processor_over_utilized THEN 1 ELSE 0 END), 0)
            , ISNULL(SUM(CASE WHEN 0 < hs.is_dac_processor_under_utilized THEN 1 ELSE 0 END), 0)
    FROM msdb.dbo.sysutility_ucp_dac_health_internal hs   
    WHERE hs.set_number = @new_set_number       

END
GO

--*********************************************************************
-- Create procedure sp_sysutility_ucp_calculate_mi_health 
-- Description: This is the top level procedure that computes all
-- the resource health states for the MI.
--**********************************************************************      
IF OBJECT_ID ('dbo.sp_sysutility_ucp_calculate_mi_health') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure dbo.sp_sysutility_ucp_calculate_mi_health', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_ucp_calculate_mi_health 
END;
GO

RAISERROR ('Creating procedure dbo.sp_sysutility_ucp_calculate_mi_health', 0, 1) WITH NOWAIT;
GO

CREATE PROCEDURE dbo.sp_sysutility_ucp_calculate_mi_health 
   @new_set_number INT
WITH EXECUTE AS OWNER
AS
BEGIN

    -- Compute managed instance database health state
    EXEC msdb.dbo.sp_sysutility_ucp_calculate_mi_file_space_health @new_set_number;

    -- Compute managed instance health state

    -- Insert new record
    SELECT hs.server_instance_name AS server_instance_name, 
       SUM(CASE WHEN health_state.val = 2 THEN 1 ELSE 0 END) AS under_utilized_count,
       SUM(CASE WHEN health_state.val = 3 THEN 1 ELSE 0 END) AS over_utilized_count
       INTO #instance_file_space_utilization 
    FROM msdb.dbo.sysutility_ucp_mi_file_space_health_internal as hs
    CROSS APPLY msdb.dbo.fn_sysutility_ucp_get_aggregated_health(hs.over_utilized_count, hs.under_utilized_count) as health_state
    WHERE hs.set_number = @new_set_number
    GROUP BY hs.server_instance_name;
    
    SELECT sv.Name AS server_instance_name, 
        SUM(CASE WHEN hs.health_state = 2 THEN 1 ELSE 0 END) AS under_utilized_count,
        SUM(CASE WHEN hs.health_state = 3 THEN 1 ELSE 0 END) AS over_utilized_count
    INTO #instance_computer_cpu_utilization 
    FROM msdb.dbo.sysutility_ucp_computer_cpu_health_internal AS hs 
    INNER JOIN msdb.dbo.sysutility_ucp_instances AS sv 
        ON hs.physical_server_name = sv.ComputerNamePhysicalNetBIOS
    WHERE hs.set_number = @new_set_number 
    GROUP BY sv.Name;
            
    SELECT hs.server_instance_name AS server_instance_name, 
        SUM(CASE WHEN hs.health_state = 2 THEN 1 ELSE 0 END) AS under_utilized_count,
        SUM(CASE WHEN hs.health_state = 3 THEN 1 ELSE 0 END) AS over_utilized_count
    INTO #instance_volume_file_space_utilization 
    FROM msdb.dbo.sysutility_ucp_mi_volume_space_health_internal AS hs 
    INNER JOIN (
        SELECT server_instance_name, database_name, volume_device_id FROM dbo.sysutility_ucp_datafiles 
        UNION ALL
        SELECT server_instance_name, database_name, volume_device_id FROM dbo.sysutility_ucp_logfiles
    ) AS df 
        ON hs.volume_device_id = df.volume_device_id AND hs.server_instance_name = df.server_instance_name  
    WHERE hs.set_number = @new_set_number  
    GROUP BY hs.server_instance_name;

    -- Cache view data into temp table
    SELECT *
    INTO #instance_policies
    FROM dbo.sysutility_ucp_instance_policies
    
    -- Get the MI cpu utilization based on processor violating the health policy 
    -- Mark the instance as unhealthy if processor violate the policy
    SELECT ip.server_instance_name AS server_instance_name
        , SUM(CASE WHEN ip.utilization_type = 1 THEN 1 ELSE 0 END) AS under_utilized_count
        , SUM(CASE WHEN ip.utilization_type = 2 THEN 1 ELSE 0 END) AS over_utilized_count
    INTO #instance_cpu_utilization        
    FROM #instance_policies ip
    INNER JOIN dbo.sysutility_ucp_policy_violations pv
        ON ip.policy_id = pv.policy_id AND ip.powershell_path = pv.target_query_expression
    WHERE ip.resource_type = 3      -- processor_resource_type
       AND ip.target_type = 4       -- instance_target_type
    GROUP BY ip.server_instance_name

    INSERT INTO msdb.dbo.sysutility_ucp_mi_health_internal(mi_name, set_number
        , processing_time     
        , is_volume_space_over_utilized
        , is_volume_space_under_utilized
        , is_computer_processor_over_utilized
        , is_computer_processor_under_utilized
        , is_file_space_over_utilized
        , is_file_space_under_utilized
        , is_mi_processor_over_utilized
        , is_mi_processor_under_utilized
        , is_policy_overridden)
    SELECT CAST(sv.Name AS SYSNAME) mi_name
        , @new_set_number
        , sv.processing_time
        , vu.over_utilized_count AS mi_volume_space_over_utilized_count
        , vu.under_utilized_count AS mi_volume_space_under_utilized_count
        , cu.over_utilized_count AS mi_computer_cpu_over_utilized_count
        , cu.under_utilized_count AS mi_computer_cpu_under_utilized_count 
        , su.over_utilized_count AS mi_file_space_over_utilized_count
        , su.under_utilized_count AS mi_file_space_under_utilized_count
        , ISNULL(iu.over_utilized_count ,0) AS mi_cpu_over_utilized_count
        , ISNULL(iu.under_utilized_count ,0) AS mi_cpu_under_utilized_count
        , pt.is_policy_overridden
    FROM msdb.dbo.sysutility_ucp_managed_instances AS mi
    INNER JOIN msdb.dbo.sysutility_ucp_instances AS sv ON sv.Name = mi.instance_name
    LEFT OUTER JOIN #instance_cpu_utilization AS iu ON sv.Name = iu.server_instance_name
    INNER JOIN #instance_volume_file_space_utilization AS vu ON sv.Name = vu.server_instance_name
    INNER JOIN #instance_computer_cpu_utilization AS cu ON sv.Name = cu.server_instance_name
    INNER JOIN #instance_file_space_utilization AS su ON sv.Name = su.server_instance_name
    INNER JOIN msdb.dbo.sysutility_ucp_instance_policy_type AS pt ON sv.Name = pt.server_instance_name;

END
GO
    
--*********************************************************************
-- Create procedure sp_sysutility_ucp_calculate_aggregated_mi_health 
-- Description: This is the top level procedure that computes the health
-- statistics for the MI. This uses the pre-computed health states for MI's
-- The resulting health statistics are used in dashboard display
--**********************************************************************      
IF OBJECT_ID ('dbo.sp_sysutility_ucp_calculate_aggregated_mi_health') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure dbo.sp_sysutility_ucp_calculate_aggregated_mi_health', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_ucp_calculate_aggregated_mi_health 
END;
GO

RAISERROR ('Creating procedure dbo.sp_sysutility_ucp_calculate_aggregated_mi_health', 0, 1) WITH NOWAIT;
GO

CREATE PROCEDURE dbo.sp_sysutility_ucp_calculate_aggregated_mi_health 
   @new_set_number INT
WITH EXECUTE AS OWNER
AS
BEGIN
       
    -- ManagedInstanceCount
    DECLARE @mi_count INT = 0
    SELECT @mi_count = COUNT(*) 
    FROM msdb.dbo.sysutility_ucp_mi_health_internal hs
    WHERE hs.set_number = @new_set_number

    -- ManagedInstanceOverUtilizeCount
    DECLARE @mi_over_utilize_count INT = 0
    SELECT @mi_over_utilize_count = COUNT(*)
    FROM msdb.dbo.sysutility_ucp_mi_health_internal hs
    WHERE hs.set_number = @new_set_number AND
          (0 != hs.is_volume_space_over_utilized OR
           0 != hs.is_computer_processor_over_utilized OR
           0 != hs.is_file_space_over_utilized OR
           0 != hs.is_mi_processor_over_utilized)
           
    -- ManagedInstanceUnderUtilizeCount
    DECLARE @mi_under_utilize_count INT = 0
    SELECT @mi_under_utilize_count = COUNT(*)
    FROM msdb.dbo.sysutility_ucp_mi_health_internal hs
    WHERE hs.set_number = @new_set_number AND
          (0 != hs.is_volume_space_under_utilized OR
           0 != hs.is_computer_processor_under_utilized OR
           0 != hs.is_file_space_under_utilized OR
           0 != hs.is_mi_processor_under_utilized)
           AND 0 = hs.is_volume_space_over_utilized
           AND 0 = hs.is_computer_processor_over_utilized
           AND 0 = hs.is_file_space_over_utilized
           AND 0 = hs.is_mi_processor_over_utilized	           
    	   
    -- ManagedInstanceUnhealthyCount
    DECLARE @mi_unhealthy_count INT = 0
    SELECT @mi_unhealthy_count = @mi_over_utilize_count + @mi_under_utilize_count

    -- ManagedInstanceHealthyCount
    DECLARE @mi_healthy_count INT = 0
    SELECT @mi_healthy_count = COUNT(*)
    FROM msdb.dbo.sysutility_ucp_mi_health_internal hs
    WHERE hs.set_number = @new_set_number
    AND 0 = hs.is_volume_space_under_utilized
    AND 0 = hs.is_computer_processor_under_utilized
    AND 0 = hs.is_file_space_under_utilized
    AND 0 = hs.is_mi_processor_under_utilized
    AND 0 = hs.is_volume_space_over_utilized
    AND 0 = hs.is_computer_processor_over_utilized
    AND 0 = hs.is_file_space_over_utilized
    AND 0 = hs.is_mi_processor_over_utilized

    -- Insert new record
    INSERT INTO msdb.dbo.sysutility_ucp_aggregated_mi_health_internal(set_number
           , mi_count
           , mi_healthy_count
           , mi_unhealthy_count
           , mi_over_utilize_count
           , mi_under_utilize_count
           , mi_on_over_utilized_computer_count
           , mi_on_under_utilized_computer_count
           , mi_with_files_on_over_utilized_volume_count
           , mi_with_files_on_under_utilized_volume_count
           , mi_with_over_utilized_file_count
           , mi_with_under_utilized_file_count
           , mi_with_over_utilized_processor_count
           , mi_with_under_utilized_processor_count)
    SELECT @new_set_number
            , @mi_count 
            , @mi_healthy_count 
            , @mi_unhealthy_count 
            , @mi_over_utilize_count 
            , @mi_under_utilize_count 
            , ISNULL(SUM(CASE WHEN 0 < hs.is_computer_processor_over_utilized THEN 1 ELSE 0 END), 0)
            , ISNULL(SUM(CASE WHEN 0 < hs.is_computer_processor_under_utilized THEN 1 ELSE 0 END), 0)
            , ISNULL(SUM(CASE WHEN 0 < hs.is_volume_space_over_utilized THEN 1 ELSE 0 END), 0)
            , ISNULL(SUM(CASE WHEN 0 < hs.is_volume_space_under_utilized THEN 1 ELSE 0 END), 0)
            , ISNULL(SUM(CASE WHEN 0 < hs.is_file_space_over_utilized THEN 1 ELSE 0 END), 0)
            , ISNULL(SUM(CASE WHEN 0 < hs.is_file_space_under_utilized THEN 1 ELSE 0 END), 0)
            , ISNULL(SUM(CASE WHEN 0 < hs.is_mi_processor_over_utilized THEN 1 ELSE 0 END), 0)
            , ISNULL(SUM(CASE WHEN 0 < hs.is_mi_processor_under_utilized THEN 1 ELSE 0 END), 0)
    FROM msdb.dbo.sysutility_ucp_mi_health_internal hs 
    WHERE hs.set_number = @new_set_number
END
GO        

--**********************************************************************
-- Create procedure sp_sysutility_ucp_calculate_health                 
-- Procedure description:
-- This SP computes the health state of the utility resources based on the 
-- policy evaluation results. The scheduled job runs the policies which evaluate
-- the resource for under and over utilization. The health state on the rollup object
-- are then determined by aggregating the health state of the underlying objects. 
-- Following are the resource on which health states determined 
-- 1. DAC / Server processor 
-- 2. File storage space  
-- 3. Volume storage space
-- 4. Computer processor
--**********************************************************************
IF OBJECT_ID ('dbo.sp_sysutility_ucp_calculate_health') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure dbo.sp_sysutility_ucp_calculate_health', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_ucp_calculate_health 
END;
GO

RAISERROR ('Creating procedure dbo.sp_sysutility_ucp_calculate_health', 0, 1) WITH NOWAIT;
GO

CREATE PROCEDURE dbo.sp_sysutility_ucp_calculate_health 
WITH EXECUTE AS OWNER
AS
BEGIN
    SET NOCOUNT ON;
    -- Snapshot isolation prevents the nightly purge jobs that delete much older data from blocking us. 
    SET TRANSACTION ISOLATION LEVEL SNAPSHOT; 

    DECLARE @new_set_number INT
    DECLARE @myTableVar table(next_health_state_id INT);
    DECLARE @task_start_time DATETIME;
    DECLARE @task_elapsed_ms INT;
            
    -- get the "latest" set-number. We want all the health_state tables to 
    -- reflect the same point in time, and we achieve this by using a single 
    -- set_number column in each of the tables. At any point of time, we should 
    -- be using the entries from the table which correspond to the latest_health_state_id 
    -- value in the sysutility_ucp_processing_state_internal table
    UPDATE msdb.dbo.sysutility_ucp_processing_state_internal 
      SET next_health_state_id = next_health_state_id + 1
      OUTPUT INSERTED.next_health_state_id INTO @myTableVar;

    SELECT @new_set_number = next_health_state_id 
      FROM @myTableVar;
      
    -- Fetch the violations for health polices from latest policy evaluation 
    -- and cache them in the intermediate table. All the health state queries
    -- reference this table to optimize performance       
    SET @task_start_time = GETUTCDATE();
    EXEc dbo.sp_sysutility_ucp_get_policy_violations
    SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
    RAISERROR ('sp_sysutility_ucp_get_policy_violations completed in %d ms', 0, 1, @task_elapsed_ms);
    SET @task_start_time = GETUTCDATE();
    
    -- Identify filegroups that have a policy violation. (i.e.) all files in the filegroup
    -- should have violated the same policy. Logfiles are considered to belong to a
    -- fake filegroup with name=N''
    -- We will use this information in subsequent calls
    EXEC dbo.sp_sysutility_ucp_calculate_filegroups_with_policy_violations @new_set_number
    SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
    RAISERROR ('sp_sysutility_ucp_calculate_filegroups_with_policy_violations completed in %d ms', 0, 1, @task_elapsed_ms);
    SET @task_start_time = GETUTCDATE();
    
    -- Compute computer health state
    EXEC  sp_sysutility_ucp_calculate_computer_health @new_set_number
    SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
    RAISERROR ('sp_sysutility_ucp_calculate_computer_health completed in %d ms', 0, 1, @task_elapsed_ms);
    SET @task_start_time = GETUTCDATE();

    -- Compute dac health state
    EXEC  msdb.dbo.sp_sysutility_ucp_calculate_dac_health @new_set_number
    SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
    RAISERROR ('sp_sysutility_ucp_calculate_dac_health completed in %d ms', 0, 1, @task_elapsed_ms);
    SET @task_start_time = GETUTCDATE();
    
    -- Compute dac dashboard health stats 
    EXEC msdb.dbo.sp_sysutility_ucp_calculate_aggregated_dac_health @new_set_number
    SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
    RAISERROR ('sp_sysutility_ucp_calculate_aggregated_dac_health completed in %d ms', 0, 1, @task_elapsed_ms);
    SET @task_start_time = GETUTCDATE();
    
    -- Compute managed instance health state
    EXEC msdb.dbo.sp_sysutility_ucp_calculate_mi_health @new_set_number
    SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
    RAISERROR ('sp_sysutility_ucp_calculate_mi_health completed in %d ms', 0, 1, @task_elapsed_ms);
    SET @task_start_time = GETUTCDATE();
    
    -- Compute managed instance dashboard health stats 
    EXEC msdb.dbo.sp_sysutility_ucp_calculate_aggregated_mi_health @new_set_number
    SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
    RAISERROR ('sp_sysutility_ucp_calculate_aggregated_mi_health completed in %d ms', 0, 1, @task_elapsed_ms);
    SET @task_start_time = GETUTCDATE();
    
    -- Update the config table with the new set_number
    UPDATE msdb.dbo.sysutility_ucp_processing_state_internal
      SET latest_health_state_id = @new_set_number
    
    -- Delete the old sets
    SET @task_start_time = GETUTCDATE();
    DELETE FROM msdb.dbo.sysutility_ucp_aggregated_mi_health_internal WHERE set_number < @new_set_number
    DELETE FROM msdb.dbo.sysutility_ucp_mi_health_internal WHERE set_number < @new_set_number
    DELETE FROM msdb.dbo.sysutility_ucp_aggregated_dac_health_internal WHERE set_number < @new_set_number
    DELETE FROM msdb.dbo.sysutility_ucp_dac_health_internal WHERE set_number < @new_set_number
    DELETE FROM msdb.dbo.sysutility_ucp_computer_cpu_health_internal WHERE set_number < @new_set_number
    DELETE FROM msdb.dbo.sysutility_ucp_mi_volume_space_health_internal WHERE set_number < @new_set_number
    DELETE FROM msdb.dbo.sysutility_ucp_mi_database_health_internal WHERE set_number < @new_set_number
    DELETE FROM msdb.dbo.sysutility_ucp_mi_file_space_health_internal WHERE set_number < @new_set_number
    DELETE FROM msdb.dbo.sysutility_ucp_dac_file_space_health_internal WHERE set_number < @new_set_number
    DELETE FROM msdb.dbo.sysutility_ucp_filegroups_with_policy_violations_internal WHERE set_number < @new_set_number
    SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
    RAISERROR ('Deleted older sets in %d ms', 0, 1, @task_elapsed_ms);
    
END
GO


IF OBJECT_ID ('dbo.sp_sysutility_ucp_configure_policies') IS NOT NULL
BEGIN
   RAISERROR ('Dropping procedure dbo.sp_sysutility_ucp_configure_policies', 0, 1) WITH NOWAIT;
   DROP PROCEDURE dbo.sp_sysutility_ucp_configure_policies
END;
GO

RAISERROR ('Creating procedure dbo.sp_sysutility_ucp_configure_policies', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE [dbo].[sp_sysutility_ucp_configure_policies]
WITH EXECUTE AS OWNER
AS
BEGIN
    DECLARE @condition_id INT
    DECLARE @object_set_id INT
    DECLARE @target_set_id INT
    DECLARE @policy_id INT
    DECLARE @computer_urn NVARCHAR(4000)
    DECLARE @dac_urn NVARCHAR(4000)
    DECLARE @server_urn NVARCHAR(4000)
    DECLARE @start DATETIME

    SELECT @start = getdate()

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Delete existing Policies, Conditions, objectSets and Schedule
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityComputerProcessorOverUtilizationPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityComputerProcessorOverUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityComputerProcessorOverUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityComputerProcessorOverUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityComputerProcessorOverUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityComputerProcessorOverUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityComputerProcessorOverUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityComputerProcessorOverUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityComputerProcessorOverUtilizationCondition'
    END

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityComputerProcessorOverUtilizationTargetCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityComputerProcessorOverUtilizationTargetCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityComputerProcessorOverUtilizationTargetCondition'
    END

    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityComputerProcessorUnderUtilizationPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityComputerProcessorUnderUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityComputerProcessorUnderUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityComputerProcessorUnderUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityComputerProcessorUnderUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityComputerProcessorUnderUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityComputerProcessorUnderUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityComputerProcessorUnderUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityComputerProcessorUnderUtilizationCondition'
    END   

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityComputerProcessorUnderUtilizationTargetCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityComputerProcessorUnderUtilizationTargetCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityComputerProcessorUnderUtilizationTargetCondition'
    END   

    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityDacDataFileSpaceOverUtilizationPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityDacDataFileSpaceOverUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityDacDataFileSpaceOverUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityDacDataFileSpaceOverUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityDacDataFileSpaceOverUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityDacDataFileSpaceOverUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityDacDataFileSpaceOverUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacDataFileSpaceOverUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityDacDataFileSpaceOverUtilizationCondition'
    END    

    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityDacDataFileSpaceUnderUtilizationPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityDacDataFileSpaceUnderUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityDacDataFileSpaceUnderUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityDacDataFileSpaceUnderUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityDacDataFileSpaceUnderUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityDacDataFileSpaceUnderUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityDacDataFileSpaceUnderUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacDataFileSpaceUnderUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityDacDataFileSpaceUnderUtilizationCondition'
    END   

    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityDacLogFileSpaceOverUtilizationPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityDacLogFileSpaceOverUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityDacLogFileSpaceOverUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityDacLogFileSpaceOverUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityDacLogFileSpaceOverUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityDacLogFileSpaceOverUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityDacLogFileSpaceOverUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacLogFileSpaceOverUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityDacLogFileSpaceOverUtilizationCondition'
    END    

    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityDacLogFileSpaceUnderUtilizationPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityDacLogFileSpaceUnderUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityDacLogFileSpaceUnderUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityDacLogFileSpaceUnderUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityDacLogFileSpaceUnderUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityDacLogFileSpaceUnderUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityDacLogFileSpaceUnderUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacLogFileSpaceUnderUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityDacLogFileSpaceUnderUtilizationCondition'
    END    

    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityServerDataFileSpaceOverUtilizationPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityServerDataFileSpaceOverUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityServerDataFileSpaceOverUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityServerDataFileSpaceOverUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityServerDataFileSpaceOverUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityServerDataFileSpaceOverUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityServerDataFileSpaceOverUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerDataFileSpaceOverUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityServerDataFileSpaceOverUtilizationCondition'
    END    

    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityServerDataFileSpaceUnderUtilizationPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityServerDataFileSpaceUnderUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityServerDataFileSpaceUnderUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityServerDataFileSpaceUnderUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityServerDataFileSpaceUnderUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityServerDataFileSpaceUnderUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityServerDataFileSpaceUnderUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerDataFileSpaceUnderUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityServerDataFileSpaceUnderUtilizationCondition'
    END   

    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityServerLogFileSpaceOverUtilizationPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityServerLogFileSpaceOverUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityServerLogFileSpaceOverUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityServerLogFileSpaceOverUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityServerLogFileSpaceOverUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityServerLogFileSpaceOverUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityServerLogFileSpaceOverUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerLogFileSpaceOverUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityServerLogFileSpaceOverUtilizationCondition'
    END    

    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityServerLogFileSpaceUnderUtilizationPolicy')
    BEGIN
    	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityServerLogFileSpaceUnderUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityServerLogFileSpaceUnderUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityServerLogFileSpaceUnderUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityServerLogFileSpaceUnderUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityServerLogFileSpaceUnderUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityServerLogFileSpaceUnderUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerLogFileSpaceUnderUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityServerLogFileSpaceUnderUtilizationCondition'
    END    

    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityServerProcessorOverUtilizationPolicy')
    BEGIN
    	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityServerProcessorOverUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityServerProcessorOverUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityServerProcessorOverUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityServerProcessorOverUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityServerProcessorOverUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityServerProcessorOverUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerProcessorOverUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityServerProcessorOverUtilizationCondition'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityServerProcessorOverUtilizationTargetCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerProcessorOverUtilizationTargetCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityServerProcessorOverUtilizationTargetCondition'
    END    

    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityServerProcessorUnderUtilizationPolicy')
    BEGIN
    	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityServerProcessorUnderUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityServerProcessorUnderUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityServerProcessorUnderUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityServerProcessorUnderUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityServerProcessorUnderUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityServerProcessorUnderUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerProcessorUnderUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityServerProcessorUnderUtilizationCondition'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityServerProcessorUnderUtilizationTargetCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerProcessorUnderUtilizationTargetCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityServerProcessorUnderUtilizationTargetCondition'
    END    

    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityDacProcessorOverUtilizationPolicy')
    BEGIN
    	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityDacProcessorOverUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityDacProcessorOverUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityDacProcessorOverUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityDacProcessorOverUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityDacProcessorOverUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityDacProcessorOverUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacProcessorOverUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityDacProcessorOverUtilizationCondition'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityDacProcessorOverUtilizationTargetCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacProcessorOverUtilizationTargetCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityDacProcessorOverUtilizationTargetCondition'
    END    

    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityDacProcessorUnderUtilizationPolicy')
    BEGIN
    	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityDacProcessorUnderUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityDacProcessorUnderUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityDacProcessorUnderUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityDacProcessorUnderUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityDacProcessorUnderUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityDacProcessorUnderUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacProcessorUnderUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityDacProcessorUnderUtilizationCondition'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityDacProcessorUnderUtilizationTargetCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacProcessorUnderUtilizationTargetCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityDacProcessorUnderUtilizationTargetCondition'
    END    

    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityVolumeSpaceOverUtilizationPolicy')
    BEGIN
    	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityVolumeSpaceOverUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityVolumeSpaceOverUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityVolumeSpaceOverUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityVolumeSpaceOverUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityVolumeSpaceOverUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityVolumeSpaceOverUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityVolumeSpaceOverUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityVolumeSpaceOverUtilizationCondition'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityVolumeSpaceOverUtilizationTargetCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityVolumeSpaceOverUtilizationTargetCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityVolumeSpaceOverUtilizationTargetCondition'
    END    

    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'UtilityVolumeSpaceUnderUtilizationPolicy')
    BEGIN
    	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityVolumeSpaceUnderUtilizationPolicy', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_policy @name=N'UtilityVolumeSpaceUnderUtilizationPolicy'
    END    

    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'UtilityVolumeSpaceUnderUtilizationPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityVolumeSpaceUnderUtilizationPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'UtilityVolumeSpaceUnderUtilizationPolicy_ObjectSet'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityVolumeSpaceUnderUtilizationCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityVolumeSpaceUnderUtilizationCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityVolumeSpaceUnderUtilizationCondition'
    END    

    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=N'UtilityVolumeSpaceUnderUtilizationTargetCondition')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityVolumeSpaceUnderUtilizationTargetCondition', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_condition @name=N'UtilityVolumeSpaceUnderUtilizationTargetCondition'
    END    

    IF EXISTS(SELECT COUNT(*) FROM msdb.dbo.sysutility_ucp_health_policies_internal)
    BEGIN
        DELETE FROM msdb.dbo.sysutility_ucp_health_policies_internal
    END    

    IF EXISTS(SELECT schedule_id FROM msdb.dbo.sysschedules WHERE name=N'UtilityResourceHealthStateSchedule')
    BEGIN
        EXEC msdb.dbo.sp_delete_schedule @schedule_name=N'UtilityResourceHealthStateSchedule', @force_delete=1
    END 

    IF  EXISTS (SELECT job_id FROM msdb.dbo.sysjobs_view WHERE name = N'Utility Resource Health State')
    BEGIN
        EXEC msdb.dbo.sp_delete_job @job_name=N'Utility Resource Health State', @delete_unused_schedule=1
    END

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityComputerProcessorOverUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityComputerProcessorOverUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the CPU overutilization policy is satisfied for a computer that hosts a managed instance of SQL Server. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'Computer', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>LE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>ProcessorUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>70</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id
    
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityComputerProcessorOverUtilizationCondition', @marker=1


    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityComputerProcessorOverUtilizationTargetCondition', 
    @description=N'The SQL Server Utility condition that is used to determine whether the CPU overutilization policy is violated for a computer that hosts a managed instance of SQL Server. This condition is used by the utility control point to query the computers.', 
    @facet=N'Computer', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>GT</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>ProcessorUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>70</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id
        
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityComputerProcessorOverUtilizationTargetCondition', @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityComputerProcessorUnderUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityComputerProcessorUnderUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the CPU underutilization policy is satisfied for a computer that hosts a managed instance of SQL Server. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'Computer', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>GE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>ProcessorUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>0</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityComputerProcessorUnderUtilizationCondition', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityComputerProcessorUnderUtilizationTargetCondition', 
    @description=N'The SQL Server Utility condition that is used to determine whether the CPU underutilization policy is violated for a computer that hosts a managed instance of SQL Server. This condition is used by the utility control point to query the computers.', 
    @facet=N'Computer', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>LT</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>ProcessorUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>0</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityComputerProcessorUnderUtilizationTargetCondition', @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityDacDataFileSpaceOverUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityDacDataFileSpaceOverUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the data file space overutilization policy is satisfied for a deployed data-tier application. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'IDataFilePerformanceFacet', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>LE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>SpaceUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>70</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacDataFileSpaceOverUtilizationCondition', @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityDacDataFileSpaceUnderUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityDacDataFileSpaceUnderUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the data file space underutilization policy is satisfied for a deployed data-tier application. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'IDataFilePerformanceFacet', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>GE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>SpaceUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>0</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacDataFileSpaceUnderUtilizationCondition', @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityDacLogFileSpaceOverUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityDacLogFileSpaceOverUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the log file space overutilization policy is satisfied for a deployed data-tier application. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'ILogFilePerformanceFacet', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>LE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>SpaceUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>70</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacLogFileSpaceOverUtilizationCondition', @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityDacLogFileSpaceUnderUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityDacLogFileSpaceUnderUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the log file space underutilization policy is satisfied for a deployed data-tier application. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'ILogFilePerformanceFacet', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>GE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>SpaceUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>0</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacLogFileSpaceUnderUtilizationCondition', @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityServerDataFileSpaceOverUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityServerDataFileSpaceOverUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the data file space overutilization policy is satisfied for a managed instance of SQL Server. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'IDataFilePerformanceFacet', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>LE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>SpaceUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>70</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerDataFileSpaceOverUtilizationCondition', @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityServerDataFileSpaceUnderUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityServerDataFileSpaceUnderUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the data file space underutilization policy is satisfied for a managed instance of SQL Server. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'IDataFilePerformanceFacet', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>GE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>SpaceUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>0</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerDataFileSpaceUnderUtilizationCondition', @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityServerLogFileSpaceOverUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityServerLogFileSpaceOverUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the log file space overutilization policy is satisfied for a managed instance of SQL Server. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'ILogFilePerformanceFacet', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>LE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>SpaceUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>70</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerLogFileSpaceOverUtilizationCondition', @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityServerLogFileSpaceUnderUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityServerLogFileSpaceUnderUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the log file space underutilization policy is satisfied for a managed instance of SQL Server. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'ILogFilePerformanceFacet', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>GE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>SpaceUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>0</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerLogFileSpaceUnderUtilizationCondition', @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityServerProcessorOverUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityServerProcessorOverUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the CPU overutilization policy is satisfied for a managed instance of SQL Server. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'Server', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>LE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>ProcessorUsage</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>70</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerProcessorOverUtilizationCondition', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityServerProcessorOverUtilizationTargetCondition', 
    @description=N'The SQL Server Utility condition that is used to determine whether the CPU overutilization policy is violated for a managed instance of SQL Server. This condition is used by the utility control point to query the managed instances of SQL Server.', 
    @facet=N'Server', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>GT</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>ProcessorUsage</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>70</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerProcessorOverUtilizationTargetCondition', @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityServerProcessorUnderUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityServerProcessorUnderUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the CPU underutilization policy is satisfied for a managed instance of SQL Server. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'Server', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>GE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>ProcessorUsage</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>0</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerProcessorUnderUtilizationCondition', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityServerProcessorUnderUtilizationTargetCondition', 
    @description=N'The SQL Server Utility condition that is used to determine whether the CPU underutilization policy is violated for a managed instance of SQL Server. This condition is used by the utility control point to query the managed instances of SQL Server.', 
    @facet=N'Server', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>LT</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>ProcessorUsage</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>0</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityServerProcessorUnderUtilizationTargetCondition', @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityDacProcessorOverUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityDacProcessorOverUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the CPU overutilization policy is satisfied for a deployed data-tier application. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'DeployedDac', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>LE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>ProcessorUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>70</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacProcessorOverUtilizationCondition', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityDacProcessorOverUtilizationTargetCondition', 
    @description=N'The SQL Server Utility condition that is used to determine whether the CPU overutilization policy is violated for a deployed data-tier application. This condition is used by the utility control point to query the deployed data-tier applications. ', 
    @facet=N'DeployedDac', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>GT</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>ProcessorUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>70</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacProcessorOverUtilizationTargetCondition', @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityDacProcessorUnderUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityDacProcessorUnderUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the CPU underutilization policy is satisfied for a deployed data-tier application. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'DeployedDac', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>GE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>ProcessorUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>0</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacProcessorUnderUtilizationCondition', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityDacProcessorUnderUtilizationTargetCondition', 
    @description=N'The SQL Server Utility condition that is used to determine whether the CPU underutilization policy is violated for a deployed data-tier application. This condition is used by the utility control point to query the deployed data-tier applications. ', 
    @facet=N'DeployedDac', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>LT</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>ProcessorUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>0</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityDacProcessorUnderUtilizationTargetCondition', @marker=1

    ------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityVolumeSpaceOverUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityVolumeSpaceOverUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the volume space overutilization policy is satisfied for a computer that hosts a managed instance of SQL Server. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'Volume', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>LE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>TotalSpaceUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>70</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityVolumeSpaceOverUtilizationCondition', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityVolumeSpaceOverUtilizationTargetCondition', 
    @description=N'The SQL Server Utility condition that is used to determine whether the volume space overutilization policy is violated for a computer that hosts a managed instance of SQL Server. This condition is used by the utility control point to query the volumes.', 
    @facet=N'Volume', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>GT</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>TotalSpaceUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>70</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityVolumeSpaceOverUtilizationTargetCondition', @marker=1

    ------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityVolumeSpaceUnderUtilizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityVolumeSpaceUnderUtilizationCondition', 
    @description=N'The SQL Server Utility condition that expresses when the volume space underutilization policy is satisfied for a computer that hosts a managed instance of SQL Server. The value that is used in the condition expression is set in SQL Server Utility Explorer.', 
    @facet=N'Volume', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>GE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>TotalSpaceUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>0</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityVolumeSpaceUnderUtilizationCondition', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_condition @name=N'UtilityVolumeSpaceUnderUtilizationTargetCondition', 
    @description=N'The SQL Server Utility condition that is used to determine whether the volume space underutilization policy is violated for a computer that hosts a managed instance of SQL Server. This condition is used by the utility control point to query the volumes. ', 
    @facet=N'Volume', @expression=N'<Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>LT</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>TotalSpaceUtilization</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>0</Value>
      </Constant>
    </Operator>', @is_name_condition=0, @obj_name=N'', @condition_id=@condition_id OUTPUT
    Select @condition_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=N'UtilityVolumeSpaceUnderUtilizationTargetCondition', @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityComputerProcessorOverUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityComputerProcessorOverUtilizationPolicy_ObjectSet', @facet=N'Computer', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityComputerProcessorOverUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityComputerProcessorOverUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/Computer', @type=N'COMPUTER', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Computer', @level_name=N'Computer', @condition_name=N'UtilityComputerProcessorOverUtilizationTargetCondition', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityComputerProcessorOverUtilizationPolicy', @condition_name=N'UtilityComputerProcessorOverUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for CPU overutilization on a computer that hosts a managed instance of SQL Server.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityComputerProcessorOverUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityComputerProcessorOverUtilizationPolicy', @marker=1

    SELECT @computer_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/Computer'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityComputerProcessorOverUtilizationPolicy',@rollup_object_type=3,@rollup_object_urn=@computer_urn,@target_type=1,@resource_type=3,@utilization_type=2,@utilization_threshold=70

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityComputerProcessorUnderUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityComputerProcessorUnderUtilizationPolicy_ObjectSet', @facet=N'Computer', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityComputerProcessorUnderUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityComputerProcessorUnderUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/Computer', @type=N'COMPUTER', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Computer', @level_name=N'Computer', @condition_name=N'UtilityComputerProcessorUnderUtilizationTargetCondition', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityComputerProcessorUnderUtilizationPolicy', @condition_name=N'UtilityComputerProcessorUnderUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for CPU underutilization on a computer that hosts a managed instance of SQL Server.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityComputerProcessorUnderUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityComputerProcessorUnderUtilizationPolicy', @marker=1

    SELECT @computer_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/Computer'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityComputerProcessorUnderUtilizationPolicy',@rollup_object_type=3,@rollup_object_urn=@computer_urn,@target_type=1,@resource_type=3,@utilization_type=1,@utilization_threshold=0

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityDacDataFileSpaceOverUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityDacDataFileSpaceOverUtilizationPolicy_ObjectSet', @facet=N'IDataFilePerformanceFacet', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityDacDataFileSpaceOverUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityDacDataFileSpaceOverUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/Server/Database/FileGroup/File', @type=N'FILE', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database/FileGroup/File', @level_name=N'File', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database/FileGroup', @level_name=N'FileGroup', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database', @level_name=N'Database', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server', @level_name=N'Server', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityDacDataFileSpaceOverUtilizationPolicy', @condition_name=N'UtilityDacDataFileSpaceOverUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for data file space overutilization for a deployed data-tier application.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityDacDataFileSpaceOverUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityDacDataFileSpaceOverUtilizationPolicy', @marker=1

    SELECT @dac_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/DeployedDac'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityDacDataFileSpaceOverUtilizationPolicy',@rollup_object_type=1,@rollup_object_urn=@dac_urn,@target_type=2,@resource_type=1,@utilization_type=2,@utilization_threshold=70

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityDacDataFileSpaceUnderUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityDacDataFileSpaceUnderUtilizationPolicy_ObjectSet', @facet=N'IDataFilePerformanceFacet', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityDacDataFileSpaceUnderUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityDacDataFileSpaceUnderUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/Server/Database/FileGroup/File', @type=N'FILE', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database/FileGroup/File', @level_name=N'File', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database/FileGroup', @level_name=N'FileGroup', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database', @level_name=N'Database', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server', @level_name=N'Server', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityDacDataFileSpaceUnderUtilizationPolicy', @condition_name=N'UtilityDacDataFileSpaceUnderUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for data file space underutilization for a deployed data-tier application.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityDacDataFileSpaceUnderUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityDacDataFileSpaceUnderUtilizationPolicy', @marker=1

    SELECT @dac_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/DeployedDac'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityDacDataFileSpaceUnderUtilizationPolicy',@rollup_object_type=1,@rollup_object_urn=@dac_urn,@target_type=2,@resource_type=1,@utilization_type=1,@utilization_threshold=0

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityDacLogFileSpaceOverUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityDacLogFileSpaceOverUtilizationPolicy_ObjectSet', @facet=N'ILogFilePerformanceFacet', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityDacLogFileSpaceOverUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityDacLogFileSpaceOverUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/Server/Database/LogFile', @type=N'LOGFILE', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database/LogFile', @level_name=N'LogFile', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database', @level_name=N'Database', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server', @level_name=N'Server', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityDacLogFileSpaceOverUtilizationPolicy', @condition_name=N'UtilityDacLogFileSpaceOverUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for log file space overutilization for a deployed data-tier application.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityDacLogFileSpaceOverUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityDacLogFileSpaceOverUtilizationPolicy', @marker=1

    SELECT @dac_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/DeployedDac'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityDacLogFileSpaceOverUtilizationPolicy',@rollup_object_type=1,@rollup_object_urn=@dac_urn,@target_type=3,@resource_type=1,@utilization_type=2,@utilization_threshold=70

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityDacLogFileSpaceUnderUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityDacLogFileSpaceUnderUtilizationPolicy_ObjectSet', @facet=N'ILogFilePerformanceFacet', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityDacLogFileSpaceUnderUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityDacLogFileSpaceUnderUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/Server/Database/LogFile', @type=N'LOGFILE', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database/LogFile', @level_name=N'LogFile', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database', @level_name=N'Database', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server', @level_name=N'Server', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityDacLogFileSpaceUnderUtilizationPolicy', @condition_name=N'UtilityDacLogFileSpaceUnderUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for log file space underutilization for a deployed data-tier application.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityDacLogFileSpaceUnderUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityDacLogFileSpaceUnderUtilizationPolicy', @marker=1

    SELECT @dac_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/DeployedDac'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityDacLogFileSpaceUnderUtilizationPolicy',@rollup_object_type=1,@rollup_object_urn=@dac_urn,@target_type=3,@resource_type=1,@utilization_type=1,@utilization_threshold=0

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityServerDataFileSpaceOverUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityServerDataFileSpaceOverUtilizationPolicy_ObjectSet', @facet=N'IDataFilePerformanceFacet', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityServerDataFileSpaceOverUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityServerDataFileSpaceOverUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/Server/Database/FileGroup/File', @type=N'FILE', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database/FileGroup/File', @level_name=N'File', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database/FileGroup', @level_name=N'FileGroup', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database', @level_name=N'Database', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server', @level_name=N'Server', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityServerDataFileSpaceOverUtilizationPolicy', @condition_name=N'UtilityServerDataFileSpaceOverUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for data file space overutilization on a managed instance of SQL Server.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityServerDataFileSpaceOverUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityServerDataFileSpaceOverUtilizationPolicy', @marker=1

    SELECT @server_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/Server'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityServerDataFileSpaceOverUtilizationPolicy',@rollup_object_type=2,@rollup_object_urn=@server_urn,@target_type=2,@resource_type=1,@utilization_type=2,@utilization_threshold=70

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityServerDataFileSpaceUnderUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityServerDataFileSpaceUnderUtilizationPolicy_ObjectSet', @facet=N'IDataFilePerformanceFacet', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityServerDataFileSpaceUnderUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityServerDataFileSpaceUnderUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/Server/Database/FileGroup/File', @type=N'FILE', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database/FileGroup/File', @level_name=N'File', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database/FileGroup', @level_name=N'FileGroup', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database', @level_name=N'Database', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server', @level_name=N'Server', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityServerDataFileSpaceUnderUtilizationPolicy', @condition_name=N'UtilityServerDataFileSpaceUnderUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for data file space underutilization on a managed instance of SQL Server.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityServerDataFileSpaceUnderUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityServerDataFileSpaceUnderUtilizationPolicy', @marker=1

    SELECT @server_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/Server'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityServerDataFileSpaceUnderUtilizationPolicy',@rollup_object_type=2,@rollup_object_urn=@server_urn,@target_type=2,@resource_type=1,@utilization_type=1,@utilization_threshold=0

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityServerLogFileSpaceOverUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityServerLogFileSpaceOverUtilizationPolicy_ObjectSet', @facet=N'ILogFilePerformanceFacet', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityServerLogFileSpaceOverUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityServerLogFileSpaceOverUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/Server/Database/LogFile', @type=N'LOGFILE', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database/LogFile', @level_name=N'LogFile', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database', @level_name=N'Database', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server', @level_name=N'Server', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityServerLogFileSpaceOverUtilizationPolicy', @condition_name=N'UtilityServerLogFileSpaceOverUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for log file space overutilization on a managed instance of SQL Server.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityServerLogFileSpaceOverUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityServerLogFileSpaceOverUtilizationPolicy', @marker=1

    SELECT @server_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/Server'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityServerLogFileSpaceOverUtilizationPolicy',@rollup_object_type=2,@rollup_object_urn=@server_urn,@target_type=3,@resource_type=1,@utilization_type=2,@utilization_threshold=70

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityServerLogFileSpaceUnderUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityServerLogFileSpaceUnderUtilizationPolicy_ObjectSet', @facet=N'ILogFilePerformanceFacet', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityServerLogFileSpaceUnderUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityServerLogFileSpaceUnderUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/Server/Database/LogFile', @type=N'LOGFILE', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database/LogFile', @level_name=N'LogFile', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server/Database', @level_name=N'Database', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server', @level_name=N'Server', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityServerLogFileSpaceUnderUtilizationPolicy', @condition_name=N'UtilityServerLogFileSpaceUnderUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for log file space underutilization on a managed instance of SQL Server.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityServerLogFileSpaceUnderUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityServerLogFileSpaceUnderUtilizationPolicy', @marker=1

    SELECT @server_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/Server'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityServerLogFileSpaceUnderUtilizationPolicy',@rollup_object_type=2,@rollup_object_urn=@server_urn,@target_type=3,@resource_type=1,@utilization_type=1,@utilization_threshold=0

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityServerProcessorOverUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityServerProcessorOverUtilizationPolicy_ObjectSet', @facet=N'Server', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityServerProcessorOverUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityServerProcessorOverUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/Server', @type=N'SERVER', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server', @level_name=N'Server', @condition_name=N'UtilityServerProcessorOverUtilizationTargetCondition', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityServerProcessorOverUtilizationPolicy', @condition_name=N'UtilityServerProcessorOverUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for CPU overutilization on a managed instance of SQL Server.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityServerProcessorOverUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityServerProcessorOverUtilizationPolicy', @marker=1

    SELECT @server_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/Server'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityServerProcessorOverUtilizationPolicy',@rollup_object_type=2,@rollup_object_urn=@server_urn,@target_type=4,@resource_type=3,@utilization_type=2,@utilization_threshold=70

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityServerProcessorUnderUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityServerProcessorUnderUtilizationPolicy_ObjectSet', @facet=N'Server', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityServerProcessorUnderUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityServerProcessorUnderUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/Server', @type=N'SERVER', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Server', @level_name=N'Server', @condition_name=N'UtilityServerProcessorUnderUtilizationTargetCondition', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityServerProcessorUnderUtilizationPolicy', @condition_name=N'UtilityServerProcessorUnderUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for CPU underutilization on a managed instance of SQL Server.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityServerProcessorUnderUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityServerProcessorUnderUtilizationPolicy', @marker=1

    SELECT @server_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/Server'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityServerProcessorUnderUtilizationPolicy',@rollup_object_type=2,@rollup_object_urn=@server_urn,@target_type=4,@resource_type=3,@utilization_type=1,@utilization_threshold=0

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityDacProcessorOverUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityDacProcessorOverUtilizationPolicy_ObjectSet', @facet=N'DeployedDac', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityDacProcessorOverUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityDacProcessorOverUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/DeployedDac', @type=N'DEPLOYEDDAC', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/DeployedDac', @level_name=N'DeployedDac', @condition_name=N'UtilityDacProcessorOverUtilizationTargetCondition', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityDacProcessorOverUtilizationPolicy', @condition_name=N'UtilityDacProcessorOverUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for CPU overutilization for a deployed data-tier application.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityDacProcessorOverUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityDacProcessorOverUtilizationPolicy', @marker=1

    SELECT @dac_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/DeployedDac'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityDacProcessorOverUtilizationPolicy',@rollup_object_type=1,@rollup_object_urn=@dac_urn,@target_type=5,@resource_type=3,@utilization_type=2,@utilization_threshold=70

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityDacProcessorUnderUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityDacProcessorUnderUtilizationPolicy_ObjectSet', @facet=N'DeployedDac', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityDacProcessorUnderUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityDacProcessorUnderUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/DeployedDac', @type=N'DEPLOYEDDAC', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/DeployedDac', @level_name=N'DeployedDac', @condition_name=N'UtilityDacProcessorUnderUtilizationTargetCondition', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityDacProcessorUnderUtilizationPolicy', @condition_name=N'UtilityDacProcessorUnderUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for CPU underutilization for a deployed data-tier application.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityDacProcessorUnderUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityDacProcessorUnderUtilizationPolicy', @marker=1

    SELECT @dac_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/DeployedDac'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityDacProcessorUnderUtilizationPolicy',@rollup_object_type=1,@rollup_object_urn=@dac_urn,@target_type=5,@resource_type=3,@utilization_type=1,@utilization_threshold=0

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityVolumeSpaceOverUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityVolumeSpaceOverUtilizationPolicy_ObjectSet', @facet=N'Volume', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityVolumeSpaceOverUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityVolumeSpaceOverUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/Computer/Volume', @type=N'VOLUME', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Computer/Volume', @level_name=N'Volume', @condition_name=N'UtilityVolumeSpaceOverUtilizationTargetCondition', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Computer', @level_name=N'Computer', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityVolumeSpaceOverUtilizationPolicy', @condition_name=N'UtilityVolumeSpaceOverUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for volume space overutilization on a computer that hosts a managed instance of SQL Server.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityVolumeSpaceOverUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityVolumeSpaceOverUtilizationPolicy', @marker=1

    SELECT @computer_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/Computer'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityVolumeSpaceOverUtilizationPolicy',@rollup_object_type=3,@rollup_object_urn=@computer_urn,@target_type=6,@resource_type=1,@utilization_type=2,@utilization_threshold=70

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- UtilityVolumeSpaceUnderUtilizationPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'UtilityVolumeSpaceUnderUtilizationPolicy_ObjectSet', @facet=N'Volume', @object_set_id=@object_set_id OUTPUT
    Select @object_set_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'UtilityVolumeSpaceUnderUtilizationPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'UtilityVolumeSpaceUnderUtilizationPolicy_ObjectSet', @type_skeleton=N'Utility/Computer/Volume', @type=N'VOLUME', @enabled=True, @target_set_id=@target_set_id OUTPUT
    Select @target_set_id

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Computer/Volume', @level_name=N'Volume', @condition_name=N'UtilityVolumeSpaceUnderUtilizationTargetCondition', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@target_set_id, @type_skeleton=N'Utility/Computer', @level_name=N'Computer', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'UtilityVolumeSpaceUnderUtilizationPolicy', @condition_name=N'UtilityVolumeSpaceUnderUtilizationCondition', @policy_category=N'', 
    @description=N'The SQL Server Utility policy that checks for volume space underutilization on a computer that hosts a managed instance of SQL Server.', 
    @help_text=N'', @help_link=N'', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=@policy_id OUTPUT, @root_condition_name=N'', @object_set=N'UtilityVolumeSpaceUnderUtilizationPolicy_ObjectSet'
    Select @policy_id

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'UtilityVolumeSpaceUnderUtilizationPolicy', @marker=1

    SELECT @computer_urn = 'Utility[@Name='''+CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName'))+''']/Computer'
    EXEC msdb.dbo.sp_sysutility_ucp_add_policy @policy_name=N'UtilityVolumeSpaceUnderUtilizationPolicy',@rollup_object_type=3,@rollup_object_urn=@computer_urn,@target_type=6,@resource_type=1,@utilization_type=1,@utilization_threshold=0

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Update sysutility_ucp_health_policies_internal entries to system policies 
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    IF EXISTS(SELECT COUNT(*) FROM msdb.dbo.sysutility_ucp_health_policies_internal)
    BEGIN
        UPDATE msdb.dbo.sysutility_ucp_health_policies_internal SET is_global_policy = 1
    END    
    
    -- Compute default health states during UCP creation (boot strap scenario)
    EXEC msdb.dbo.sp_sysutility_ucp_calculate_health
    
    --**********************************************************************
    -- Mark all newly created sp_sysutility* artifacts as system.
    -- (code extracted and modified from "Mark system objects" in instdb.sql.)
    --**********************************************************************
    BEGIN
        declare @name sysname;
        declare newsysobjs cursor for select name from sys.objects where schema_id = 1 and name LIKE '%sysutility%' and create_date >= @start 
        open newsysobjs
        fetch next from newsysobjs into @name
        while @@fetch_status = 0
        begin
            Exec sp_MS_marksystemobject @name
            fetch next from newsysobjs into @name
        end
        deallocate newsysobjs
    END

END
GO

/*******************************************************************
 Procedure to remove utility control point (UCP) from target SQL server instance 

 Following are the pre-requsite validations done by the procedure
 
 a. The user executing the script has sysadmin role on target instance
 b. The target instance must be a utility control point
 c. There are no active enrolled managed instances in the UCP

 The procedure handles cleanup of following UCP artifacts
 
 1. Drops the MDW database if there are no system collection sets enabled; 
    else truncates the utility live, dimension and measure tables.
 2. Update UCP configuration tables with default values
 3. Drop all the resource health policies
 4. Truncate utility health state tables in MSDB database
 5. Remove the utility aggregation jobs 
 6. Remove the utility related registry keys 
*******************************************************************/

IF OBJECT_ID(N'[dbo].[sp_sysutility_ucp_remove]') IS NOT NULL
BEGIN
   RAISERROR('Dropping [dbo].[sp_sysutility_ucp_remove] procedure', 0, 1)  WITH NOWAIT;
   DROP PROCEDURE [dbo].[sp_sysutility_ucp_remove];
END
GO

RAISERROR('Creating [dbo].[sp_sysutility_ucp_remove] procedure', 0, 1)  WITH NOWAIT;
GO

CREATE PROCEDURE [dbo].[sp_sysutility_ucp_remove]
WITH EXECUTE AS CALLER
AS
BEGIN
    SET NOCOUNT ON;
    
    ---------------------------------------------------------------------
    -- Validation Steps
    ---------------------------------------------------------------------

    -- Validate the user running the script is sysadmin on the UCP instance
    IF (1 != IS_SRVROLEMEMBER(N'sysadmin ', SUSER_NAME()))  
    BEGIN
        RAISERROR(37008, -1, -1) 
        RETURN(1)
    END   

    -- Validate the instance is UCP
    IF (0 = (SELECT msdb.dbo.fn_sysutility_get_is_instance_ucp()))
    BEGIN
        RAISERROR(37009, -1, -1) 
        RETURN(1)
    END        
 
    -- Validate all managed instances are un-enrolled
    IF (0 < (SELECT COUNT(*) FROM [dbo].[sysutility_ucp_managed_instances]))  
    BEGIN
        RAISERROR(37010, -1, -1) 
        RETURN(1)
    END  
 

    ---------------------------------------------------------------------
    -- Remove UCP artifacts
    ---------------------------------------------------------------------

    IF  EXISTS (SELECT name FROM [master].[sys].[databases] WHERE name = N'sysutility_mdw')
    BEGIN

        -- Check whether there are other non-utility (DC system / custom) collection sets targeted to sysutility_mdw database
        IF (0 = (SELECT COUNT(*) FROM [sysutility_mdw].[core].[source_info_internal] WHERE collection_set_uid != N'ABA37A22-8039-48C6-8F8F-39BFE0A195DF'))  
        BEGIN

            -- Drop utility MDW database as there are no non-utility collection sets uploading data to this DB
            ALTER DATABASE [sysutility_mdw] SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
            DROP DATABASE [sysutility_mdw];
            
            -- Delete MDW purge jobs 
            IF  EXISTS (SELECT job_id FROM [dbo].[sysjobs_view] WHERE name = N'mdw_purge_data_[sysutility_mdw]')
            BEGIN
                EXEC [dbo].sp_delete_job @job_name=N'mdw_purge_data_[sysutility_mdw]', @delete_unused_schedule=1
            END
            
        END  
        ELSE
        BEGIN
        
            -- There are non-utility collection sets uploading data to mdw
            -- so do not drop the MDW database; instead truncate utility tables to purge data
            DECLARE @schema_name SYSNAME
            DECLARE @table_name SYSNAME
            DECLARE @expression NVARCHAR(MAX)
            
            -- Truncate the dimension, measure and live tables in MDW database
            DECLARE tables_cursor CURSOR FOR    
            SELECT object_schema, object_name 
            FROM [sysutility_mdw].[sysutility_ucp_misc].[utility_objects_internal]
            WHERE sql_object_type = N'USER_TABLE'
              AND utility_object_type IN (N'DIMENSION', N'MEASURE', N'LIVE')
                
            OPEN tables_cursor;
            FETCH NEXT FROM tables_cursor INTO @schema_name, @table_name
            WHILE (@@FETCH_STATUS <> -1)
            BEGIN

                SET @expression = 'TRUNCATE TABLE [sysutility_mdw].' + QUOTENAME(@schema_name) + '.' + QUOTENAME(@table_name);
                EXEC sp_executesql @expression;

                FETCH NEXT FROM tables_cursor INTO @schema_name, @table_name
            END;
            CLOSE tables_cursor;
            DEALLOCATE tables_cursor;

        END
    END     

    --###FP 1

    ---------------------------------------------------------------------
    -- Truncate the utility tables in msdb database
    -- Note: Do not truncate tables in which data is pre-shipped
    ---------------------------------------------------------------------
    TRUNCATE TABLE [dbo].[sysutility_ucp_mi_health_internal];
    TRUNCATE TABLE [dbo].[sysutility_ucp_aggregated_mi_health_internal];
    TRUNCATE TABLE [dbo].[sysutility_ucp_mi_database_health_internal];
    TRUNCATE TABLE [dbo].[sysutility_ucp_mi_volume_space_health_internal];
    TRUNCATE TABLE [dbo].[sysutility_ucp_mi_file_space_health_internal];
    TRUNCATE TABLE [dbo].[sysutility_ucp_dac_health_internal];
    TRUNCATE TABLE [dbo].[sysutility_ucp_aggregated_dac_health_internal];
    TRUNCATE TABLE [dbo].[sysutility_ucp_dac_file_space_health_internal];
    TRUNCATE TABLE [dbo].[sysutility_ucp_computer_cpu_health_internal];
    TRUNCATE TABLE [dbo].[sysutility_ucp_filegroups_with_policy_violations_internal];
    TRUNCATE TABLE [dbo].[sysutility_ucp_policy_violations_internal];
    TRUNCATE TABLE [dbo].[sysutility_ucp_snapshot_partitions_internal];

    --###FP 2

    ---------------------------------------------------------------------
    -- Delete utility aggregation jobs 
    ---------------------------------------------------------------------
    IF  EXISTS (SELECT job_id FROM [dbo].[sysjobs_view] WHERE name = N'sysutility_get_views_data_into_cache_tables')
    BEGIN
        EXEC [dbo].sp_delete_job @job_name=N'sysutility_get_views_data_into_cache_tables', @delete_unused_schedule=1
    END       

    IF  EXISTS (SELECT job_id FROM [dbo].[sysjobs_view] WHERE name = N'sysutility_get_cache_tables_data_into_aggregate_tables_hourly')
    BEGIN
        EXEC [dbo].sp_delete_job @job_name=N'sysutility_get_cache_tables_data_into_aggregate_tables_hourly', @delete_unused_schedule=1
    END        

    IF  EXISTS (SELECT job_id FROM [dbo].[sysjobs_view] WHERE name = N'sysutility_get_cache_tables_data_into_aggregate_tables_daily')
    BEGIN
        EXEC [dbo].sp_delete_job @job_name=N'sysutility_get_cache_tables_data_into_aggregate_tables_daily', @delete_unused_schedule=1
    END        

    --###FP 3

    ---------------------------------------------------------------------
    -- Drop resource health policies, conditions and objectSets 
    ---------------------------------------------------------------------
    DECLARE @policy_name SYSNAME
    DECLARE @health_policy_id INT
    DECLARE @policy_id INT
    DECLARE @object_set_id INT
    DECLARE @condition_id INT
    DECLARE @target_condition_id INT
    
    DECLARE policies_cursor CURSOR FOR
    SELECT policy_name, health_policy_id
    FROM [dbo].[sysutility_ucp_policies]

    OPEN policies_cursor;
    FETCH NEXT FROM policies_cursor INTO @policy_name, @health_policy_id
    WHILE (@@FETCH_STATUS <> -1)
    BEGIN
        SELECT @policy_id = policy_id
            , @object_set_id = object_set_id
            , @condition_id = condition_id
        FROM [dbo].[syspolicy_policies]
        WHERE name = @policy_name
        
        -- Delete the policy 
        EXEC [dbo].sp_syspolicy_mark_system @type=N'POLICY', @object_id=@policy_id, @marker=0
        EXEC [dbo].sp_syspolicy_delete_policy @policy_id=@policy_id

        -- Get the target set condtions before deleting the object set  
        CREATE TABLE #target_conditions(condition_id INT);
        
        INSERT INTO #target_conditions 
        SELECT condition_id
        FROM [dbo].[syspolicy_target_sets] ts
            , [dbo].[syspolicy_target_set_levels] tsl
        WHERE ts.target_set_id = tsl.target_set_id
            AND ts.object_set_id = @object_set_id   
            
        -- Delete the object set
        EXEC [dbo].sp_syspolicy_mark_system @type=N'OBJECTSET', @object_id=@object_set_id, @marker=0
        EXEC [dbo].sp_syspolicy_delete_object_set @object_set_id=@object_set_id
        
        DECLARE target_conditions_cursor CURSOR FOR
        SELECT condition_id
        FROM #target_conditions
        
        OPEN target_conditions_cursor;
        FETCH NEXT FROM target_conditions_cursor INTO @target_condition_id
        WHILE (@@FETCH_STATUS <> -1)
        BEGIN
        
            IF (@target_condition_id IS NOT NULL)
            BEGIN
                --- Delete the target set condition
                EXEC [dbo].sp_syspolicy_mark_system @type=N'CONDITION', @object_id=@target_condition_id, @marker=0
                EXEC [dbo].sp_syspolicy_delete_condition @condition_id=@target_condition_id
            END
            FETCH NEXT FROM target_conditions_cursor INTO @target_condition_id
            
        END;
        CLOSE target_conditions_cursor;
        DEALLOCATE target_conditions_cursor;
        DROP TABLE #target_conditions            
                            
        --- Delete the check condition
        EXEC [dbo].sp_syspolicy_mark_system @type=N'CONDITION', @object_id=@condition_id, @marker=0
        EXEC [dbo].sp_syspolicy_delete_condition @condition_id=@condition_id

        -- Delete the resource health policy
        DELETE [dbo].[sysutility_ucp_health_policies_internal]
        WHERE health_policy_id = @health_policy_id
            
        FETCH NEXT FROM policies_cursor INTO @policy_name, @health_policy_id
        
    END;
    CLOSE policies_cursor;
    DEALLOCATE policies_cursor;

    --###FP 4
        
    ---------------------------------------------------------------------
    -- Remove the utility related registry keys from the system
    ---------------------------------------------------------------------
    -- Remove the UtilityVersion registry key value 
    DECLARE @utility_version nvarchar(1024)
    EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                        N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Utility',
                                        N'UtilityVersion',
                                        @utility_version OUTPUT

    IF (@utility_version IS NOT NULL) 
    BEGIN
        EXEC master.dbo.xp_instance_regdeletevalue N'HKEY_LOCAL_MACHINE',
                                                   N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Utility',
                                                   N'UtilityVersion'
    END

    -- Remove the UcpName registry key value 
    DECLARE @utility_name nvarchar(1024)
    EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                        N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Utility',
                                        N'UcpName',
                                        @utility_name OUTPUT

    IF (@utility_name IS NOT NULL) 
    BEGIN
        EXEC master.dbo.xp_instance_regdeletevalue N'HKEY_LOCAL_MACHINE',
                                                   N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Utility',
                                                   N'UcpName'
    END
   
    -- Remove the UcpFriendlyName registry key value 
    DECLARE @utility_friendly_name nvarchar(1024)
    EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                        N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Utility',
                                        N'UcpFriendlyName',
                                        @utility_friendly_name OUTPUT

    IF (@utility_friendly_name IS NOT NULL) 
    BEGIN
        EXEC master.dbo.xp_instance_regdeletevalue N'HKEY_LOCAL_MACHINE',
                                                   N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Utility',
                                                   N'UcpFriendlyName'
    END

    -- Remove the Utility registry key  
    EXEC master.dbo.xp_instance_regdeletekey N'HKEY_LOCAL_MACHINE',
                                             N'SOFTWARE\Microsoft\MSSQLServer\MSSQLServer\Utility'    

    --###FP 5
            
    ---------------------------------------------------------------------
    -- Reset the processing state table to default values
    ---------------------------------------------------------------------    
    UPDATE [dbo].[sysutility_ucp_processing_state_internal]
    SET latest_processing_time = SYSDATETIMEOFFSET(), latest_health_state_id = 0, next_health_state_id = 1    

    --###FP 6

    ---------------------------------------------------------------------
    -- Update utility configuration table entries to default values
    -- Note: Keep this cleanup as the last one as the script uses this 
    -- to check if the target instance is a UCP in the validation
    ---------------------------------------------------------------------
    UPDATE [dbo].[sysutility_ucp_configuration_internal] SET current_value = N'' WHERE name like N'Utility%'
    UPDATE [dbo].[sysutility_ucp_configuration_internal] SET current_value = N'' WHERE name = N'MdwDatabaseName'
END
GO

/**********************************************************************/
/* Delete Managed Instance.                                           */
/*                                                                    */
/*                                                                    */
/**********************************************************************/
IF OBJECT_ID ('dbo.sp_sysutility_ucp_remove_mi') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_ucp_remove_mi]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE dbo.sp_sysutility_ucp_remove_mi 
END;
GO


RAISERROR ('Creating procedure [dbo].[sp_sysutility_ucp_remove_mi]', 0, 1) WITH NOWAIT;
GO

CREATE PROCEDURE [dbo].[sp_sysutility_ucp_remove_mi]
@instance_id int
WITH EXECUTE AS OWNER
AS
BEGIN
   DECLARE @retval              INT


    IF (@instance_id IS NULL)
    BEGIN
        RAISERROR(14043, -1, -1, 'instance_id', 'sp_sysutility_ucp_remove_mi')
        RETURN(1)
    END

    DECLARE @instance_name SYSNAME
    SELECT @instance_name = instance_name 
    FROM msdb.dbo.sysutility_ucp_managed_instances_internal 
    WHERE instance_id = @instance_id
    
    -- Clean up managed instance health states and update dashboard stats
    -- This block comes before the delete from sysutility_ucp_managed_instances_internal
    -- so we can retrieve the instance name in case there's an error inside the block and
    -- this sp is rerun
    IF EXISTS (SELECT 1 FROM msdb.dbo.sysutility_ucp_mi_health_internal WHERE mi_name = @instance_name)
    BEGIN
        DECLARE @health_state_id INT
        SELECT @health_state_id = latest_health_state_id FROM msdb.dbo.sysutility_ucp_processing_state_internal
        
        -- Delete the managed instance record
        DELETE FROM msdb.dbo.sysutility_ucp_mi_health_internal WHERE mi_name = @instance_name

        -- Re-compute the dashboard health stats
        DELETE FROM msdb.dbo.sysutility_ucp_aggregated_mi_health_internal WHERE set_number = @health_state_id
        EXEC msdb.dbo.sp_sysutility_ucp_calculate_aggregated_mi_health @health_state_id   
        
        -- Delete the health records of DACs in the removed instance.
        DELETE FROM msdb.dbo.sysutility_ucp_dac_health_internal WHERE dac_server_instance_name = @instance_name        
        
        -- Re-compute the DAC health stats in the dashboard
        DELETE FROM msdb.dbo.sysutility_ucp_aggregated_dac_health_internal WHERE set_number = @health_state_id
        EXEC msdb.dbo.sp_sysutility_ucp_calculate_aggregated_dac_health @health_state_id   
    END

    DELETE [dbo].[sysutility_ucp_managed_instances_internal] 
        WHERE instance_id = @instance_id

    SELECT @retval = @@error
    RETURN(@retval)
END
GO

/**********************************************************************/
/*                                                                    */
/* Remove a managed instance from its UCP                             */
/*                                                                    */
/**********************************************************************/
IF OBJECT_ID ('dbo.sp_sysutility_mi_remove') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [dbo].[sp_sysutility_mi_remove]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE [dbo].[sp_sysutility_mi_remove]
END;
GO

CREATE PROCEDURE [dbo].[sp_sysutility_mi_remove]
WITH EXECUTE AS OWNER
AS
BEGIN
    SET NOCOUNT ON;

    EXEC msdb.dbo.sp_sysutility_mi_disable_collection;
    
    --###FP 1

    EXEC msdb.dbo.sp_syscollector_disable_collector;
    
    --###FP 2

    DECLARE @collection_set_id int;
    DECLARE @proxy_id int;
    DECLARE @utility_collection_set_uid uniqueidentifier = N'ABA37A22-8039-48C6-8F8F-39BFE0A195DF';

    -- find our collection set and determine if its proxy is set
    SELECT 
         @collection_set_id = collection_set.collection_set_id
        ,@proxy_id = collection_set.proxy_id
    FROM msdb.dbo.syscollector_collection_sets AS collection_set
    WHERE collection_set.collection_set_uid = @utility_collection_set_uid;

    -- determine if DC is running
    -- if agent is not running, is_running won't be changed
    -- so default it to false
    DECLARE @is_running int = 0
    EXEC msdb.dbo.sp_syscollector_get_collection_set_execution_status @collection_set_id, @is_running OUTPUT;
    
    --###FP 3

    IF (@is_running = 1)
    BEGIN
      EXEC msdb.dbo.sp_syscollector_stop_collection_set @collection_set_id;
    END
    
    --###FP 4

    IF (@proxy_id IS NOT NULL )
    BEGIN
        -- retrieve the current cache directory setting
        -- if the setting can't be found, assume it is not set	
        DECLARE @cache_directory_is_set bit = 0
        SELECT @cache_directory_is_set = CASE WHEN config.parameter_value IS NULL THEN 0 ELSE 1 END
        FROM msdb.dbo.syscollector_config_store AS config
        WHERE config.parameter_name = N'CacheDirectory';
        
        IF(@cache_directory_is_set = 1)
        BEGIN
          EXEC msdb.dbo.sp_syscollector_set_cache_directory @cache_directory = NULL;
        END
        
        --###FP 5
        
        -- clear the proxy
        -- because we only enter this block if proxy is set,
        -- postpone clearing proxy until the end of the block
        -- to ensure that if clearing the cache directory fails
        -- we will re-enter this block the next time this proc is called
        EXEC msdb.dbo.sp_syscollector_update_collection_set @collection_set_id = @collection_set_id, @proxy_name = N'';
        
        --###FP 6
    END
    
    EXEC msdb.dbo.sp_syscollector_enable_collector;
    
    --###FP 7

    EXEC msdb.dbo.sp_sysutility_mi_remove_ucp_registration;
END;
GO

/**********************************************************************/
/* Provision security for utility objects                             */
/**********************************************************************/

EXEC sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_mi_file_space_health_internal', N'UtilityCMRReader'
EXEC sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_mi_database_health_internal', N'UtilityCMRReader'
EXEC sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_dac_file_space_health_internal', N'UtilityCMRReader'
EXEC sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_mi_volume_space_health_internal', N'UtilityCMRReader'

EXEC sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_mi_file_space_health', N'UtilityCMRReader'
EXEC sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_mi_database_health', N'UtilityCMRReader'
EXEC sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_dac_database_file_space_health', N'UtilityCMRReader'
EXEC sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_mi_volume_space_health', N'UtilityCMRReader'



/**********************************************************************/
/* Add an extended database property to identify database as a utility */
/* store.                                                             */
/*                                                                    */
/**********************************************************************/

DECLARE @prop_name sysname
DECLARE @new_value sql_variant
DECLARE @old_value sql_variant
DECLARE @new_value_str varchar(256);
SET @prop_name = 'Microsoft_Management_Utility_Version'
SET @new_value = '___SQLVERSION___NEW___'     -- This should be replaced at build time with the sql build number.  See DC's code for how to do this when needed.

SELECT @old_value = value
FROM fn_listextendedproperty(@prop_name, NULL, NULL, NULL, NULL, NULL, NULL)

IF (@old_value IS NOT NULL)
BEGIN
    -- At some point we might want to do version checking here, but for now, just drop the property
    RAISERROR ('Dropping extended database property - Microsoft_Management_Utility_Version', 0, 1) WITH NOWAIT;
    EXEC sp_dropextendedproperty @name = @prop_name
END            

SET @new_value_str = CONVERT (varchar(256), @new_value);
RAISERROR ('Creating extended database property - Microsoft_Management_Utility_Version (version ''%s'')', 0, 1, @new_value_str) WITH NOWAIT;
EXEC sp_addextendedproperty
        @name = @prop_name,
        @value = @new_value
GO



-----------------------------------------------------------
-- Security for Utility objects
-----------------------------------------------------------
-- Provision the Utility tables: Grant the tables, Select permissions to public
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_mi_configuration_internal', N'UtilityIMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_mi_session_statistics_internal', N'UtilityIMRReader'
GRANT INSERT, DELETE  ON [dbo].[sysutility_mi_session_statistics_internal] TO [UtilityIMRWriter]
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_mi_dac_execution_statistics_internal', N'UtilityIMRReader'
GRANT INSERT, DELETE  ON [dbo].[sysutility_mi_dac_execution_statistics_internal] TO [UtilityIMRWriter]
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_mi_volumes_stage_internal', N'UtilityIMRReader'
GRANT INSERT, DELETE  ON [dbo].[sysutility_mi_volumes_stage_internal]    TO [UtilityIMRWriter]
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_mi_cpu_stage_internal', N'UtilityIMRReader'
GRANT INSERT, DELETE  ON [dbo].[sysutility_mi_cpu_stage_internal]    TO [UtilityIMRWriter]
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_mi_smo_stage_internal', N'UtilityIMRReader'
GRANT INSERT, DELETE  ON [dbo].[sysutility_mi_smo_stage_internal]    TO [UtilityIMRWriter]
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_mi_smo_properties_to_collect_internal', N'UtilityIMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_mi_smo_objects_to_collect_internal', N'UtilityIMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_managed_instances_internal', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_configuration_internal', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_processing_state_internal', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_health_policies_internal', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_aggregated_dac_health_internal', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_aggregated_mi_health_internal', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_dac_health_internal', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_mi_health_internal', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_policy_check_conditions_internal', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_policy_target_conditions_internal', N'UtilityCMRReader'


EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_deployed_dacs', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_computers', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_volumes', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_utility_space_utilization', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_instances', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_databases', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_filegroups', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_datafiles', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_logfiles', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_mi_database_file_space_utilizations', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_dac_database_file_space_utilizations', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_mi_volume_space_utilizations', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_dac_volume_space_utilizations', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_mi_cpu_utilizations', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'fn_sysutility_ucp_get_file_space_utilization_history', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'fn_sysutility_ucp_get_cpu_utilization_history', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'fn_sysutility_get_is_instance_ucp', N'UtilityCMRReader'
GO

-- Provision the Utility views: Keep the views UtilityCMRReader
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_managed_instances', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_configuration', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_policies', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_policy_violations', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_aggregated_dac_health', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_aggregated_mi_health', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_dac_health', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_mi_health', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_policy_check_conditions', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_policy_target_conditions', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_dac_policy_type', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_instance_policy_type', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_instance_policies', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_dac_policies', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sysutility_ucp_computer_policies', N'UtilityCMRReader'

EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'fn_encode_sqlname_for_powershell', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'fn_sysutility_ucp_get_applicable_policy', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'fn_sysutility_ucp_get_aggregated_failure_count', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'fn_sysutility_ucp_get_global_health_policy', N'UtilityCMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'fn_sysutility_ucp_get_aggregated_health', N'UtilityCMRReader'
GO


-- Grant Utility SPs, execute permissions to the role
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sp_sysutility_mi_collect_dac_execution_statistics_internal', N'UtilityIMRWriter'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'sp_sysutility_mi_get_dac_execution_statistics_internal', N'UtilityIMRWriter'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'fn_sysutility_mi_get_batch_manifest', N'UtilityIMRWriter'

EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'fn_sysutility_mi_get_cpu_architecture_name', N'UtilityIMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'fn_sysutility_mi_get_cpu_family_name', N'UtilityIMRReader'
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'fn_sysutility_get_culture_invariant_conversion_style_internal', N'UtilityIMRReader'
GO

-- Provision the table valued functions
EXEC dbo.sp_sysutility_ucp_provision_utility_object_internal N'fn_sysutility_ucp_get_policy_violations', N'UtilityCMRReader'
GO

-- Redirect the Utility msdb wrapper views from the msdb stub tables to the sysutility_mdw tables, if this 
-- instance is a UCP.  We tell the proc not to refresh the msdb views (yet), though. Because setup upgrades 
-- the sysutility_mdw database after msdb, when this script is executed the MDW tables might not yet have all 
-- of the columns that are required by the upgraded msdb views. Refeshing them in this partially-upgraded 
-- state would cause an error.  This procedure will be executed again after instmdw.sql has been run and 
-- both MDW and msdb have been upgraded.  At that time, the views will be refreshed. 
EXEC msdb.dbo.sp_sysutility_ucp_initialize_mdw 
    @mdw_database_name = 'sysutility_mdw', 
    @require_mdw = 0, 
    @refresh_views = 0;
GO

DECLARE @advanced_options_original_value INT;
DECLARE @configuration_original_value    INT;
SELECT 
   @advanced_options_original_value = advanced_options_original_value, 
   @configuration_original_value = configuration_original_value 
FROM #configuration_original_values 
WHERE configuration_option = 'Agent XPs';

-- Restore Agent XPs to previous state
EXECUTE #sp_restore_component_state 'Agent XPs', 
    @advopt_old_value = @advanced_options_original_value, 
    @comp_old_value = @configuration_original_value;
GO


/**************************************************************/
/*                     END UTILITY SCRIPTS                    */
/**************************************************************/

/**********************************************************************/
/* OBD.SQL                                                            */
/*                                                                    */
/* Signs all the SPs that will be added to the                        */
/* Off By Default component.                                          */
/* TODO: Split this too based on each component.                      */
/*                                                                    */
/*                                                                    */
/* Copyright (c) Microsoft Corporation                                */
/* All Rights Reserved.                                               */
/*                                                                    */
/**********************************************************************/


/**************************************************************/
/* Sign agent sps and add them to Off By Default component    */
/*                                                            */
/* Also sign SPs for other components located in MSDB         */
/**************************************************************/

-- List all of the stored procedures we need to sign with the Agent
-- signing certicate. Optionally if your SP belongs in the 'Agent XPs'
-- Off-by-default component, then specify 1 for the 'obdComponent'
-- column; If it belongs in 'DBMail XPs', specify 2.

CREATE TABLE #sp_table (
[schema_name] SYSNAME, 
[name] SYSNAME, 
[sign] INT, 
obdComponent INT, 
bNewProc INT)
GO

INSERT INTO #sp_table VALUES(N'dbo', N'sp_sqlagent_is_srvrolemember',                       1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_category_identifiers',                     1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_proxy_identifiers',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_credential_identifiers',                   1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_subsystem_identifiers',                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_login_identifiers',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_proxy',                                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_proxy',                                       1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_proxy',                                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_update_proxy',                                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_sqlagent_is_member',                              1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_proxy_permissions',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_proxy',                                      1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_grant_proxy_to_subsystem',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_grant_login_to_proxy',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_revoke_login_from_proxy',                         1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_revoke_proxy_from_subsystem',                     1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_enum_proxy_for_subsystem',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_enum_login_for_proxy',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_sqlagent_get_startup_info',                       1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_sqlagent_has_server_access',                      1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_sem_add_message',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_sem_drop_message',                                1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_get_message_description',                         1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_sqlagent_get_perf_counters',                      1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_sqlagent_notify',                                 1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_is_sqlagent_starting',                            1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_job_identifiers',                          1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_schedule_identifiers',                     1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_jobproc_caller',                           1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_downloaded_row_limiter',                          1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_post_msx_operation',                              1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_performance_condition',                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_job_date',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_job_time',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_alert',                                    1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_update_alert',                                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_job_references',                           1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_all_msx_jobs',                             1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_generate_target_server_job_assignment_sql',       1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_generate_server_description',                     1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_msx_set_account',                                 1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_msx_get_account',                                 1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_operator',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_msx_defect',                                      1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_msx_enlist',                                      1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_targetserver',                             1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_get_sqlagent_properties',                         1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_set_sqlagent_properties',                         1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_targetservergroup',                           1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_update_targetservergroup',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_targetservergroup',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_targetservergroup',                          1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_targetsvrgrp_member',                         1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_targetsvrgrp_member',                      1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_category',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_category',                                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_update_category',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_category',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_category',                                   1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_targetserver',                               1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_resync_targetserver',                             1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_purge_jobhistory',                                1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_jobhistory',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_jobhistory_full',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_jobhistory_summary',                         1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_jobhistory_sem',                             1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_jobserver',                                   1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_jobserver',                                1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_jobserver',                                  1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_downloadlist',                               1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_enum_sqlagent_subsystems',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_enum_sqlagent_subsystems_internal',               1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_subsystem',                                1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_subsystems',                               1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_schedule',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_schedule',                                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_attach_schedule',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_detach_schedule',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_update_schedule',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_schedule',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_get_jobstep_db_username',                         1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_jobstep',                                  1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_jobstep_internal',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_jobstep',                                     1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_update_jobstep',                                  1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_jobstep',                                  1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_jobstep',                                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_write_sysjobstep_log',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_jobsteplog',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_jobsteplog',                               1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_get_schedule_description',                        1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_jobschedule',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_update_replication_job_parameter',                1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_update_jobschedule',                              1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_jobschedule',                              1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_schedule',                                   1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_jobschedule',                                1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_job',                                      1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_job',                                         1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_update_job',                                      1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_job',                                      1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_get_composite_job_info',                          1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_job',                                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_jobcount ',                                  1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_jobs_in_schedule',                           1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_manage_jobs_by_login',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_apply_job_to_targets',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_remove_job_from_targets',                         1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_get_job_alerts',                                  1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_convert_jobid_to_char',                           1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_start_job',                                       1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_stop_job',                                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_cycle_agent_errorlog',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_get_chunked_jobstep_params',                      1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_check_for_owned_jobs',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_check_for_owned_jobsteps',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_sqlagent_refresh_job',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_jobhistory_row_limiter',                          1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_sqlagent_log_jobhistory',                         1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_sqlagent_check_msx_version',                      1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_sqlagent_probe_msx',                              1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_set_local_time',                                  1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_multi_server_job_summary',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_target_server_summary',                           1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_uniquetaskname',                                  1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_addtask',                                         1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_droptask',                                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_alert_internal',                              1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_alert',                                       1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_alert',                                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_alert',                                      1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_operator',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_operator',                                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_update_operator',                                 1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_operator',                                   1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_operator_jobs',                              1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_operator_identifiers',                     1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_notify_operator',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_verify_notification',                             1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_notification',                                1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_update_notification',                             1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_notification',                             1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_notification',                               1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_jobactivity',                                1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_enlist_tsx',                                      1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'trig_targetserver_insert',                           1, 0, 0)

-- Database Mail configuration procs
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_verify_accountparams_sp',                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_verify_principal_sp',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_verify_profile_sp',                          1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_verify_account_sp',                          1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_add_profile_sp',                             1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_update_profile_sp',                          1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_delete_profile_sp',                          1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_help_profile_sp',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_create_user_credential_sp',                  1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_alter_user_credential_sp',                   1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_drop_user_credential_sp',                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_add_account_sp',                             1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_update_account_sp',                          1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_delete_account_sp',                          1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_help_account_sp',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_help_admin_account_sp',                      1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_add_profileaccount_sp',                      1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_update_profileaccount_sp',                   1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_delete_profileaccount_sp',                   1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_help_profileaccount_sp',                     1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_configure_sp',                               1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_help_configure_sp',                          1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_help_configure_value_sp',                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_add_principalprofile_sp',                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_update_principalprofile_sp',                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_delete_principalprofile_sp',                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_help_principalprofile_sp',                   1, 0, 0)

-- Database Mail: mail host database specific procs
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_start_sp',                                   1, 2, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_stop_sp',                                    1, 2, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_logmailevent_sp',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_SendMailMessage',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_isprohibited',                                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_SendMailQueues',                                  1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_ProcessResponse',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_MailItemResultSets',                              1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_process_DialogTimer',                             1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_readrequest',                                     1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_GetAttachmentData',                               1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_RunMailQuery',                                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_help_queue_sp',                              1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_help_status_sp',                             1, 2, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_delete_mailitems_sp',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sysmail_delete_log_sp',                              1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_validate_user',                                   1, 2, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_send_dbmail',                                     1, 2, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_ExternalMailQueueListener',                       1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_sysmail_activate',                                1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_get_script',                                      1, 0, 0)

-- maintenance Plans
INSERT INTO #sp_table VALUES(N'dbo', N'sp_maintplan_delete_log',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_maintplan_delete_subplan',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_maintplan_open_logentry',                         1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_maintplan_close_logentry',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_maintplan_update_log',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_maintplan_update_subplan',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_maintplan_delete_plan',                           1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_maintplan_start',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_clear_dbmaintplan_by_db',                         1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_maintenance_plan',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_maintenance_plan',                         1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_maintenance_plan_db',                         1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_maintenance_plan_db',                      1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_maintenance_plan_job',                        1, 1, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_maintenance_plan_job',                     1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_help_maintenance_plan',                           1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_maintplan_update_subplan_tsx',                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_maintplan_subplans_by_job',                       1, 0, 0)

-- Log Shipping
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_log_shipping_monitor_jobs',                   1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_log_shipping_primary',                        1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_add_log_shipping_secondary',                      1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_log_shipping_monitor_jobs',                1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_log_shipping_primary',                     1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_log_shipping_secondary ',                  1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_log_shipping_in_sync',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_log_shipping_get_date_from_file ',                1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_get_log_shipping_monitor_info',                   1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_update_log_shipping_monitor_info',                1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_delete_log_shipping_monitor_info',                1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_remove_log_shipping_monitor_account',             1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_log_shipping_monitor_backup',                     1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_log_shipping_monitor_restore',                    1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_change_monitor_role',                             1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_create_log_shipping_monitor_account',             1, 0, 0)

-- SSIS
INSERT INTO #sp_table VALUES(N'dbo', N'sp_ssis_addlogentry',                                1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_ssis_listpackages',                               1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_ssis_listfolders',                                1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_ssis_deletepackage',                              1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_ssis_deletefolder',                               1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_ssis_getpackage',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_ssis_getfolder',                                  1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_ssis_putpackage',                                 1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_ssis_addfolder',                                  1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_ssis_renamefolder',                               1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_ssis_setpackageroles',                            1, 0, 0)
INSERT INTO #sp_table VALUES(N'dbo', N'sp_ssis_getpackageroles',                            1, 0, 0)

-- Smart Admin
insert into #sp_table values(N'smart_admin', N'sp_create_job',                              1, 0, 0)
insert into #sp_table values(N'smart_admin', N'sp_add_task_command',                        1, 0, 0)
insert into #sp_table values(N'smart_admin', N'sp_set_db_backup',                           1, 0, 0)
insert into #sp_table values(N'smart_admin', N'sp_check_health',                            1, 0, 0)
insert into #sp_table values(N'smart_admin', N'sp_check_backup_health',                     1, 0, 0)
insert into #sp_table values(N'smart_admin', N'sp_get_history',                             1, 0, 0)
insert into #sp_table values(N'smart_admin', N'sp_get_backup_history',                      1, 0, 0)
insert into #sp_table values(N'smart_admin', N'sp_get_backup_diagnostics',                  1, 0, 0)
insert into #sp_table values(N'smart_admin', N'sp_master_switch',                           1, 0, 0)
insert into #sp_table values(N'smart_admin', N'sp_xe_start_session',                        1, 0, 0)

-- Managed Backup
insert into #sp_table values(N'managed_backup', N'sp_backup_config_basic',                  1, 0, 0)
insert into #sp_table values(N'managed_backup', N'sp_backup_config_advanced',               1, 0, 0)
insert into #sp_table values(N'managed_backup', N'sp_backup_config_schedule',               1, 0, 0)
insert into #sp_table values(N'managed_backup', N'sp_create_job',                           1, 0, 0)
insert into #sp_table values(N'managed_backup', N'sp_add_task_command',                     1, 0, 0)
insert into #sp_table values(N'managed_backup', N'sp_backup_on_demand',                     1, 0, 0)
insert into #sp_table values(N'managed_backup', N'sp_set_parameter',                        1, 0, 0)
insert into #sp_table values(N'managed_backup', N'sp_get_backup_diagnostics',               1, 0, 0)
insert into #sp_table values(N'managed_backup', N'sp_backup_master_switch',                 1, 0, 0)
insert into #sp_table values(N'managed_backup', N'sp_get_encryption_option',                1, 0, 0)
insert into #sp_table values(N'managed_backup', N'sp_do_backup',                            1, 0, 0)

/**************************************************************/
/* Alwayson built-in policies                                 */
/**************************************************************/
SET NOCOUNT ON
PRINT ''
PRINT '--------------------------------'
PRINT 'Starting execution of Alwayson.SQL'
PRINT '--------------------------------'
go

-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create temp procedure to handle the cascade delete issue in procedure sp_syspolicy_delete_policy, see VSTS 904601 for more details.
--- basically this procedure is to simulate the sp_syspolicy_delete_policy + syspolicy_instead_delete_policy_trigger, so if there is any logic change
--- there, we need to update this temp proc too.
--- In case a failure occurs, procedure may have not been dropped, and the create will fail, so execute CREATE OR ALTER.
-------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE OR ALTER PROCEDURE #sp_syspolicy_cascade_delete_policy
   @name sysname = NULL
AS
BEGIN
    DECLARE @policyid int;
    DECLARE @retval int;
    EXEC @retval = [msdb].[dbo].[sp_syspolicy_verify_policy_identifiers] @name, @policyid OUTPUT
    IF (@retval = 0)
	BEGIN
		DELETE msdb.dbo.syspolicy_policy_execution_history_internal	WHERE policy_id = @policyid
		DELETE msdb.dbo.syspolicy_system_health_state_internal		WHERE policy_id = @policyid
		EXEC msdb.dbo.sp_syspolicy_delete_policy @policy_id = @policyid
	END
END
go

Begin
    Declare @alwayson_target_set_id int
    Declare @alwayson_policy_helptext NVARCHAR(1024)
    Declare @alwayson_policy_description NVARCHAR(max)
	Declare @condition_name NVARCHAR(1024)
	Declare @expression NVARCHAR(max)
	Declare @alwayson_policy_helptext_prefix NVARCHAR(100)
	Declare @alwayson_policy_description_prefix NVARCHAR(100)

	select @alwayson_policy_helptext_prefix = 'Alwayson Policy Helptext ID:'
	select @alwayson_policy_description_prefix = 'Alwayson Policy Description ID:'

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Delete existing Policies,objectSets
	-- NOTE:
	-- We shall not delete condition and category because customer may have dependency on condition, especially on category.
	-- so if we delete the category, the upgrade will be broken.
	-- instead we shall use add_condition/add_category if the condition/category does not exist.
	-- if they exist, use update. 
	-- For Denali, the updating category does not really do anything. But for the future if we want to change the category name, that will be useful
	-- the condition update will be very useful if we decide to change the expression of the condition.
	-- the policy does not have any dependency, so "delete and add" are fine.
	-- object set is also fine because it's already referred by our policy, user-defined policy can not refer to it.
	--
	-- one thing here is if we upgrade from Denali CTP3 (SSS) to Denali CTP3 refresh (MS) or later, the old policy/condition/category will be left
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Deleting existing policies, objectsets...'
	
	PRINT ''
	PRINT 'Deleting AlwaysOnArJoinStateHealthPolicy'
	IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'AlwaysOnArJoinStateHealthPolicy')
    BEGIN
    	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnArJoinStateHealthPolicy', @marker=0
        EXECUTE #sp_syspolicy_cascade_delete_policy @name=N'AlwaysOnArJoinStateHealthPolicy'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnArJoinStateHealthPolicy_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'AlwaysOnArJoinStateHealthPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnArJoinStateHealthPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'AlwaysOnArJoinStateHealthPolicy_ObjectSet'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnAgReplicasConnectionHealthPolicy'
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'AlwaysOnAgReplicasConnectionHealthPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnAgReplicasConnectionHealthPolicy', @marker=0
        EXEC #sp_syspolicy_cascade_delete_policy @name=N'AlwaysOnAgReplicasConnectionHealthPolicy'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnAgReplicasConnectionHealthPolicy_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'AlwaysOnAgReplicasConnectionHealthPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnAgReplicasConnectionHealthPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'AlwaysOnAgReplicasConnectionHealthPolicy_ObjectSet'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnAgReplicasDataSynchronizationHealthPolicy'
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'AlwaysOnAgReplicasDataSynchronizationHealthPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnAgReplicasDataSynchronizationHealthPolicy', @marker=0
        EXEC #sp_syspolicy_cascade_delete_policy @name=N'AlwaysOnAgReplicasDataSynchronizationHealthPolicy'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnAgReplicasDataSynchronizationHealthPolicy_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'AlwaysOnAgReplicasDataSynchronizationHealthPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnAgReplicasDataSynchronizationHealthPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'AlwaysOnAgReplicasDataSynchronizationHealthPolicy_ObjectSet'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnAgReplicasRoleHealthPolicy'
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'AlwaysOnAgReplicasRoleHealthPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnAgReplicasRoleHealthPolicy', @marker=0
        EXEC #sp_syspolicy_cascade_delete_policy @name=N'AlwaysOnAgReplicasRoleHealthPolicy'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnAgReplicasRoleHealthPolicy_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'AlwaysOnAgReplicasRoleHealthPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnAgReplicasRoleHealthPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'AlwaysOnAgReplicasRoleHealthPolicy_ObjectSet'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy'
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy', @marker=0
        EXEC #sp_syspolicy_cascade_delete_policy @name=N'AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy_ObjectSet'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnDbrJoinStatePolicy'
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'AlwaysOnDbrJoinStatePolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnDbrJoinStatePolicy', @marker=0
        EXEC #sp_syspolicy_cascade_delete_policy @name=N'AlwaysOnDbrJoinStatePolicy'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnDbrJoinStatePolicy_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'AlwaysOnDbrJoinStatePolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnDbrJoinStatePolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'AlwaysOnDbrJoinStatePolicy_ObjectSet'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnDbrSuspendStatePolicy'
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'AlwaysOnDbrSuspendStatePolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnDbrSuspendStatePolicy', @marker=0
        EXEC #sp_syspolicy_cascade_delete_policy @name=N'AlwaysOnDbrSuspendStatePolicy'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnDbrSuspendStatePolicy_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'AlwaysOnDbrSuspendStatePolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnDbrSuspendStatePolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'AlwaysOnDbrSuspendStatePolicy_ObjectSet'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnAgOnlineStateHealthPolicy'
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'AlwaysOnAgOnlineStateHealthPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnAgOnlineStateHealthPolicy', @marker=0
        EXEC #sp_syspolicy_cascade_delete_policy @name=N'AlwaysOnAgOnlineStateHealthPolicy'
    END    

	PRINT ''
	PRINT 'Deleting existing AlwaysOnAgOnlineStateHealthPolicy_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'AlwaysOnAgOnlineStateHealthPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnAgOnlineStateHealthPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'AlwaysOnAgOnlineStateHealthPolicy_ObjectSet'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnAgAutomaticFailoverHealthPolicy'
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'AlwaysOnAgAutomaticFailoverHealthPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnAgAutomaticFailoverHealthPolicy', @marker=0
        EXEC #sp_syspolicy_cascade_delete_policy @name=N'AlwaysOnAgAutomaticFailoverHealthPolicy'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnAgAutomaticFailoverHealthPolicy_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'AlwaysOnAgAutomaticFailoverHealthPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnAgAutomaticFailoverHealthPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'AlwaysOnAgAutomaticFailoverHealthPolicy_ObjectSet'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnArConnectionHealthPolicy'
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'AlwaysOnArConnectionHealthPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnArConnectionHealthPolicy', @marker=0
        EXEC #sp_syspolicy_cascade_delete_policy @name=N'AlwaysOnArConnectionHealthPolicy'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnArConnectionHealthPolicy_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'AlwaysOnArConnectionHealthPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnArConnectionHealthPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'AlwaysOnArConnectionHealthPolicy_ObjectSet'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnDbrDataSynchronizationState'
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'AlwaysOnDbrDataSynchronizationState')
    BEGIN
    	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnDbrDataSynchronizationState', @marker=0
        EXEC #sp_syspolicy_cascade_delete_policy @name=N'AlwaysOnDbrDataSynchronizationState'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnDbrDataSynchronizationState_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'AlwaysOnDbrDataSynchronizationState_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnDbrDataSynchronizationState_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'AlwaysOnDbrDataSynchronizationState_ObjectSet'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnArDataSynchronizationHealthPolicy'
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'AlwaysOnArDataSynchronizationHealthPolicy')
    BEGIN
    	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnArDataSynchronizationHealthPolicy', @marker=0
        EXEC #sp_syspolicy_cascade_delete_policy @name=N'AlwaysOnArDataSynchronizationHealthPolicy'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnArDataSynchronizationHealthPolicy_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'AlwaysOnArDataSynchronizationHealthPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnArDataSynchronizationHealthPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'AlwaysOnArDataSynchronizationHealthPolicy_ObjectSet'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnArRoleHealthPolicy'
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'AlwaysOnArRoleHealthPolicy')
    BEGIN
    	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnArRoleHealthPolicy', @marker=0
        EXEC #sp_syspolicy_cascade_delete_policy @name=N'AlwaysOnArRoleHealthPolicy'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnArRoleHealthPolicy_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'AlwaysOnArRoleHealthPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnArRoleHealthPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'AlwaysOnArRoleHealthPolicy_ObjectSet'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnAgWSFClusterHealthPolicy'
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'AlwaysOnAgWSFClusterHealthPolicy')
    BEGIN
    	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnAgWSFClusterHealthPolicy', @marker=0
        EXEC #sp_syspolicy_cascade_delete_policy @name=N'AlwaysOnAgWSFClusterHealthPolicy'
    END    

	PRINT ''
	PRINT 'Deleting AlwaysOnAgWSFClusterHealthPolicy_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'AlwaysOnAgWSFClusterHealthPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnAgWSFClusterHealthPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'AlwaysOnAgWSFClusterHealthPolicy_ObjectSet'
    END    

	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	-- Category: Availability group errors (any replica role)
	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating category: Availability group errors (any replica role)...'
	IF EXISTS(SELECT policy_category_id FROM msdb.dbo.syspolicy_policy_categories WHERE name=N'Availability group errors (any replica role)')	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_rename_policy_category @name=N'Availability group errors (any replica role)', @new_name=N'Availability group errors (any replica role)'	
	END
	ELSE	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_policy_category @name=N'Availability group errors (any replica role)', @policy_category_id=0	
	END

	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	-- Category: Availability group errors (primary replica only)
	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating category: Availability group errors (primary replica only)...'
	IF EXISTS(SELECT policy_category_id FROM msdb.dbo.syspolicy_policy_categories WHERE name=N'Availability group errors (primary replica only)')	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_rename_policy_category @name=N'Availability group errors (primary replica only)', @new_name=N'Availability group errors (primary replica only)'	
	END
	ELSE	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_policy_category @name=N'Availability group errors (primary replica only)', @policy_category_id=0	
	END
   
	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	-- Category: Availability replica errors 
	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating category: Availability replica errors...'
	IF EXISTS(SELECT policy_category_id FROM msdb.dbo.syspolicy_policy_categories WHERE name=N'Availability replica errors')	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_rename_policy_category @name=N'Availability replica errors', @new_name=N'Availability replica errors'	
	END
	ELSE	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_policy_category @name=N'Availability replica errors', @policy_category_id=0	
	END
   
	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	-- Category: Availability database errors 
	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating category: Availability database errors...'
	IF EXISTS(SELECT policy_category_id FROM msdb.dbo.syspolicy_policy_categories WHERE name=N'Availability database errors')	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_rename_policy_category @name=N'Availability database errors', @new_name=N'Availability database errors'	
	END
	ELSE	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_policy_category @name=N'Availability database errors', @policy_category_id=0	
	END

	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	-- Category: Availability group warnings (any replica role) 
	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating category: Availability group warnings (any replica role)...'
	IF EXISTS(SELECT policy_category_id FROM msdb.dbo.syspolicy_policy_categories WHERE name=N'Availability group warnings (any replica role)')	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_rename_policy_category @name=N'Availability group warnings (any replica role)', @new_name=N'Availability group warnings (any replica role)'	
	END
	ELSE	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_policy_category @name=N'Availability group warnings (any replica role)', @policy_category_id=0	
	END


	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	-- Category: Availability group warnings (primary replica only) 
	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating category: Availability group warnings (primary replica only)...'
	IF EXISTS(SELECT policy_category_id FROM msdb.dbo.syspolicy_policy_categories WHERE name=N'Availability group warnings (primary replica only)')	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_rename_policy_category @name=N'Availability group warnings (primary replica only)', @new_name=N'Availability group warnings (primary replica only)'	
	END
	ELSE	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_policy_category @name=N'Availability group warnings (primary replica only)', @policy_category_id=0	
	END


	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	-- Category: Availability replica warnings 
	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating category: Availability replica warnings...'
	IF EXISTS(SELECT policy_category_id FROM msdb.dbo.syspolicy_policy_categories WHERE name=N'Availability replica warnings')	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_rename_policy_category @name=N'Availability replica warnings', @new_name=N'Availability replica warnings'	
	END
	ELSE	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_policy_category @name=N'Availability replica warnings', @policy_category_id=0	
	END

	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	-- Category: Availability database warnings 
	-------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating category: Availability database warnings...'
	IF EXISTS(SELECT policy_category_id FROM msdb.dbo.syspolicy_policy_categories WHERE name=N'Availability database warnings')	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_rename_policy_category @name=N'Availability database warnings', @new_name=N'Availability database warnings'	
	END
	ELSE	
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_policy_category @name=N'Availability database warnings', @policy_category_id=0	
	END

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: AlwaysOnAgAutomaticFailoverHealthCondition 
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Condition: AlwaysOnAgAutomaticFailoverHealthCondition...'
	set @condition_name = N'AlwaysOnAgAutomaticFailoverHealthCondition'
	set @expression = N'<Operator>
		  <TypeClass>Bool</TypeClass>
		  <OpType>OR</OpType>
		  <Count>2</Count>
		  <Group>
			<TypeClass>Bool</TypeClass>
			<Count>1</Count>
			<Operator>
			  <TypeClass>Bool</TypeClass>
			  <OpType>AND</OpType>
			  <Count>2</Count>
			  <Operator>
				<TypeClass>Bool</TypeClass>
				<OpType>EQ</OpType>
				<Count>2</Count>
				<Attribute>
				  <TypeClass>Bool</TypeClass>
				  <Name>IsAutoFailover</Name>
				</Attribute>
				<Function>
				  <TypeClass>Bool</TypeClass>
				  <FunctionType>True</FunctionType>
				  <ReturnType>Bool</ReturnType>
				  <Count>0</Count>
				</Function>
			  </Operator>
			  <Operator>
				<TypeClass>Bool</TypeClass>
				<OpType>GT</OpType>
				<Count>2</Count>
				<Attribute>
				  <TypeClass>Numeric</TypeClass>
				  <Name>NumberOfSynchronizedSecondaryReplicas</Name>
				</Attribute>
				<Constant>
				  <TypeClass>Numeric</TypeClass>
				  <ObjType>System.Double</ObjType>
				  <Value>0</Value>
				</Constant>
			  </Operator>
			</Operator>
		  </Group>
		  <Operator>
			<TypeClass>Bool</TypeClass>
			<OpType>EQ</OpType>
			<Count>2</Count>
			<Attribute>
			  <TypeClass>Bool</TypeClass>
			  <Name>IsAutoFailover</Name>
			</Attribute>
			<Function>
			  <TypeClass>Bool</TypeClass>
			  <FunctionType>False</FunctionType>
			  <ReturnType>Bool</ReturnType>
			  <Count>0</Count>
			</Function>
		  </Operator>
	</Operator>'
	IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'', 
			@facet=N'IAvailabilityGroupState', @expression=@expression, @is_name_condition=0, @obj_name=N''
	END
	ELSE
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'', 
			@facet=N'IAvailabilityGroupState', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
	END
	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: AlwaysOnDbrJoinStateCondition 
    -------------------------------------------------------------------------------------------------------------------------------------------------------------    
	PRINT ''
	PRINT 'Creating Condition: AlwaysOnDbrJoinStateCondition...'
	set @condition_name = N'AlwaysOnDbrJoinStateCondition'
	set @expression = N'<Operator>
		  <TypeClass>Bool</TypeClass>
		  <OpType>EQ</OpType>
		  <Count>2</Count>
		  <Attribute>
			<TypeClass>Bool</TypeClass>
			<Name>IsJoined</Name>
		  </Attribute>
		  <Function>
			<TypeClass>Bool</TypeClass>
			<FunctionType>True</FunctionType>
			<ReturnType>Bool</ReturnType>
			<Count>0</Count>
		  </Function>
	</Operator>'
	IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'', 
			@facet=N'DatabaseReplicaState', @expression=@expression, @is_name_condition=0, @obj_name=N''
	END
	ELSE
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'', 
			@facet=N'DatabaseReplicaState', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
	END
	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: AlwaysOnAgWSFClusterHealthCondition 
    -------------------------------------------------------------------------------------------------------------------------------------------------------------    
	PRINT ''
	PRINT 'Creating Condition: AlwaysOnAgWSFClusterHealthCondition...'
	set @condition_name = N'AlwaysOnAgWSFClusterHealthCondition'
	set @expression = N'<Operator>
		  <TypeClass>Bool</TypeClass>
		  <OpType>EQ</OpType>
		  <Count>2</Count>
		  <Attribute>
			<TypeClass>Numeric</TypeClass>
			<Name>ClusterQuorumState</Name>
		  </Attribute>
		  <Function>
			<TypeClass>Numeric</TypeClass>
			<FunctionType>Enum</FunctionType>
			<ReturnType>Numeric</ReturnType>
			<Count>2</Count>
			<Constant>
			  <TypeClass>String</TypeClass>
			  <ObjType>System.String</ObjType>
			  <Value>Microsoft.SqlServer.Management.Smo.ClusterQuorumState</Value>
			</Constant>
			<Constant>
			  <TypeClass>String</TypeClass>
			  <ObjType>System.String</ObjType>
			  <Value>NormalQuorum</Value>
			</Constant>
		  </Function>
	</Operator>'
	IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'', 
			@facet=N'Server', @expression=@expression, @is_name_condition=0, @obj_name=N''
	END
	ELSE
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'', 
			@facet=N'Server', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
	END
	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: AlwaysOnArConnectionHealthCondition 
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Condition: AlwaysOnArConnectionHealthCondition...'
	set @condition_name = N'AlwaysOnArConnectionHealthCondition'
	set @expression = N'<Operator>
		  <TypeClass>Bool</TypeClass>
		  <OpType>EQ</OpType>
		  <Count>2</Count>
		  <Attribute>
			<TypeClass>Numeric</TypeClass>
			<Name>ConnectionState</Name>
		  </Attribute>
		  <Function>
			<TypeClass>Numeric</TypeClass>
			<FunctionType>Enum</FunctionType>
			<ReturnType>Numeric</ReturnType>
			<Count>2</Count>
			<Constant>
			  <TypeClass>String</TypeClass>
			  <ObjType>System.String</ObjType>
			  <Value>Microsoft.SqlServer.Management.Smo.AvailabilityReplicaConnectionState</Value>
			</Constant>
			<Constant>
			  <TypeClass>String</TypeClass>
			  <ObjType>System.String</ObjType>
			  <Value>Connected</Value>
			</Constant>
		  </Function>
	</Operator>'
	IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'', 
			@facet=N'AvailabilityReplica', @expression=@expression, @is_name_condition=0, @obj_name=N''
	END
	ELSE
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'', 
			@facet=N'AvailabilityReplica', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
	END
	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1
    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: AlwaysOnAgReplicasConnectionHealthCondition 
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Condition: AlwaysOnAgReplicasConnectionHealthCondition...'
	set @condition_name = N'AlwaysOnAgReplicasConnectionHealthCondition'
	set @expression = N'<Operator>
		  <TypeClass>Bool</TypeClass>
		  <OpType>EQ</OpType>
		  <Count>2</Count>
		  <Attribute>
			<TypeClass>Numeric</TypeClass>
			<Name>NumberOfDisconnectedReplicas</Name>
		  </Attribute>
		  <Constant>
			<TypeClass>Numeric</TypeClass>
			<ObjType>System.Double</ObjType>
			<Value>0</Value>
		  </Constant>
	</Operator>'
	IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'',
			@facet=N'IAvailabilityGroupState', @expression=@expression, @is_name_condition=0, @obj_name=N''
	END
	ELSE
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'',
		 	@facet=N'IAvailabilityGroupState', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
	END
	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1
    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: AlwaysOnAgReplicasDataSynchronizationHealthCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Condition: AlwaysOnAgReplicasDataSynchronizationHealthCondition...'
	set @condition_name = N'AlwaysOnAgReplicasDataSynchronizationHealthCondition'
	set @expression = N'<Operator>
		  <TypeClass>Bool</TypeClass>
		  <OpType>EQ</OpType>
		  <Count>2</Count>
		  <Attribute>
			<TypeClass>Numeric</TypeClass>
			<Name>NumberOfNotSynchronizingReplicas</Name>
		  </Attribute>
		  <Constant>
			<TypeClass>Numeric</TypeClass>
			<ObjType>System.Double</ObjType>
			<Value>0</Value>
		  </Constant>
	</Operator>'
	IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'',
			@facet=N'IAvailabilityGroupState', @expression=@expression, @is_name_condition=0, @obj_name=N''
	END
	ELSE
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'',
			@facet=N'IAvailabilityGroupState', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
	END
	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1
    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: AlwaysOnAgReplicasRoleHealthCondition 
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Condition: AlwaysOnAgReplicasRoleHealthCondition...'
	set @condition_name = N'AlwaysOnAgReplicasRoleHealthCondition'
	set @expression = N'<Operator>
		  <TypeClass>Bool</TypeClass>
		  <OpType>EQ</OpType>
		  <Count>2</Count>
		  <Attribute>
			<TypeClass>Numeric</TypeClass>
			<Name>NumberOfReplicasWithUnhealthyRole</Name>
		  </Attribute>
		  <Constant>
			<TypeClass>Numeric</TypeClass>
			<ObjType>System.Double</ObjType>
			<Value>0</Value>
		  </Constant>
	</Operator>'
	IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'', 
			@facet=N'IAvailabilityGroupState', @expression=@expression, @is_name_condition=0, @obj_name=N''
	END
	ELSE
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'', 
			@facet=N'IAvailabilityGroupState', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
	END
	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1
  
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: AlwaysOnAgSynchronousReplicasDataSynchronizationHealthCondition 
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Condition: AlwaysOnAgSynchronousReplicasDataSynchronizationHealthCondition...'
	set @condition_name = N'AlwaysOnAgSynchronousReplicasDataSynchronizationHealthCondition'
	set @expression = N'<Operator>
		  <TypeClass>Bool</TypeClass>
		  <OpType>EQ</OpType>
		  <Count>2</Count>
		  <Attribute>
			<TypeClass>Numeric</TypeClass>
			<Name>NumberOfNotSynchronizedReplicas</Name>
		  </Attribute>
		  <Constant>
			<TypeClass>Numeric</TypeClass>
			<ObjType>System.Double</ObjType>
			<Value>0</Value>
		  </Constant>
	</Operator>'
	IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'', 
			@facet=N'IAvailabilityGroupState', @expression=@expression, @is_name_condition=0, @obj_name=N''
	END
	ELSE
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'', 
			@facet=N'IAvailabilityGroupState', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
	END
	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1
  
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: AlwaysOnDbrSuspendStateCondition 
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Condition: AlwaysOnDbrSuspendStateCondition...'
	set @condition_name = N'AlwaysOnDbrSuspendStateCondition'
	set @expression = N'<Operator>
		  <TypeClass>Bool</TypeClass>
		  <OpType>EQ</OpType>
		  <Count>2</Count>
		  <Attribute>
			<TypeClass>Bool</TypeClass>
			<Name>IsSuspended</Name>
		  </Attribute>
		  <Function>
			<TypeClass>Bool</TypeClass>
			<FunctionType>False</FunctionType>
			<ReturnType>Bool</ReturnType>
			<Count>0</Count>
		  </Function>
	</Operator>'
	IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'', 
			@facet=N'DatabaseReplicaState', @expression=@expression, @is_name_condition=0, @obj_name=N''
	END
	ELSE
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'', 
			@facet=N'DatabaseReplicaState', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
	END
	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1
    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: AlwaysOnDbrDataSynchronizationCondition
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Condition: AlwaysOnDbrDataSynchronizationCondition...'
	set @condition_name = N'AlwaysOnDbrDataSynchronizationCondition'
	set @expression = N'<Group>
	  <TypeClass>Bool</TypeClass>
	  <Count>1</Count>
	  <Operator>
		<TypeClass>Bool</TypeClass>
		<OpType>OR</OpType>
		<Count>2</Count>
		<Group>
		  <TypeClass>Bool</TypeClass>
		  <Count>1</Count>
		  <Operator>
			<TypeClass>Bool</TypeClass>
			<OpType>AND</OpType>
			<Count>2</Count>
			<Operator>
			  <TypeClass>Bool</TypeClass>
			  <OpType>EQ</OpType>
			  <Count>2</Count>
			  <Attribute>
				<TypeClass>Numeric</TypeClass>
				<Name>ReplicaAvailabilityMode</Name>
			  </Attribute>
			  <Function>
				<TypeClass>Numeric</TypeClass>
				<FunctionType>Enum</FunctionType>
				<ReturnType>Numeric</ReturnType>
				<Count>2</Count>
				<Constant>
				  <TypeClass>String</TypeClass>
				  <ObjType>System.String</ObjType>
				  <Value>Microsoft.SqlServer.Management.Smo.AvailabilityReplicaAvailabilityMode</Value>
				</Constant>
				<Constant>
				  <TypeClass>String</TypeClass>
				  <ObjType>System.String</ObjType>
				  <Value>AsynchronousCommit</Value>
				</Constant>
			  </Function>
			</Operator>
			<Operator>
			  <TypeClass>Bool</TypeClass>
			  <OpType>NE</OpType>
			  <Count>2</Count>
			  <Attribute>
				<TypeClass>Numeric</TypeClass>
				<Name>SynchronizationState</Name>
			  </Attribute>
			  <Function>
				<TypeClass>Numeric</TypeClass>
				<FunctionType>Enum</FunctionType>
				<ReturnType>Numeric</ReturnType>
				<Count>2</Count>
				<Constant>
				  <TypeClass>String</TypeClass>
				  <ObjType>System.String</ObjType>
				  <Value>Microsoft.SqlServer.Management.Smo.AvailabilityDatabaseSynchronizationState</Value>
				</Constant>
				<Constant>
				  <TypeClass>String</TypeClass>
				  <ObjType>System.String</ObjType>
				  <Value>NotSynchronizing</Value>
				</Constant>
			  </Function>
			</Operator>
		  </Operator>
		</Group>
		<Operator>
		  <TypeClass>Bool</TypeClass>
		  <OpType>EQ</OpType>
		  <Count>2</Count>
		  <Attribute>
			<TypeClass>Numeric</TypeClass>
			<Name>SynchronizationState</Name>
		  </Attribute>
		  <Function>
			<TypeClass>Numeric</TypeClass>
			<FunctionType>Enum</FunctionType>
			<ReturnType>Numeric</ReturnType>
			<Count>2</Count>
			<Constant>
			  <TypeClass>String</TypeClass>
			  <ObjType>System.String</ObjType>
			  <Value>Microsoft.SqlServer.Management.Smo.AvailabilityDatabaseSynchronizationState</Value>
			</Constant>
			<Constant>
			  <TypeClass>String</TypeClass>
			  <ObjType>System.String</ObjType>
			  <Value>Synchronized</Value>
			</Constant>
		  </Function>
		</Operator>
	  </Operator>
	</Group>'
	IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'', 
			@facet=N'DatabaseReplicaState', @expression=@expression, @is_name_condition=0, @obj_name=N''
	END
	ELSE
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'', 
			@facet=N'DatabaseReplicaState', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
	END
	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: IsHadrEnabled 
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Condition: IsHadrEnabled...'
	set @condition_name = N'IsHadrEnabled'
	set @expression = N'<Operator>
		  <TypeClass>Bool</TypeClass>
		  <OpType>EQ</OpType>
		  <Count>2</Count>
		  <Attribute>
			<TypeClass>Bool</TypeClass>
			<Name>IsHadrEnabled</Name>
		  </Attribute>
		  <Function>
			<TypeClass>Bool</TypeClass>
			<FunctionType>True</FunctionType>
			<ReturnType>Bool</ReturnType>
			<Count>0</Count>
		  </Function>
	</Operator>'
	IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'', 
			@facet=N'Server', @expression=@expression, @is_name_condition=0, @obj_name=N''
	END
	ELSE
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'', 
			@facet=N'Server', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
	END
	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1
	    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: AlwaysOnAgOnlineStateHealthCondition 
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Condition: AlwaysOnAgOnlineStateHealthCondition...'
	set @condition_name = N'AlwaysOnAgOnlineStateHealthCondition'
	set @expression = N'<Operator>
		  <TypeClass>Bool</TypeClass>
		  <OpType>EQ</OpType>
		  <Count>2</Count>
		  <Attribute>
			<TypeClass>Bool</TypeClass>
			<Name>IsOnline</Name>
		  </Attribute>
		  <Function>
			<TypeClass>Bool</TypeClass>
			<FunctionType>True</FunctionType>
			<ReturnType>Bool</ReturnType>
			<Count>0</Count>
		  </Function>
	</Operator>'
	IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'', 
			@facet=N'IAvailabilityGroupState', @expression=@expression, @is_name_condition=0, @obj_name=N''
	END
	ELSE
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'', 
			@facet=N'IAvailabilityGroupState', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
	END
	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1
  
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: AlwaysOnArDataSynchronizationHealthCondition 
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Condition: AlwaysOnArDataSynchronizationHealthCondition...'
	set @condition_name = N'AlwaysOnArDataSynchronizationHealthCondition'
	set @expression = N'<Group>
		  <TypeClass>Bool</TypeClass>
		  <Count>1</Count>
		  <Operator>
			<TypeClass>Bool</TypeClass>
			<OpType>OR</OpType>
			<Count>2</Count>
			<Group>
			  <TypeClass>Bool</TypeClass>
			  <Count>1</Count>
			  <Operator>
				<TypeClass>Bool</TypeClass>
				<OpType>AND</OpType>
				<Count>2</Count>
				<Operator>
				  <TypeClass>Bool</TypeClass>
				  <OpType>EQ</OpType>
				  <Count>2</Count>
				  <Attribute>
					<TypeClass>Numeric</TypeClass>
					<Name>AvailabilityMode</Name>
				  </Attribute>
				  <Function>
					<TypeClass>Numeric</TypeClass>
					<FunctionType>Enum</FunctionType>
					<ReturnType>Numeric</ReturnType>
					<Count>2</Count>
					<Constant>
					  <TypeClass>String</TypeClass>
					  <ObjType>System.String</ObjType>
					  <Value>Microsoft.SqlServer.Management.Smo.AvailabilityReplicaAvailabilityMode</Value>
					</Constant>
					<Constant>
					  <TypeClass>String</TypeClass>
					  <ObjType>System.String</ObjType>
					  <Value>AsynchronousCommit</Value>
					</Constant>
				  </Function>
				</Operator>
				<Group>
				  <TypeClass>Bool</TypeClass>
				  <Count>1</Count>
				  <Operator>
					<TypeClass>Bool</TypeClass>
					<OpType>OR</OpType>
					<Count>2</Count>
					<Operator>
					  <TypeClass>Bool</TypeClass>
					  <OpType>EQ</OpType>
					  <Count>2</Count>
					  <Attribute>
						<TypeClass>Numeric</TypeClass>
						<Name>RollupSynchronizationState</Name>
					  </Attribute>
					  <Function>
						<TypeClass>Numeric</TypeClass>
						<FunctionType>Enum</FunctionType>
						<ReturnType>Numeric</ReturnType>
						<Count>2</Count>
						<Constant>
						  <TypeClass>String</TypeClass>
						  <ObjType>System.String</ObjType>
						  <Value>Microsoft.SqlServer.Management.Smo.AvailabilityReplicaRollupSynchronizationState</Value>
						</Constant>
						<Constant>
						  <TypeClass>String</TypeClass>
						  <ObjType>System.String</ObjType>
						  <Value>Synchronizing</Value>
						</Constant>
					  </Function>
					</Operator>
					<Operator>
					  <TypeClass>Bool</TypeClass>
					  <OpType>EQ</OpType>
					  <Count>2</Count>
					  <Attribute>
						<TypeClass>Numeric</TypeClass>
						<Name>RollupSynchronizationState</Name>
					  </Attribute>
					  <Function>
						<TypeClass>Numeric</TypeClass>
						<FunctionType>Enum</FunctionType>
						<ReturnType>Numeric</ReturnType>
						<Count>2</Count>
						<Constant>
						  <TypeClass>String</TypeClass>
						  <ObjType>System.String</ObjType>
						  <Value>Microsoft.SqlServer.Management.Smo.AvailabilityReplicaRollupSynchronizationState</Value>
						</Constant>
						<Constant>
						  <TypeClass>String</TypeClass>
						  <ObjType>System.String</ObjType>
						  <Value>Synchronized</Value>
						</Constant>
					  </Function>
					</Operator>
				  </Operator>
				</Group>
			  </Operator>
			</Group>
			<Operator>
			  <TypeClass>Bool</TypeClass>
			  <OpType>EQ</OpType>
			  <Count>2</Count>
			  <Attribute>
				<TypeClass>Numeric</TypeClass>
				<Name>RollupSynchronizationState</Name>
			  </Attribute>
			  <Function>
				<TypeClass>Numeric</TypeClass>
				<FunctionType>Enum</FunctionType>
				<ReturnType>Numeric</ReturnType>
				<Count>2</Count>
				<Constant>
				  <TypeClass>String</TypeClass>
				  <ObjType>System.String</ObjType>
				  <Value>Microsoft.SqlServer.Management.Smo.AvailabilityReplicaRollupSynchronizationState</Value>
				</Constant>
				<Constant>
				  <TypeClass>String</TypeClass>
				  <ObjType>System.String</ObjType>
				  <Value>Synchronized</Value>
				</Constant>
			  </Function>
			</Operator>
		  </Operator>
	</Group>'
	IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'', 
			@facet=N'AvailabilityReplica', @expression=@expression, @is_name_condition=0, @obj_name=N''
	END
	ELSE
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'', 
			@facet=N'AvailabilityReplica', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
	END
	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1   
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: AlwaysOnArRoleHealthCondition 
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Condition: AlwaysOnArRoleHealthCondition...'
	set @condition_name = N'AlwaysOnArRoleHealthCondition'
	set @expression = N'<Operator>
		  <TypeClass>Bool</TypeClass>
		  <OpType>OR</OpType>
		  <Count>2</Count>
		  <Operator>
			<TypeClass>Bool</TypeClass>
			<OpType>EQ</OpType>
			<Count>2</Count>
			<Attribute>
			  <TypeClass>Numeric</TypeClass>
			  <Name>Role</Name>
			</Attribute>
			<Function>
			  <TypeClass>Numeric</TypeClass>
			  <FunctionType>Enum</FunctionType>
			  <ReturnType>Numeric</ReturnType>
			  <Count>2</Count>
			  <Constant>
				<TypeClass>String</TypeClass>
				<ObjType>System.String</ObjType>
				<Value>Microsoft.SqlServer.Management.Smo.AvailabilityReplicaRole</Value>
			  </Constant>
			  <Constant>
				<TypeClass>String</TypeClass>
				<ObjType>System.String</ObjType>
				<Value>Primary</Value>
			  </Constant>
			</Function>
		  </Operator>
		  <Operator>
			<TypeClass>Bool</TypeClass>
			<OpType>EQ</OpType>
			<Count>2</Count>
			<Attribute>
			  <TypeClass>Numeric</TypeClass>
			  <Name>Role</Name>
			</Attribute>
			<Function>
			  <TypeClass>Numeric</TypeClass>
			  <FunctionType>Enum</FunctionType>
			  <ReturnType>Numeric</ReturnType>
			  <Count>2</Count>
			  <Constant>
				<TypeClass>String</TypeClass>
				<ObjType>System.String</ObjType>
				<Value>Microsoft.SqlServer.Management.Smo.AvailabilityReplicaRole</Value>
			  </Constant>
			  <Constant>
				<TypeClass>String</TypeClass>
				<ObjType>System.String</ObjType>
				<Value>Secondary</Value>
			  </Constant>
			</Function>
		  </Operator>
	</Operator>'
	IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'', 
			@facet=N'AvailabilityReplica', @expression=@expression, @is_name_condition=0, @obj_name=N''
	END
	ELSE
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'', 
			@facet=N'AvailabilityReplica', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
	END
	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: AlwaysOnArJoinStateHealthCondition 
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Condition: AlwaysOnArJoinStateHealthCondition'
    set @condition_name = N'AlwaysOnArJoinStateHealthCondition'
	set @expression = N'<Operator>
          <TypeClass>Bool</TypeClass>
          <OpType>NE</OpType>
          <Count>2</Count>
          <Attribute>
            <TypeClass>Numeric</TypeClass>
            <Name>JoinState</Name>
          </Attribute>
          <Function>
            <TypeClass>Numeric</TypeClass>
            <FunctionType>Enum</FunctionType>
            <ReturnType>Numeric</ReturnType>
            <Count>2</Count>
            <Constant>
              <TypeClass>String</TypeClass>
              <ObjType>System.String</ObjType>
              <Value>Microsoft.SqlServer.Management.Smo.AvailabilityReplicaJoinState</Value>
            </Constant>
            <Constant>
              <TypeClass>String</TypeClass>
              <ObjType>System.String</ObjType>
              <Value>NotJoined</Value>
            </Constant>
          </Function>
    </Operator>'
	IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'', 
			@facet=N'AvailabilityReplica', @expression=@expression, @is_name_condition=0, @obj_name=N''
	END
	ELSE
	BEGIN
		EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'', 
			@facet=N'AvailabilityReplica', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
	END
	EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Policy : AlwaysOnAgReplicasConnectionHealthPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------       
	PRINT ''
	PRINT 'Creating Policy: AlwaysOnAgReplicasConnectionHealthPolicy...'
	select @alwayson_policy_helptext = @alwayson_policy_helptext_prefix + '41413'
	select @alwayson_policy_description = @alwayson_policy_description_prefix + '41414'

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'AlwaysOnAgReplicasConnectionHealthPolicy_ObjectSet', @facet=N'IAvailabilityGroupState', @object_set_id=0

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnAgReplicasConnectionHealthPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'AlwaysOnAgReplicasConnectionHealthPolicy_ObjectSet', @type_skeleton=N'Server/AvailabilityGroup', @type=N'AVAILABILITYGROUP', @enabled=True, @target_set_id=@alwayson_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup', @level_name=N'AvailabilityGroup', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'AlwaysOnAgReplicasConnectionHealthPolicy', @condition_name=N'AlwaysOnAgReplicasConnectionHealthCondition', @policy_category=N'Availability group warnings (primary replica only)', 
    @description=@alwayson_policy_description, 
    @help_text=@alwayson_policy_helptext, @help_link=N'swb.agdashboard.agp7allconnected.issues.f1', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=0, @root_condition_name=N'IsHadrEnabled', @object_set=N'AlwaysOnAgReplicasConnectionHealthPolicy_ObjectSet'
   
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnAgReplicasConnectionHealthPolicy', @marker=1 
  
   -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Policy : AlwaysOnAgReplicasDataSynchronizationHealthPolicy
   -------------------------------------------------------------------------------------------------------------------------------------------------------------
   	PRINT ''
	PRINT 'Creating Policy: AlwaysOnAgReplicasDataSynchronizationHealthPolicy...'
	select @alwayson_policy_helptext = @alwayson_policy_helptext_prefix + '41407'
	select @alwayson_policy_description = @alwayson_policy_description_prefix + '41408'

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'AlwaysOnAgReplicasDataSynchronizationHealthPolicy_ObjectSet', @facet=N'IAvailabilityGroupState', @object_set_id=0

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnAgReplicasDataSynchronizationHealthPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'AlwaysOnAgReplicasDataSynchronizationHealthPolicy_ObjectSet', @type_skeleton=N'Server/AvailabilityGroup', @type=N'AVAILABILITYGROUP', @enabled=True, @target_set_id=@alwayson_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup', @level_name=N'AvailabilityGroup', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'AlwaysOnAgReplicasDataSynchronizationHealthPolicy', @condition_name=N'AlwaysOnAgReplicasDataSynchronizationHealthCondition', @policy_category=N'Availability group warnings (primary replica only)', 
    @description=@alwayson_policy_description, 
    @help_text=@alwayson_policy_helptext, @help_link=N'swb.agdashboard.agp4synchronizing.issues.f1', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=0, @root_condition_name=N'IsHadrEnabled', @object_set=N'AlwaysOnAgReplicasDataSynchronizationHealthPolicy_ObjectSet'

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnAgReplicasDataSynchronizationHealthPolicy', @marker=1  

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Policy : AlwaysOnAgReplicasRoleHealthPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------    
	PRINT ''
	PRINT 'Creating Policy: AlwaysOnAgReplicasRoleHealthPolicy...'
	select @alwayson_policy_helptext = @alwayson_policy_helptext_prefix + '41411'
	select @alwayson_policy_description = @alwayson_policy_description_prefix + '41412'

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'AlwaysOnAgReplicasRoleHealthPolicy_ObjectSet', @facet=N'IAvailabilityGroupState', @object_set_id=0
    
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnAgReplicasRoleHealthPolicy_ObjectSet', @marker=1 

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'AlwaysOnAgReplicasRoleHealthPolicy_ObjectSet', @type_skeleton=N'Server/AvailabilityGroup', @type=N'AVAILABILITYGROUP', @enabled=True, @target_set_id=@alwayson_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup', @level_name=N'AvailabilityGroup', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'AlwaysOnAgReplicasRoleHealthPolicy', @condition_name=N'AlwaysOnAgReplicasRoleHealthCondition', @policy_category=N'Availability group warnings (primary replica only)', 
    @description=@alwayson_policy_description, 
    @help_text=@alwayson_policy_helptext, @help_link=N'swb.agdashboard.agp6allroleshealthy.issues.f1', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=0, @root_condition_name=N'IsHadrEnabled', @object_set=N'AlwaysOnAgReplicasRoleHealthPolicy_ObjectSet'
   
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnAgReplicasRoleHealthPolicy', @marker=1  
     
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Policy : AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Policy: AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy...'
	select @alwayson_policy_helptext = @alwayson_policy_helptext_prefix + '41409'
	select @alwayson_policy_description = @alwayson_policy_description_prefix + '41410'

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy_ObjectSet', @facet=N'IAvailabilityGroupState', @object_set_id=0

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy_ObjectSet', @marker=1 

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy_ObjectSet', @type_skeleton=N'Server/AvailabilityGroup', @type=N'AVAILABILITYGROUP', @enabled=True, @target_set_id=@alwayson_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup', @level_name=N'AvailabilityGroup', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy', @condition_name=N'AlwaysOnAgSynchronousReplicasDataSynchronizationHealthCondition', @policy_category=N'Availability group warnings (primary replica only)', 
    @description=@alwayson_policy_description, 
    @help_text=@alwayson_policy_helptext, @help_link=N'swb.agdashboard.agp5synchronized.issues.f1', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=0, @root_condition_name=N'IsHadrEnabled', @object_set=N'AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy_ObjectSet'
    
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnAgSynchronousReplicasDataSynchronizationHealthPolicy', @marker=1  

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Policy : AlwaysOnDbrJoinStatePolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Policy: AlwaysOnDbrJoinStatePolicy...'
	select @alwayson_policy_helptext = @alwayson_policy_helptext_prefix + '41423'
	select @alwayson_policy_description = @alwayson_policy_description_prefix + '41424'

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'AlwaysOnDbrJoinStatePolicy_ObjectSet', @facet=N'DatabaseReplicaState', @object_set_id=0
     
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnDbrJoinStatePolicy_ObjectSet', @marker=1  

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'AlwaysOnDbrJoinStatePolicy_ObjectSet', @type_skeleton=N'Server/AvailabilityGroup/DatabaseReplicaState', @type=N'DATABASEREPLICASTATE', @enabled=True, @target_set_id=@alwayson_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup/DatabaseReplicaState', @level_name=N'DatabaseReplicaState', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup', @level_name=N'AvailabilityGroup', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'AlwaysOnDbrJoinStatePolicy', @condition_name=N'AlwaysOnDbrJoinStateCondition', @policy_category=N'Availability database warnings', 
    @description=@alwayson_policy_description, 
    @help_text=@alwayson_policy_helptext, @help_link=N'swb.agdashboard.drp2joined.issues.f1', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=0, @root_condition_name=N'IsHadrEnabled', @object_set=N'AlwaysOnDbrJoinStatePolicy_ObjectSet'
    
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnDbrJoinStatePolicy', @marker=1  

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    --Policy : AlwaysOnDbrSuspendStatePolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Policy: AlwaysOnDbrSuspendStatePolicy...'
	select @alwayson_policy_helptext = @alwayson_policy_helptext_prefix + '41421'
	select @alwayson_policy_description = @alwayson_policy_description_prefix + '41422'

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'AlwaysOnDbrSuspendStatePolicy_ObjectSet', @facet=N'DatabaseReplicaState', @object_set_id=0

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnDbrSuspendStatePolicy_ObjectSet', @marker=1 

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'AlwaysOnDbrSuspendStatePolicy_ObjectSet', @type_skeleton=N'Server/AvailabilityGroup/DatabaseReplicaState', @type=N'DATABASEREPLICASTATE', @enabled=True, @target_set_id=@alwayson_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup/DatabaseReplicaState', @level_name=N'DatabaseReplicaState', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup', @level_name=N'AvailabilityGroup', @condition_name=N'', @target_set_level_id=0
    
    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'AlwaysOnDbrSuspendStatePolicy', @condition_name=N'AlwaysOnDbrSuspendStateCondition', @policy_category=N'Availability database warnings', 
    @description=@alwayson_policy_description, 
    @help_text=@alwayson_policy_helptext, @help_link=N'swb.agdashboard.drp1notsuspended.issues.f1', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=0, @root_condition_name=N'IsHadrEnabled', @object_set=N'AlwaysOnDbrSuspendStatePolicy_ObjectSet'

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnDbrSuspendStatePolicy', @marker=1 
    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    --Policy : AlwaysOnAgOnlineStateHealthPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Policy: AlwaysOnAgOnlineStateHealthPolicy...'
	select @alwayson_policy_helptext = @alwayson_policy_helptext_prefix + '41403'
	select @alwayson_policy_description = @alwayson_policy_description_prefix + '41404'

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'AlwaysOnAgOnlineStateHealthPolicy_ObjectSet', @facet=N'IAvailabilityGroupState', @object_set_id=0

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnAgOnlineStateHealthPolicy_ObjectSet', @marker=1 

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'AlwaysOnAgOnlineStateHealthPolicy_ObjectSet', @type_skeleton=N'Server/AvailabilityGroup', @type=N'AVAILABILITYGROUP', @enabled=True, @target_set_id=@alwayson_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup', @level_name=N'AvailabilityGroup', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'AlwaysOnAgOnlineStateHealthPolicy', @condition_name=N'AlwaysOnAgOnlineStateHealthCondition', @policy_category=N'Availability group errors (any replica role)', 
    @description=@alwayson_policy_description, 
    @help_text=@alwayson_policy_helptext, @help_link=N'swb.agdashboard.agp2online.issues.f1', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=0, @root_condition_name=N'IsHadrEnabled', @object_set=N'AlwaysOnAgOnlineStateHealthPolicy_ObjectSet'

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnAgOnlineStateHealthPolicy', @marker=1 
    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    --Policy : AlwaysOnAgAutomaticFailoverHealthPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Policy: AlwaysOnAgAutomaticFailoverHealthPolicy...'
	select @alwayson_policy_helptext = @alwayson_policy_helptext_prefix + '41405'
	select @alwayson_policy_description = @alwayson_policy_description_prefix + '41406'

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'AlwaysOnAgAutomaticFailoverHealthPolicy_ObjectSet', @facet=N'IAvailabilityGroupState', @object_set_id=0

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnAgAutomaticFailoverHealthPolicy_ObjectSet', @marker=1 

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'AlwaysOnAgAutomaticFailoverHealthPolicy_ObjectSet', @type_skeleton=N'Server/AvailabilityGroup', @type=N'AVAILABILITYGROUP', @enabled=True, @target_set_id=@alwayson_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup', @level_name=N'AvailabilityGroup', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'AlwaysOnAgAutomaticFailoverHealthPolicy', @condition_name=N'AlwaysOnAgAutomaticFailoverHealthCondition', @policy_category=N'Availability group errors (primary replica only)', 
    @description=@alwayson_policy_description, 
    @help_text=@alwayson_policy_helptext, @help_link=N'swb.agdashboard.agp3autofailover.issues.f1', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=0, @root_condition_name=N'IsHadrEnabled', @object_set=N'AlwaysOnAgAutomaticFailoverHealthPolicy_ObjectSet'

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnAgAutomaticFailoverHealthPolicy', @marker=1 
    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    --Policy : AlwaysOnArConnectionHealthPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Policy: AlwaysOnArConnectionHealthPolicy...'
	select @alwayson_policy_helptext = @alwayson_policy_helptext_prefix + '41417'
	select @alwayson_policy_description = @alwayson_policy_description_prefix + '41418'

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'AlwaysOnArConnectionHealthPolicy_ObjectSet', @facet=N'AvailabilityReplica', @object_set_id=0
    
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnArConnectionHealthPolicy_ObjectSet', @marker=1 

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'AlwaysOnArConnectionHealthPolicy_ObjectSet', @type_skeleton=N'Server/AvailabilityGroup/AvailabilityReplica', @type=N'AVAILABILITYREPLICA', @enabled=True, @target_set_id=@alwayson_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup/AvailabilityReplica', @level_name=N'AvailabilityReplica', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup', @level_name=N'AvailabilityGroup', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'AlwaysOnArConnectionHealthPolicy', @condition_name=N'AlwaysOnArConnectionHealthCondition', @policy_category=N'Availability replica errors', 
    @description=@alwayson_policy_description, 
    @help_text=@alwayson_policy_helptext, @help_link=N'swb.agdashboard.arp2connected.issues.f1', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=0, @root_condition_name=N'IsHadrEnabled', @object_set=N'AlwaysOnArConnectionHealthPolicy_ObjectSet'

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnArConnectionHealthPolicy', @marker=1 
    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    --Policy : AlwaysOnDbrDataSynchronizationState
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Policy: AlwaysOnDbrDataSynchronizationState...'
	select @alwayson_policy_helptext = @alwayson_policy_helptext_prefix + '41425'
	select @alwayson_policy_description = @alwayson_policy_description_prefix + '41426'

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'AlwaysOnDbrDataSynchronizationState_ObjectSet', @facet=N'DatabaseReplicaState', @object_set_id=0
    
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnDbrDataSynchronizationState_ObjectSet', @marker=1 

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'AlwaysOnDbrDataSynchronizationState_ObjectSet', @type_skeleton=N'Server/AvailabilityGroup/DatabaseReplicaState', @type=N'DATABASEREPLICASTATE', @enabled=True, @target_set_id=@alwayson_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup/DatabaseReplicaState', @level_name=N'DatabaseReplicaState', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup', @level_name=N'AvailabilityGroup', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'AlwaysOnDbrDataSynchronizationState', @condition_name=N'AlwaysOnDbrDataSynchronizationCondition', @policy_category=N'Availability database warnings', 
    @description=@alwayson_policy_description, 
    @help_text=@alwayson_policy_helptext, @help_link=N'swb.agdashboard.drp3datasynchealthy.issues.f1', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=0, @root_condition_name=N'IsHadrEnabled', @object_set=N'AlwaysOnDbrDataSynchronizationState_ObjectSet'
    
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnDbrDataSynchronizationState', @marker=1 
    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    --Policy : AlwaysOnArDataSynchronizationHealthPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Policy: AlwaysOnArDataSynchronizationHealthPolicy...'
	select @alwayson_policy_helptext = @alwayson_policy_helptext_prefix + '41419'
	select @alwayson_policy_description = @alwayson_policy_description_prefix + '41420'

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'AlwaysOnArDataSynchronizationHealthPolicy_ObjectSet', @facet=N'AvailabilityReplica', @object_set_id=0
    
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnArDataSynchronizationHealthPolicy_ObjectSet', @marker=1 

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'AlwaysOnArDataSynchronizationHealthPolicy_ObjectSet', @type_skeleton=N'Server/AvailabilityGroup/AvailabilityReplica', @type=N'AVAILABILITYREPLICA', @enabled=True, @target_set_id=@alwayson_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup/AvailabilityReplica', @level_name=N'AvailabilityReplica', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup', @level_name=N'AvailabilityGroup', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'AlwaysOnArDataSynchronizationHealthPolicy', @condition_name=N'AlwaysOnArDataSynchronizationHealthCondition', @policy_category=N'Availability replica warnings', 
    @description=@alwayson_policy_description, 
    @help_text=@alwayson_policy_helptext, @help_link=N'swb.agdashboard.arp3datasynchealthy.issues.f1', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=0, @root_condition_name=N'IsHadrEnabled', @object_set=N'AlwaysOnArDataSynchronizationHealthPolicy_ObjectSet'

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnArDataSynchronizationHealthPolicy', @marker=1 
    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    --Policy : AlwaysOnArRoleHealthPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Policy: AlwaysOnArRoleHealthPolicy...'
	select @alwayson_policy_helptext = @alwayson_policy_helptext_prefix + '41415'
	select @alwayson_policy_description = @alwayson_policy_description_prefix + '41416'

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'AlwaysOnArRoleHealthPolicy_ObjectSet', @facet=N'AvailabilityReplica', @object_set_id=0

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnArRoleHealthPolicy_ObjectSet', @marker=1 

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'AlwaysOnArRoleHealthPolicy_ObjectSet', @type_skeleton=N'Server/AvailabilityGroup/AvailabilityReplica', @type=N'AVAILABILITYREPLICA', @enabled=True, @target_set_id=@alwayson_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup/AvailabilityReplica', @level_name=N'AvailabilityReplica', @condition_name=N'', @target_set_level_id=0
    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@alwayson_target_set_id, @type_skeleton=N'Server/AvailabilityGroup', @level_name=N'AvailabilityGroup', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'AlwaysOnArRoleHealthPolicy', @condition_name=N'AlwaysOnArRoleHealthCondition', @policy_category=N'Availability replica errors', 
    @description=@alwayson_policy_description, 
    @help_text=@alwayson_policy_helptext, @help_link=N'swb.agdashboard.arp1rolehealthy.issues.f1', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=0, @root_condition_name=N'IsHadrEnabled', @object_set=N'AlwaysOnArRoleHealthPolicy_ObjectSet'
    
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnArRoleHealthPolicy', @marker=1 
    
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    --Policy : AlwaysOnAgWSFClusterHealthPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Policy: AlwaysOnAgWSFClusterHealthPolicy...'
	select @alwayson_policy_helptext = @alwayson_policy_helptext_prefix + '41401'
	select @alwayson_policy_description = @alwayson_policy_description_prefix + '41402'
    
	EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'AlwaysOnAgWSFClusterHealthPolicy_ObjectSet', @facet=N'Server', @object_set_id=0
    
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'AlwaysOnAgWSFClusterHealthPolicy_ObjectSet', @marker=1 

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'AlwaysOnAgWSFClusterHealthPolicy_ObjectSet', @type_skeleton=N'Server', @type=N'SERVER', @enabled=True, @target_set_id=@alwayson_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'AlwaysOnAgWSFClusterHealthPolicy', @condition_name=N'AlwaysOnAgWSFClusterHealthCondition', @policy_category=N'Availability group errors (any replica role)', 
    @description=@alwayson_policy_description, 
    @help_text=@alwayson_policy_helptext, @help_link=N'swb.agdashboard.agp1WSFCquorum.issues.f1', @schedule_uid=N'00000000-0000-0000-0000-000000000000', @execution_mode=0, @is_enabled=False, @policy_id=0, @root_condition_name=N'IsHadrEnabled', @object_set=N'AlwaysOnAgWSFClusterHealthPolicy_ObjectSet'
    
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'AlwaysOnAgWSFClusterHealthPolicy', @marker=1 
  
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    --Policy : AlwaysOnArJoinStateHealthPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
	PRINT ''
	PRINT 'Creating Policy: AlwaysOnArJoinStateHealthPolicy...'
	select @alwayson_policy_helptext = @alwayson_policy_helptext_prefix + '41427'
	select @alwayson_policy_description = @alwayson_policy_description_prefix + '41428'
    
	EXEC msdb.dbo.sp_syspolicy_add_object_set 
        @object_set_name=N'AlwaysOnArJoinStateHealthPolicy_ObjectSet', 
        @facet=N'AvailabilityReplica', 
        @object_set_id=0
    
    EXEC msdb.dbo.sp_syspolicy_mark_system 
        @type=N'OBJECTSET', 
        @name=N'AlwaysOnArJoinStateHealthPolicy_ObjectSet', 
        @marker=1 

    EXEC msdb.dbo.sp_syspolicy_add_target_set 
        @object_set_name=N'AlwaysOnArJoinStateHealthPolicy_ObjectSet', 
        @type_skeleton=N'Server/AvailabilityGroup/AvailabilityReplica', 
        @type=N'AVAILABILITYREPLICA', 
        @enabled=True, 
        @target_set_id=@alwayson_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level 
        @target_set_id=@alwayson_target_set_id, 
        @type_skeleton=N'Server/AvailabilityGroup/AvailabilityReplica', 
        @level_name=N'AvailabilityReplica', 
        @condition_name=N'', 
        @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level 
        @target_set_id=@alwayson_target_set_id, 
        @type_skeleton=N'Server/AvailabilityGroup', 
        @level_name=N'AvailabilityGroup', 
        @condition_name=N'', 
        @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy 
        @name=N'AlwaysOnArJoinStateHealthPolicy', 
        @condition_name=N'AlwaysOnArJoinStateHealthCondition', 
        @policy_category=N'Availability replica warnings', 
        @description=@alwayson_policy_description, 
        @help_text=@alwayson_policy_helptext, 
        @help_link=N'sql13.swb.agdashboard.arp4joinstate.issues.f1', 
        @schedule_uid=N'00000000-0000-0000-0000-000000000000', 
        @execution_mode=0, 
        @is_enabled=False, 
        @policy_id=0, 
        @root_condition_name=N'IsHadrEnabled', 
        @object_set=N'AlwaysOnArJoinStateHealthPolicy_ObjectSet'
        
    EXEC msdb.dbo.sp_syspolicy_mark_system 
        @type=N'POLICY', 
        @name=N'AlwaysOnArJoinStateHealthPolicy', 
        @marker=1 

End

DROP PROCEDURE #sp_syspolicy_cascade_delete_policy 
go

RAISERROR('Creating table [dm_hadr_automatic_seeding_history]', 0, 1)  WITH NOWAIT;

IF OBJECT_ID ( N'[msdb].[dbo].[dm_hadr_automatic_seeding_history]', N'U' ) IS NULL
	CREATE TABLE [msdb].[dbo].[dm_hadr_automatic_seeding_history] (
		start_time datetime not null,
		completion_time datetime,
		ag_id uniqueidentifier not null,
		ag_db_id uniqueidentifier not null,
		ag_remote_replica_id uniqueidentifier not null,
		operation_id uniqueidentifier not null,
		is_source bit not null,
		current_state nvarchar(4000) collate catalog_default not null,
		performed_seeding bit not null,
		failure_state int,
		failure_state_desc nvarchar(4000) collate catalog_default,
		error_code int,
		number_of_attempts int not null
	)
go

GRANT SELECT ON [msdb].[dbo].[dm_hadr_automatic_seeding_history] TO PUBLIC
go

/**************************************************************/
/* SP_DELETE_DM_HADR_AUTOMATIC_SEEDING_HISTORY                */
/**************************************************************/

PRINT ''
PRINT 'Creating procedure sp_delete_dm_hadr_automatic_seeding_history...'
go
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysobjects
            WHERE (name = N'sp_delete_dm_hadr_automatic_seeding_history')
              AND (type = 'P')))
  DROP PROCEDURE sp_delete_dm_hadr_automatic_seeding_history
go
CREATE   PROCEDURE sp_delete_dm_hadr_automatic_seeding_history
  @oldest_date datetime
AS
BEGIN
  SET NOCOUNT ON

  DELETE FROM [msdb].[dbo].[dm_hadr_automatic_seeding_history]
  WHERE completion_time < @oldest_date
END
go
USE msdb;
GO

SET QUOTED_IDENTIFIER ON
GO
PRINT 'Creating Managed Backup objects...'

-- Schemas for managed backup
IF NOT EXISTS(SELECT * FROM sys.schemas WHERE name = 'managed_backup')
BEGIN
	PRINT 'Creating schema managed_backup...'
	EXEC ('CREATE SCHEMA managed_backup')
END
GO

IF NOT EXISTS(SELECT * FROM sys.schemas WHERE name = 'smart_admin')
BEGIN
	PRINT 'Creating schema smart_admin...'
	EXEC ('CREATE SCHEMA smart_admin')
END
GO

-----------------------------------------------------------------
-- autoadmin_task_agents table
--
-- This table contains the list of task agent DLLs that are loaded by
-- the auto-admin manager when it is loaded as a SQL Agent subsystem.
--

IF OBJECT_ID ('autoadmin_task_agents','U') IS NULL
BEGIN
	PRINT 'Creating table autoadmin_task_agents...'
	CREATE TABLE autoadmin_task_agents (
				Id					INT PRIMARY KEY IDENTITY, 
				task_assembly_name	VARCHAR(255) NOT NULL, 
				task_assembly_path	VARCHAR(MAX) NOT NULL, 
				className			VARCHAR(MAX) NOT NULL)
END
ELSE
BEGIN
	DELETE 
	FROM autoadmin_task_agents
END

PRINT 'Adding task agent Microsoft.SqlAutoAdmin.AutoBackupAgent.dll...'
INSERT INTO autoadmin_task_agents (
	task_assembly_name, 
	task_assembly_path, 
	className
	)
VALUES (
	'Microsoft.SqlAutoAdmin.AutoBackupAgent.dll', 
	'.', 
	'Microsoft.SqlServer.SmartAdmin.SmartBackupAgent.SmartBackup'
	)

GO

----------------------------------------------------------------------------------------------------
-- autoadmin_update_task_agent_path
--
-- This procedure updates autoadmin_task_agents.task_assembly_path with the specified path.
--
IF OBJECT_ID ('autoadmin_update_task_agent_path', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure autoadmin_update_task_agent_path...'
	DROP PROCEDURE autoadmin_update_task_agent_path;
END 
GO

PRINT 'Creating procedure autoadmin_update_task_agent_path...'
GO
CREATE PROCEDURE autoadmin_update_task_agent_path
        @assembly_name		VARCHAR(255),
        @new_assembly_path	VARCHAR(MAX)
AS
BEGIN
    UPDATE autoadmin_task_agents 
	SET task_assembly_path = @new_assembly_path
    WHERE autoadmin_task_agents.task_assembly_name = @assembly_name
END
GO

----------------------------------------------------------------------------------------------------
--                              Metadata service table definitions
-- 
-- The service maintains two tables: one with per-DB state one with task-agent per-DB state.
-- The relationship is one-to-many i.e. for each DB multiple task agents may have per-DB state.
-- A single task agent can only have one record of per-DB state.
-- All access to these tables is through the set of stored procedures in MetaDataQueries.sql.
--
-------------------------------------------------------------------------------------------------


-------------------------------------------------------------------------------------------------
-- autoadmin_managed_databases table
--
-- This table maintain per-DB state. It contains an composite foreign key into 
-- sys.databases.name, sys.databases.database_id, and sys.database_recovery_status.database_guid.
-- These 3 columns are used to uniquely identify a database.
-- The autoadmin_id identity column is a foreign key into autoadmin_task_agent_metadata.
-- It starts at 1 because zero is reserved to identify a task agent's global metadata in autoadmin_task_agent_metadata. 
-- drop_date is NULL when a database is first detected. When a database is later found to be
-- missing from sys.databases drop_date is set to the time of the detection.
--

IF OBJECT_ID ('autoadmin_managed_databases','U') IS NULL
BEGIN
	PRINT 'Creating table autoadmin_managed_databases...'
	CREATE TABLE autoadmin_managed_databases(
        autoadmin_id            BIGINT IDENTITY(1, 1)	NOT NULL UNIQUE NONCLUSTERED,
        db_name                 NVARCHAR(128)			NOT NULL, -- Must be NVARCHAR(128) or SYSNAME to avoid issues with ANSI_PADDING
        db_id                   INT						NOT NULL,
        db_guid                 UNIQUEIDENTIFIER		NOT NULL,
        group_db_guid           UNIQUEIDENTIFIER		NULL,
        drop_date               DATETIME				NULL
        CONSTRAINT autoadmin_managed_databases_pk PRIMARY KEY CLUSTERED(db_name, db_id, db_guid, autoadmin_id)
)
END
ELSE
BEGIN
    IF NOT EXISTS (SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE 
                   WHERE CONSTRAINT_NAME = 'autoadmin_managed_databases_pk'
                   AND COLUMN_NAME = 'autoadmin_id')
    BEGIN
        PRINT 'Changing primary key on autoadmin_managed_databases'
        ALTER TABLE autoadmin_managed_databases DROP CONSTRAINT autoadmin_managed_databases_pk;
        ALTER TABLE autoadmin_managed_databases ADD CONSTRAINT autoadmin_managed_databases_pk PRIMARY KEY CLUSTERED(db_name, db_id, db_guid, autoadmin_id);
    END
END
GO

-----------------------------------------------------------------------------------------------------------
-- autoadmin_task_agent_metadata table
--
-- This table maintain task agent per-DB state. autoadmin_id is a foreign key into autoadmin_managed_databases.
-- Task agents are identified by task_agent_guid which must be unique per-DB (i.e. a task agent may only store
-- one record for each record in autoadmin_managed_databases). The task agent metadata is stored as XML.
-- An autoadmin_id value of zero is reserved to represent a task agent's global data.
-- In future if the metadata service is enhanced to manage other per-object metadata (e.g. for indexes)
-- a new object metadata table will be needed (e.g. autoadmin_managed_indexes). This table can be expanded for use 
-- with new object types by adding an object type column.
--

IF OBJECT_ID ('autoadmin_task_agent_metadata','U') IS NULL
BEGIN
	PRINT 'Creating table autoadmin_task_agent_metadata...'
	CREATE TABLE autoadmin_task_agent_metadata(
            task_agent_guid         UNIQUEIDENTIFIER NOT NULL,
            autoadmin_id            BIGINT NOT NULL,
            last_modified           DATETIME NOT NULL,
            task_agent_data         XML NULL,
		    schema_version          INT  DEFAULT 2 NOT NULL,  
            CONSTRAINT autoadmin_task_agent_metadata_pk PRIMARY KEY CLUSTERED(task_agent_guid, autoadmin_id)
            )
END
ELSE
BEGIN
    IF NOT EXISTS (
        SELECT name from msdb.dbo.syscolumns 
        WHERE name='schema_version' 
        AND id =  OBJECT_ID('autoadmin_task_agent_metadata')
    )
	BEGIN
		PRINT 'Altering table autoadmin_task_agent_metadata, adding column schema_version...'
	    -- during upgrade from SQL 2014, add a new column and set the default_schema version value as 1
		ALTER TABLE autoadmin_task_agent_metadata 
            ADD schema_version INT  DEFAULT 1 NOT NULL
	END
END
GO

-----------------------------------------------------------------
-- Retention management helper table
--
-- This table contains the list of backups that made by smart backup.
--
IF OBJECT_ID ('smart_backup_files','U') IS NULL
BEGIN
	PRINT 'Creating table smart_backup_files...'
	CREATE TABLE smart_backup_files
	(
		backup_path				NVARCHAR(260) COLLATE Latin1_General_CI_AS_KS_WS PRIMARY KEY,
		last_modified_utc		DATETIME,
		backup_type				SMALLINT,
		expiration_date			DATETIME,
		user_name				NVARCHAR(128) NULL,
		server_name				NVARCHAR(128) NULL,
		database_name			NVARCHAR(128),
		backup_size				NUMERIC(20, 0),
		first_lsn				NUMERIC(25, 0), 
		last_lsn				NUMERIC(25, 0), 
		database_backup_lsn		NUMERIC(25, 0),
		backup_start_date		DATETIME,
		backup_finish_date		DATETIME,
		machine_name			NVARCHAR(128) NULL,
		last_recovery_fork_id	UNIQUEIDENTIFIER, --last_recovery_fork_id in backupset
		first_recovery_fork_id	UNIQUEIDENTIFIER NULL,
		fork_point_lsn			NUMERIC(25, 0) NULL,
		availability_group_guid UNIQUEIDENTIFIER NULL, -- this is for Hadron
		database_guid			UNIQUEIDENTIFIER,
		status					CHAR -- 'A': available 'C' corrupted 'D' deleted 'U': Unknown (needs restore headeronly)
	)
END
--ELSE
--BEGIN
--  upgrade statements goes here. Below is an example:
--  IF NOT EXISTS (
--    select * from msdb.dbo.syscolumns where name=<new column name> and id =
--        (select id from msdb.dbo.sysobjects where name='smart_backup_files'))

--    ALTER TABLE smart_backup_files ADD <new column name> <new column type>
--END
GO

-----------------------------------------------------------------------------------------------------------
-- fn_autoadmin_schema_version
--
-- This function is effectively a constant that returns a number representing the version of the schema of
-- the tables described in this file. The metadata service will call this function through a stored procedure
-- to determine whether it is compatible with the schema.
--
IF OBJECT_ID ('fn_autoadmin_schema_version','FN') IS NOT NULL
BEGIN 
	PRINT 'Dropping function fn_autoadmin_schema_version...'
	DROP FUNCTION fn_autoadmin_schema_version;
END 
GO

PRINT 'Creating function fn_autoadmin_schema_version...'
GO

CREATE FUNCTION fn_autoadmin_schema_version() 
RETURNS INT 
AS 
BEGIN 
    -- schema version 2 is latest supported version
    RETURN 2    
END 
GO 

-----------------------------------------------------------------------------------------------------------
-- fn_autoadmin_schema_version
--
-- This function is effectively a constant that returns a number representing the version of the minimum schema
-- supported by curernt version of managed backup
--
IF OBJECT_ID ('fn_autoadmin_min_schema_version','FN') IS NOT NULL
BEGIN 
	PRINT 'Dropping function fn_autoadmin_min_schema_version...'
	DROP FUNCTION fn_autoadmin_min_schema_version;
END 
GO

PRINT 'Creating function fn_autoadmin_min_schema_version...'
GO

CREATE FUNCTION fn_autoadmin_min_schema_version() 
RETURNS INT 
AS 
BEGIN
    -- schema version 1 is minimum supported version 
    RETURN 1
END 
GO 

----------------------------------------------------------------------------------------------------
--                              System Flags service table definitions
--
----------------------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------------------
-- smart backup system flags table
--
-- This table contains key value pairs for various system flags that is used internally by  smart 
-- backup.
--

IF OBJECT_ID ('autoadmin_system_flags','U') IS NULL
BEGIN
	PRINT 'Creating table autoadmin_system_flags...'
	CREATE TABLE autoadmin_system_flags(
			name	NVARCHAR(128) NOT NULL,
			value	NVARCHAR(MAX) NOT NULL
	)
END
GO

----------------------------------------------------------------------------------------------------
--                              Miscellaneous
--
----------------------------------------------------------------------------------------------------

-- smart admin master switch table
--
-- Contains the present state of the master switch (1 = ON, 0 = OFF). 
-- If the table is empty, the state is 1 (ON).
--

IF OBJECT_ID ('autoadmin_master_switch','U') IS NULL
BEGIN
	PRINT 'Creating table autoadmin_master_switch...'
	CREATE TABLE autoadmin_master_switch(
			[state]	BIT NOT NULL
	)
END
GO

----------------------------------------------------------------------------------------------------
--                              Cross Version Configuration View For Telemetry
--
----------------------------------------------------------------------------------------------------
IF OBJECT_ID ('autoadmin_backup_configurations', 'V') IS NOT NULL
BEGIN
	PRINT 'Dropping view autoadmin_backup_configurations...'
	DROP VIEW autoadmin_backup_configurations
END
GO

PRINT 'Creating view autoadmin_backup_configurations...'
GO

----------------------------------------------------------------------------------------------------
--                              autoadmin_backup_configurations
--
-- Contains the current configuration for all databases as well as the system configuration.
-- This spans all managed backup versions to make telemetry collection simple
--
CREATE VIEW autoadmin_backup_configurations
AS
WITH XMLNAMESPACES (N'http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent' AS sb)
SELECT
aamd.db_id as db_id,
schema_version as ManagedBackupVersion,
CASE 
	WHEN aamd.group_db_guid IS NULL
	THEN CONVERT(BIT, 'false')
	ELSE CONVERT(BIT, 'true')
END as IsAlwaysOn,
CASE 
	WHEN aamd.drop_date IS NULL 
	THEN CONVERT(BIT, 'false')
	ELSE CONVERT(BIT, 'true')
END as IsDropped,
CONVERT(BIT, aatm.task_agent_data.value('(/*/autoBackupSetting)[1]', 'nvarchar(32)')) AS IsEnabled,
NULLIF(aatm.task_agent_data.value('(/*/retentionPeriod)[1]', 'int'), 0) AS RetentionPeriod,
NULLIF(aatm.task_agent_data.value('(/*/encryptionAlgorithm)[1]', 'nvarchar(128)'), '') AS EncryptionAlgorithm,
NULLIF(aatm.task_agent_data.value('(/*/schedulingOption)[1]', 'nvarchar(128)'), '') AS SchedulingOption,
NULLIF(aatm.task_agent_data.value('(/*/daysOfWeek)[1]', 'nvarchar(256)'), '') AS DayOfWeek
FROM
	autoadmin_managed_databases aamd 
	JOIN autoadmin_task_agent_metadata aatm ON aamd.autoadmin_id = aatm.autoadmin_id
WHERE
	aatm.task_agent_guid = '6DF5825B-945C-4081-A4E3-292556E99B99'
	AND aamd.autoadmin_id <> 0
UNION ALL
SELECT
NULL AS db_id,
schema_version AS ManagedBackupVersion,
NULL AS IsAlwaysOn,
NULL AS IsDropped,
CONVERT(BIT, aatm.task_agent_data.value('(/*/sb:defaultAutoBackupSetting)[1]', 'nvarchar(32)')) AS IsEnabled,
NULLIF(aatm.task_agent_data.value('(/*/sb:defaultRetentionPeriod)[1]', 'int'), 0) AS RetentionPeriod,
NULLIF(aatm.task_agent_data.value('(/*/sb:defaultEncryptionAlgorithm)[1]', 'nvarchar(128)'), '') AS EncryptionAlgorithm,
NULLIF(aatm.task_agent_data.value('(/*/sb:defaultSchedulingOption)[1]', 'nvarchar(128)'), '') AS SchedulingOption,
NULLIF(aatm.task_agent_data.value('(/*/sb:defaultDaysOfWeek)[1]', 'nvarchar(256)'), '') AS DayOfWeek
FROM autoadmin_task_agent_metadata aatm
WHERE
	task_agent_guid = '6DF5825B-945C-4081-A4E3-292556E99B99'
	AND autoadmin_id = 0
GO


IF OBJECT_ID ('autoadmin_backup_configuration_summary', 'V') IS NOT NULL
BEGIN
	PRINT 'Dropping view autoadmin_backup_configuration_summary...'
	DROP VIEW autoadmin_backup_configuration_summary
END
GO

PRINT 'Creating view autoadmin_backup_configuration_summary...'
GO

----------------------------------------------------------------------------------------------------
--                              autoadmin_backup_configuration_summary
--
-- Contains a summarized version of autoadmin_backup_configurations
-- This removes db_ids and outputs only count of databases with each configuration
--
CREATE VIEW autoadmin_backup_configuration_summary
as
SELECT 
	ManagedBackupVersion,
	IsAlwaysOn,
	IsDropped,
	IsEnabled,
	RetentionPeriod,
	EncryptionAlgorithm,
	SchedulingOption,
	DayOfWeek,
	COUNT(*) AS DatabaseCount
FROM autoadmin_backup_configurations
GROUP BY
	ManagedBackupVersion,
	IsAlwaysOn,
	IsDropped,
	IsEnabled,
	RetentionPeriod,
	EncryptionAlgorithm,
	SchedulingOption,
	DayOfWeek
GO

-- Allow all users to view the summary of the current configurations
-- This is to allow telemetry to collect the required data
GRANT SELECT ON autoadmin_backup_configuration_summary TO PUBLIC
GO

----------------------------------------------------------------------------------------------------
--                              Metadata service stored procedures
--
----------------------------------------------------------------------------------------------------


----------------------------------------------------------------------------------------------------
-- autoadmin_metadata_query_schema_version
--
-- This procedure returns the current schema version of the metadata service table and the minimum 
-- version that this set of procedures requires in the metadata service code that calls them.
-- The service must call this procedure before calling any other procedures.
-- Service code that was compiled with a version below min_schema_version_supported must not call 
-- any other procedures.
-- All of the other procedures have a @schema_version parameter that enables them to return
-- data that matches the schema version understood by the service.
-- Concurrency: this procedure may be called from multiple client connections simultaneously.
--
IF OBJECT_ID ('autoadmin_metadata_query_schema_version', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure autoadmin_metadata_query_schema_version...'
	DROP PROCEDURE autoadmin_metadata_query_schema_version;
END 
GO

PRINT 'Creating procedure autoadmin_metadata_query_schema_version...'
GO
CREATE PROCEDURE autoadmin_metadata_query_schema_version
        @current_schema_version			INT OUT,
        @min_schema_version_supported	INT OUT
AS
BEGIN
    SET NOCOUNT ON

    SET @current_schema_version = dbo.fn_autoadmin_schema_version()
    SET @min_schema_version_supported = dbo.fn_autoadmin_min_schema_version()
END
GO

----------------------------------------------------------------------------------------------------
-- autoadmin_metadata_query_dbs
--
-- This procedure returns a join of the relevant columns from sys.databases, sys.database_recovery_status,
-- sys.database_mirroring, autoadmin_managed_databases and autoadmin_task_agent_metadata. There will be a row for
-- each existing database and for each database that was detected in an earlier call and dropped. 
-- Columns from sys.databases, sys.database_mirroring, and sys.database_recovery_status will be returned as NULL for dropped databases.
-- For newly detected databases, rows will be added to autoadmin_managed_databases for the new database and to 
-- autoadmin_task_agent_metadata for the specified task agent.
-- If more columns for the sys.database_xxx tables are need in future they can be added to the returned join.
-- Concurrency: this procedure may be called from multiple client connections simultaneously.
--
IF OBJECT_ID ('autoadmin_metadata_query_dbs', 'P') IS NOT NULL
BEGIN
	PRINT 'Dropping procedure autoadmin_metadata_query_dbs...' 
	DROP PROCEDURE autoadmin_metadata_query_dbs;
END 
GO

PRINT 'Creating procedure autoadmin_metadata_query_dbs...'
GO

CREATE PROCEDURE autoadmin_metadata_query_dbs
        @task_agent_guid 	UNIQUEIDENTIFIER,
        @schema_version 	INT,
        @db_name 			SYSNAME = NULL
AS
BEGIN
    IF (@task_agent_guid IS NULL) OR (@schema_version IS NULL)
    BEGIN
    RAISERROR ('All parameters except @db_name must be non-NULL. Cannot complete auto-admin query for databases', -- Message text.
               17, -- Severity,
               1); -- State
    RETURN
    END

    -- The first part of this proc updates all autoadmin tables based on the current set of databases

    SET NOCOUNT ON

    -- Updates Step 1: For every existing database that does not have a row in autoadmin_managed_databases
    -- insert a new row into autoadmin_managed_databases
    --
    BEGIN TRANSACTION
        DECLARE @result INT
		DECLARE @empty_guid UNIQUEIDENTIFIER
		
		SET @empty_guid = '00000000-0000-0000-0000-000000000000'

        -- To avoid issues arising from two threads trying to refresh agent metadata at the same time we allow only one thread to 
        -- proceed at a time using the application lock below. However if a thread has to wait on the lock, it needn't do 
        -- a full refresh since the data was very recently refreshed. We just return whatever we have in our metadata tables.
        --
        EXEC @result = sp_getapplock @Resource = N'SSMBack2WAMetadataRefresh',
                                     @LockMode  = N'Exclusive',
                                     @DbPrincipal = N'dbo'

        IF @result = 1	-- Lock was granted after some wait, meaning another thread was refreshing. No need to refresh again.
        BEGIN
             GOTO release;
        END
        ELSE IF @result <> 0
        BEGIN
             RAISERROR ('Application lock couldn''t be granted for refreshing agent metadata', -- Message text.
                              17, -- Severity,
                              2); -- State
             RETURN
        END	
	
        BEGIN TRY
		INSERT INTO autoadmin_managed_databases (db_name, db_id, db_guid, group_db_guid, drop_date)
		SELECT dbs.name, dbs.database_id, dbrs.database_guid, dbs.group_database_id, NULL
		    FROM sys.databases dbs
		    INNER JOIN sys.database_recovery_status dbrs ON dbs.database_id = dbrs.database_id
		    WHERE NOT EXISTS (SELECT 1
				      FROM autoadmin_managed_databases aamd
				      WHERE aamd.db_id = dbs.database_id 
				      AND QUOTENAME(aamd.db_name) = QUOTENAME(dbs.name)
				      AND aamd.db_guid = dbrs.database_guid
				     )
		    AND ISNULL(dbrs.database_guid, @empty_guid) <> @empty_guid
			AND dbs.source_database_id IS NULL

		-- Updates Step 2: For every row in autoadmin_managed_databases that does not have a corresponding row in 
		-- sys.databases etc., if drop_date is not NULL then set it to the current date/time. We use db_name and
		-- db_id to locate a row in sys.databases. If a database is dropped and another database is created with
		-- the same name immediately afterwards, it might get the same db_id; in which case db_guid will be used to 
		-- distinguish the databases. However, if a database is set to OFFLINE, its db_guid becomes NULL and we might 
		-- incorrectly mark the database as dropped as the db_guids won't match. So, we specially handle NULL and empty 
		-- GUIDs. The query will also unmark a detached database as dropped if it is re-attached in the same instance.
		--
		UPDATE aamd
		SET aamd.drop_date = 
			(
				CASE 
				WHEN NOT EXISTS 
					(
						SELECT 1
						FROM sys.databases dbs
						JOIN sys.database_recovery_status dbrs
						ON dbs.database_id = dbrs.database_id
						WHERE dbs.database_id = aamd.db_id
						AND QUOTENAME(dbs.name) = QUOTENAME(aamd.db_name)
						AND (dbrs.database_guid = aamd.db_guid
						OR ISNULL(dbrs.database_guid, @empty_guid) = @empty_guid)
					) 
				THEN ISNULL(aamd.drop_date, SYSDATETIME())
				ELSE NULL
				END
			)
		FROM autoadmin_managed_databases aamd

		-- Updates Step 3: Update the group_database_id for databases if changed since last read. This happens when a database
		-- joins or leaves an availability group.
		--
		UPDATE aamd
		SET aamd.group_db_guid = dbs.group_database_id
		FROM sys.databases dbs
		INNER JOIN sys.database_recovery_status dbrs ON dbs.database_id = dbrs.database_id
		INNER JOIN autoadmin_managed_databases aamd
		     ON aamd.db_id = dbs.database_id 
		     AND QUOTENAME(aamd.db_name) = QUOTENAME(dbs.name)
		     AND aamd.db_guid = dbrs.database_guid
		WHERE ISNULL(aamd.group_db_guid, 0x0) <> ISNULL(dbs.group_database_id, 0x0)

		-- Updates Step 4: For every existing database that does not have a row in autoadmin_task_agent_metadata for the calling task agent
		-- insert a new row into autoadmin_task_agent_metadata.
		--
		INSERT INTO autoadmin_task_agent_metadata (task_agent_guid, autoadmin_id, last_modified, task_agent_data)
		SELECT @task_agent_guid, aamd.autoadmin_id, CURRENT_TIMESTAMP, NULL
		    FROM sys.databases dbs
		    INNER JOIN sys.database_recovery_status dbrs ON dbs.database_id = dbrs.database_id
		    INNER JOIN autoadmin_managed_databases aamd
			     ON aamd.db_id = dbs.database_id 
			     AND QUOTENAME(aamd.db_name) = QUOTENAME(dbs.name)
			     AND aamd.db_guid = dbrs.database_guid
		    LEFT OUTER JOIN autoadmin_task_agent_metadata aatam ON aatam.autoadmin_id = aamd.autoadmin_id AND aatam.task_agent_guid = @task_agent_guid
		    WHERE 
				aatam.task_agent_guid IS NULL 
				AND aamd.drop_date IS NULL
				AND dbs.source_database_id IS NULL
        END TRY
	BEGIN CATCH
		EXEC sp_releaseapplock @DbPrincipal = 'dbo', @Resource = 'SSMBack2WAMetadataRefresh';
		THROW
	END CATCH

release:
    EXEC sp_releaseapplock @DbPrincipal = 'dbo', @Resource = 'SSMBack2WAMetadataRefresh';

    COMMIT TRANSACTION

    -- Updates are now complete.
    -- The second part of this proc returns a join of database metadata and associated autoadmin metadata

    -- Do a join of sys.databases, sys.database_recovery_status, sys.database_mirroring, autoadmin_managed_databases and autoadmin_task_agent_metadata.
    -- The returned rowset will appear as follows:
    -- Existing databases will have columns for most tables (exception is sys.database_mirroring.mirroring_role which may be NULL)
    -- Dropped databases that have not be deleted by the specified task agent will have columns in autoadmin_managed_databases and autoadmin_task_agent_metadata.
    -- Dropped databases that have been deleted by the specified task agent will not have any rows
    --
	
	SET @db_name = ISNULL(@db_name, '')

	SELECT
	dbs.state db_state,
	dbs.create_date db_create_date,
	dbs.recovery_model db_recovery_model, 
	dbs.is_read_only db_read_only, 
	dbs.target_recovery_time_in_seconds db_recovery_time, 
	dbm.mirroring_role db_mirroring_role,
	aamd.db_name db_name,
	aamd.db_id db_id,
	aamd.db_guid db_guid,
	aamd.group_db_guid group_db_guid,
	(SELECT group_id FROM sys.dm_hadr_database_replica_states WHERE group_database_id = aamd.group_db_guid AND is_local = 1) group_guid,
	aamd.drop_date drop_date,
	sys.fn_hadr_backup_is_preferred_replica(db_name) as is_preferred_backup_replica,
	aatam.last_modified last_modified,
	aatam.task_agent_data task_agent_data,
	aatam.schema_version schema_version
	FROM sys.databases dbs 
	INNER JOIN sys.database_recovery_status dbrs ON dbs.database_id = dbrs.database_id
	INNER JOIN sys.database_mirroring dbm ON dbm.database_id = dbs.database_id
	RIGHT OUTER JOIN autoadmin_managed_databases aamd ON aamd.db_id = dbs.database_id and aamd.db_guid = dbrs.database_guid
	INNER JOIN autoadmin_task_agent_metadata aatam ON aatam.autoadmin_id = aamd.autoadmin_id AND aatam.task_agent_guid = @task_agent_guid
	WHERE 
	(
		QUOTENAME(@db_name) = QUOTENAME('') OR
		QUOTENAME(@db_name) = QUOTENAME(aamd.db_name)
	)
	AND @schema_version = aatam.schema_version
	AND dbs.source_database_id IS NULL
END
GO

----------------------------------------------------------------------------------------------------------------
-- autoadmin_metadata_insert_task_agent_global_data
--
-- This procedure inserts a record into autoadmin_task_agent_metadata for the specified task agent's global data.
-- This procedure and others that manipulate task agent global metadata use the special autoadmin_id value zero to 
-- represent the task agent global metadata.
-- That value was not encoded in a separate function as that would result in sub-optimal query performance.
-- Concurrency: this procedure may be called from multiple client connections simultaneously.
-- 
IF OBJECT_ID ('autoadmin_metadata_insert_task_agent_global_data', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure autoadmin_metadata_insert_task_agent_global_data...'
	DROP PROCEDURE autoadmin_metadata_insert_task_agent_global_data;
END 
GO

PRINT 'Creating procedure autoadmin_metadata_insert_task_agent_global_data...'
GO

CREATE PROCEDURE autoadmin_metadata_insert_task_agent_global_data
        @schema_version          INT,                
        @task_agent_guid         UNIQUEIDENTIFIER,
        @task_agent_data         XML
AS
BEGIN
    IF (@schema_version IS NULL) OR 
		(@task_agent_guid IS NULL) OR 
		(@task_agent_data IS NULL)
    BEGIN
        RAISERROR ('All parameters must be specified and non-NULL. Cannot complete task agent global metadata insertion', -- Message text.
                   17, -- Severity,
                   1); -- State
        RETURN
    END

    SET NOCOUNT ON

    BEGIN TRANSACTION
		
		DELETE FROM autoadmin_task_agent_metadata 
		WHERE autoadmin_id = 0

        INSERT INTO autoadmin_task_agent_metadata
			(task_agent_guid, 
			autoadmin_id, 
			last_modified, 
			task_agent_data, 
			schema_version)
        VALUES (@task_agent_guid, 
			0, 
			CURRENT_TIMESTAMP, 
			@task_agent_data, 
			@schema_version)

    COMMIT TRANSACTION
END
GO

----------------------------------------------------------------------------------------------------------------
-- autoadmin_metadata_query_task_agent_global_data
--
-- This procedure returns the record in autoadmin_task_agent_metadata for the specified task agent's global data.
-- Concurrency: this procedure may be called from multiple client connections simultaneously.
-- 
IF OBJECT_ID ('autoadmin_metadata_query_task_agent_global_data', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure autoadmin_metadata_query_task_agent_global_data...'
	DROP PROCEDURE autoadmin_metadata_query_task_agent_global_data;
END 
GO

PRINT 'Creating procedure autoadmin_metadata_query_task_agent_global_data...'
GO

CREATE PROCEDURE autoadmin_metadata_query_task_agent_global_data
        @schema_version						INT,
        @task_agent_guid					UNIQUEIDENTIFIER,
        @task_agent_data					XML OUTPUT,
		@task_agent_data_schema_version		INT OUTPUT
AS
BEGIN
    IF (@task_agent_guid IS NULL)
    BEGIN
        RAISERROR ('All parameters must be specified. Cannot complete task agent global metadata query', -- Message text.
                   17, -- Severity,
                   1); -- State
        RETURN
    END

    SET NOCOUNT ON

	-- if no records were found, null is returned in @task_agent_data.. it is upto caller's responsbility to handle this case
    SELECT @task_agent_data = aatam.task_agent_data,
			@task_agent_data_schema_version = aatam.schema_version
		FROM autoadmin_task_agent_metadata aatam
		WHERE aatam.task_agent_guid = @task_agent_guid 
		AND aatam.autoadmin_id = 0
		AND (ISNULL(@schema_version, 0 ) = 0
			OR aatam.schema_version = @schema_version)

	IF(@task_agent_data_schema_version IS NULL)
	BEGIN
	    -- If null was returned in above query, Get latest schema version & return
		SET @task_agent_data_schema_version = dbo.fn_autoadmin_schema_version()
	END
END
GO

----------------------------------------------------------------------------------------------------------------
-- autoadmin_metadata_update_task_agent_global_data
--
-- This procedure updates the record in autoadmin_task_agent_metadata for the specified task agent's global data.
-- Concurrency: this procedure may be called from multiple client connections simultaneously.
-- 
IF OBJECT_ID ('autoadmin_metadata_update_task_agent_global_data', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure autoadmin_metadata_update_task_agent_global_data...'
	DROP PROCEDURE autoadmin_metadata_update_task_agent_global_data;
END 
GO

PRINT 'Creating procedure autoadmin_metadata_update_task_agent_global_data...'
GO

CREATE PROCEDURE autoadmin_metadata_update_task_agent_global_data
        @schema_version          INT,
        @task_agent_guid         UNIQUEIDENTIFIER,
        @task_agent_data         XML
AS
BEGIN
    IF (@schema_version IS NULL) OR 
		(@task_agent_guid IS NULL) OR 
		(@task_agent_data IS NULL)
    BEGIN
        RAISERROR ('All parameters must be specified and non-NULL. Cannot complete task agent global metadata update', -- Message text.
                   17, -- Severity,
                   1); -- State
        RETURN
    END

    SET NOCOUNT OFF

    BEGIN TRANSACTION

        UPDATE autoadmin_task_agent_metadata 
		SET task_agent_data = @task_agent_data,
		schema_version = @schema_version
        WHERE autoadmin_task_agent_metadata.task_agent_guid = @task_agent_guid 
		AND autoadmin_task_agent_metadata.autoadmin_id = 0
    
	COMMIT TRANSACTION

END
GO

----------------------------------------------------------------------------------------------------------------
-- autoadmin_metadata_delete_task_agent_global_data
--
-- This procedure deletes the record in autoadmin_task_agent_metadata for the specified task agent's global data.
-- Concurrency: this procedure may be called from multiple client connections simultaneously.
-- 
IF OBJECT_ID ('autoadmin_metadata_delete_task_agent_global_data', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure autoadmin_metadata_delete_task_agent_global_data...'
	DROP PROCEDURE autoadmin_metadata_delete_task_agent_global_data;
END 
GO

PRINT 'Creating procedure autoadmin_metadata_delete_task_agent_global_data...'
GO

CREATE PROCEDURE autoadmin_metadata_delete_task_agent_global_data
        @schema_version          INT,
        @task_agent_guid         UNIQUEIDENTIFIER
AS
BEGIN
    IF (@schema_version IS NULL) OR (@task_agent_guid IS NULL)
    BEGIN
        RAISERROR ('All parameters must be specified and non-NULL. Cannot complete task agent global metadata deletion', -- Message text.
                   17, -- Severity,
                   1); -- State
        RETURN
    END

    SET NOCOUNT ON

    BEGIN TRANSACTION  
	    DELETE FROM autoadmin_task_agent_metadata
            WHERE autoadmin_task_agent_metadata.task_agent_guid = @task_agent_guid 
		    AND autoadmin_task_agent_metadata.autoadmin_id = 0
		    AND autoadmin_task_agent_metadata.schema_version = @schema_version
    COMMIT TRANSACTION
END
GO


----------------------------------------------------------------------------------------------------------------
-- autoadmin_metadata_update_task_agent_data_for_database
--
-- This procedure updates the record in autoadmin_task_agent_metadata for the specified task agent's per-DB data for
-- the specified database.
-- Concurrency: this procedure may be called from multiple client connections simultaneously.
-- 
IF OBJECT_ID ('autoadmin_metadata_update_task_agent_data_for_database', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure autoadmin_metadata_update_task_agent_data_for_database...'
	DROP PROCEDURE autoadmin_metadata_update_task_agent_data_for_database;
END 
GO

PRINT 'Creating procedure autoadmin_metadata_update_task_agent_data_for_database...'
GO

CREATE PROCEDURE autoadmin_metadata_update_task_agent_data_for_database
        @schema_version  INT,
        @task_agent_guid UNIQUEIDENTIFIER,
        @db_name         NVARCHAR(128),
        @db_id           INT,
        @db_guid         UNIQUEIDENTIFIER,
        @last_modified   DATETIME,
        @task_agent_data XML
AS
BEGIN
    IF (@schema_version IS NULL) OR 
		(@task_agent_guid IS NULL) OR 
		(@db_name IS NULL) OR 
		(@db_id IS NULL) OR 
		(@db_guid IS NULL) OR 
		(@task_agent_data IS NULL)
    BEGIN
        RAISERROR ('All parameters must be specified and non-NULL. Cannot complete task agent metadata update for database.', -- Message text.
                   17, -- Severity,
                   1); -- State
        RETURN
    END

    DECLARE @last_modified_time DATETIME
    SET NOCOUNT OFF

    BEGIN TRANSACTION

		-- Get last modifed time for database record,  it could be v1 or v2; that is why we dont filter based on schema version
        SELECT @last_modified_time = last_modified 
        FROM autoadmin_task_agent_metadata aatam, autoadmin_managed_databases aamd
            WHERE aamd.db_id = @db_id 
                AND QUOTENAME(aamd.db_name) = QUOTENAME(@db_name)
                AND aamd.db_guid = @db_guid
                AND aamd.autoadmin_id = aatam.autoadmin_id
                AND aatam.task_agent_guid = @task_agent_guid

        IF (@last_modified > @last_modified_time) 
        BEGIN
            UPDATE autoadmin_task_agent_metadata 
	        SET task_agent_data = @task_agent_data, 
	            last_modified = @last_modified,
		        schema_version = @schema_version
                FROM autoadmin_task_agent_metadata aatam, autoadmin_managed_databases aamd
                WHERE aamd.db_id = @db_id 
                    AND QUOTENAME(aamd.db_name) = QUOTENAME(@db_name)
                    AND aamd.db_guid = @db_guid
                    AND aamd.autoadmin_id = aatam.autoadmin_id
                    AND aatam.task_agent_guid = @task_agent_guid 
        END
    COMMIT TRANSACTION
END
GO

----------------------------------------------------------------------------------------------------------------
-- autoadmin_metadata_delete_task_agent_data_for_database
--
-- This procedure deletes the record in autoadmin_task_agent_metadata for the specified task agent's per-DB data for
-- the specified database. The database must have been dropped or an error will be returned.
-- This procedure also does garbage collection on autoadmin_managed_databases. After deleting the task agent's record
-- from autoadmin_task_agent_metadata it will delete any records in autoadmin_managed_databases that no longer have 
-- referencing records in autoadmin_task_agent_metadata.
-- Concurrency: this procedure may be called from multiple client connections simultaneously.
-- 
IF OBJECT_ID ('autoadmin_metadata_delete_task_agent_data_for_database', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure autoadmin_metadata_delete_task_agent_data_for_database...'
	DROP PROCEDURE autoadmin_metadata_delete_task_agent_data_for_database;
END 
GO

PRINT 'Creating procedure autoadmin_metadata_delete_task_agent_data_for_database...'
GO

CREATE PROCEDURE autoadmin_metadata_delete_task_agent_data_for_database
        @schema_version  INT,
        @task_agent_guid UNIQUEIDENTIFIER,
        @db_name         NVARCHAR(128),
        @db_id           INT,
        @db_guid         UNIQUEIDENTIFIER
AS
BEGIN
    IF (@schema_version IS NULL) OR 
		(@task_agent_guid IS NULL) OR 
		(@db_name IS NULL) OR 
		(@db_id IS NULL) OR 
		(@db_guid IS NULL)
    BEGIN
        RAISERROR ('All parameters must be specified and non-NULL. Cannot complete task agent metadata deletion for database.', -- Message text.
                   17, -- Severity,
                   1); -- State
        RETURN
    END

    SET NOCOUNT OFF

    BEGIN TRANSACTION
        -- Step 1: Delete the task agent's row in autoadmin_task_agent_metadata for the specified database
        -- Note: deletes are only executed for a database that has been dropped.
        --
        DELETE autoadmin_task_agent_metadata 
		FROM autoadmin_task_agent_metadata aatam
        WHERE aatam.task_agent_guid = @task_agent_guid
				AND aatam.schema_version = @schema_version
                AND aatam.autoadmin_id IN
        (SELECT aamd.autoadmin_id FROM autoadmin_managed_databases aamd
            WHERE aamd.db_id = @db_id 
              AND QUOTENAME(aamd.db_name) = QUOTENAME(@db_name)
              AND aamd.db_guid = @db_guid
              AND aamd.drop_date IS NOT NULL)

        IF (@@ROWCOUNT = 0)
        BEGIN
            DECLARE @Msg NVARCHAR(256)
            SET @Msg = N'The database %s (ID = %d) has either not been dropped, never existed, or its task agent data was deleted earlier. Cannot delete task agent data from auto-admin tables.';

            RAISERROR (@Msg, -- Message text.
                       17, -- Severity,
                       1, -- State,
                       @db_name, @db_id); -- formatting arguments
            GOTO QuitWithRollback
        END

        SET NOCOUNT ON

        -- Step 2: Garbage collection on autoadmin_managed_databases.
        -- Delete rows from autoadmin_managed_databases that do not have any corresponding rows left in autoadmin_task_agent_metadata

        DELETE autoadmin_managed_databases FROM autoadmin_managed_databases aamd
            WHERE aamd.drop_date IS NOT NULL
            AND aamd.autoadmin_id NOT IN (SELECT autoadmin_id 
                                            FROM autoadmin_task_agent_metadata
                                            WHERE schema_version = @schema_version)

	QuitWithCommit:
		COMMIT TRANSACTION
		GOTO ProcEnd

	QuitWithRollback:
		IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION

	ProcEnd:
END
GO

IF OBJECT_ID ('autoadmin_metadata_delete', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure autoadmin_metadata_delete...'
	DROP PROCEDURE autoadmin_metadata_delete;
END 
GO

PRINT 'Creating procedure autoadmin_metadata_delete...'
GO
-- Procedure to delete entries in metadata tables
CREATE PROC autoadmin_metadata_delete
AS
BEGIN
	PRINT 'Cleaning up managed backup metadata tables...'

	PRINT 'Deleting entries from autoadmin_managed_databases...'
	DELETE FROM autoadmin_managed_databases

	PRINT 'Deleting entries from autoadmin_task_agent_metadata...'
	DELETE FROM autoadmin_task_agent_metadata

	PRINT 'Deleting entries from autoadmin_system_flags...'
	DELETE FROM autoadmin_system_flags

	PRINT 'Deleting entries from autoadmin_master_switch...'
	DELETE FROM autoadmin_master_switch

	PRINT 'Deleting entries from smart_backup_files...'
	DELETE FROM smart_backup_files

END
GO


IF OBJECT_ID ('autoadmin_metadata_cleanup', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure autoadmin_metadata_cleanup...'
	DROP PROCEDURE autoadmin_metadata_cleanup;
END 
GO

PRINT 'Creating procedure autoadmin_metadata_cleanup...'
GO
-- Procedure to cleanup managed backup metadata( internal only ) 
CREATE PROC autoadmin_metadata_cleanup
	@schema_version INT,
	@agent_started BIT       = 0, -- flag to specify if agent was started atleast once
	@instance_configured BIT = 0  -- flag to specify managed backup was configured at instance level
AS
BEGIN
    -- Validations

    -- If agent is running state, let us not proceed
    DECLARE @agent_service_status INT
    SELECT @agent_service_status = [status]
    FROM sys.dm_server_services
    WHERE servicename like'%SQL Server Agent%'
    
    -- Status 4 is running - http://msdn.microsoft.com/en-us/library/hh204542.aspx
    IF(@agent_service_status = 4)
    BEGIN
		RAISERROR ('Cannot perform cleanup while SQL Server Agent is currently running', -- Message text
				   17, -- Severity,
				   1); -- State
        RETURN
    END
	
	-- if schema version is 2 & @instance_configured is 1, then report back that user can configure managed backup via V2 API
	IF((@schema_version = 2) AND (@instance_configured = 1))
	BEGIN
		RAISERROR ('Managed backup V2 instance configuration as part of cleanup is not supported', -- Message text
				   17, -- Severity,
				   2); -- State
        RETURN
	END

	BEGIN TRANSACTION

	DECLARE @task_agent_data xml

	IF(@schema_version = 2)
	BEGIN
	    -- V2 default task data xml
		SET @task_agent_data = '<AutoBackupGlobalDataV2 xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent">
  <defaultAutoBackupSetting>false</defaultAutoBackupSetting>
  <defaultBackupBeginTime>0001-01-01T00:00:00</defaultBackupBeginTime>
  <defaultBackupDuration>-P10675199DT2H48M5.4775808S</defaultBackupDuration>
  <defaultContainerUrl i:nil="true" />
  <defaultDaysOfWeek>NoDay</defaultDaysOfWeek>
  <defaultEncryptionAlgorithm i:nil="true" />
  <defaultEncryptorName i:nil="true" />
  <defaultEncryptorType i:nil="true" />
  <defaultFullBackupFreqType i:nil="true" />
  <defaultLocalCachePath i:nil="true" />
  <defaultLogBackupFreq>-P10675199DT2H48M5.4775808S</defaultLogBackupFreq>
  <defaultRetentionPeriod>0</defaultRetentionPeriod>
  <defaultSchedulingOption>SYSTEM</defaultSchedulingOption>
  <firstConfiguredAt>0001-01-01T00:00:00</firstConfiguredAt>
</AutoBackupGlobalDataV2>'

		EXEC autoadmin_metadata_delete

	END
	ELSE
	BEGIN
		IF(@instance_configured = 0)
		BEGIN
			-- V1 default task data -instance not configured
			SET @task_agent_data = '<AutoBackupGlobalData xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent">
	  <defaultAutoBackupSetting>false</defaultAutoBackupSetting>
	  <defaultCredentialName i:nil="true" />
	  <defaultEncryptionAlgorithm i:nil="true" />
	  <defaultEncryptorName i:nil="true" />
	  <defaultEncryptorType i:nil="true" />
	  <defaultRetentionPeriod>0</defaultRetentionPeriod>
	  <defaultURL i:nil="true" />
	  <firstConfiguredAt>0001-01-01T00:00:00</firstConfiguredAt>
	</AutoBackupGlobalData>'
		END
		ELSE
		BEGIN
		    -- V1 default task data -instance configured
			SET @task_agent_data = '<AutoBackupGlobalData xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent">
  <defaultAutoBackupSetting>false</defaultAutoBackupSetting>
   <defaultCredentialName>AzureCredential</defaultCredentialName>
  <defaultEncryptionAlgorithm i:nil="true" />
  <defaultEncryptorName i:nil="true" />
  <defaultEncryptorType i:nil="true" />
  <defaultRetentionPeriod>0</defaultRetentionPeriod>
  <defaultURL i:nil="true" />
  <firstConfiguredAt>0001-01-01T00:00:00</firstConfiguredAt>
</AutoBackupGlobalData>'
		END

		IF(ISNULL(@agent_started, 0) = 0)
		BEGIN
		    -- SQL Agent was never started, Remove all entries in managed backup tables
			EXEC autoadmin_metadata_delete
		END
		ELSE
		BEGIN
			UPDATE autoadmin_task_agent_metadata
			SET schema_version = 1
			WHERE  task_agent_data IS NULL
		END
	END
		
	-- Only if SQL Agent was started atleast once, or instance was configured
	-- we could see default V1/V2 in table autoadmin_metadata_insert_task_agent_global_data
	IF(ISNULL(@agent_started, 0) = 1) OR (ISNULL(@instance_configured, 0) = 1)
	BEGIN
	   -- Insert or update global instance metadata
		EXEC autoadmin_metadata_insert_task_agent_global_data @schema_version = @schema_version, 
			@task_agent_guid = '6DF5825B-945C-4081-A4E3-292556E99B99',
			@task_agent_data = @task_agent_data

		-- Turn on Master Switch
		EXEC autoadmin_set_master_switch @state = 1
	END

	COMMIT TRANSACTION
END
GO
----------------------------------------------------------------------------------------------------
--                              System Flags service stored procedures
--
----------------------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------------------
-- autoadmin_set_system_flag
--
-- Call this procedure to set the value of an internal system flag by specifying its name; these 
-- flags govern the behavior of internal smart-backup algorithms.
--
IF OBJECT_ID ('autoadmin_set_system_flag', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure autoadmin_set_system_flag...'
	DROP PROCEDURE autoadmin_set_system_flag;
END 
GO

PRINT 'Creating procedure autoadmin_set_system_flag...'
GO

CREATE PROCEDURE autoadmin_set_system_flag
    @flag_name		NVARCHAR(128),
    @flag_value		NVARCHAR(MAX)
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
    BEGIN
        SAVE TRANSACTION tran_autoadmin_set_system_flag
    END
    ELSE
    BEGIN
        BEGIN TRANSACTION
    END

    BEGIN TRY
        -- Check if we are updating / adding Notification email ID 
        IF(@flag_name IS NOT NULL)
        BEGIN
            IF(@flag_name = N'SSMBackup2WANotificationEmailIds')
            BEGIN
                EXEC sp_autoadmin_configure_notification 
            END
        END

        IF EXISTS (SELECT TOP 1 * FROM autoadmin_system_flags WHERE name = @flag_name)
        BEGIN
            UPDATE autoadmin_system_flags SET value = @flag_value WHERE name = @flag_name
        END
        ELSE
        BEGIN
            INSERT autoadmin_system_flags VALUES (@flag_name, @flag_value)
        END

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)

    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_autoadmin_set_system_flag

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)
    END CATCH
END
GO

----------------------------------------------------------------------------------------------------
-- autoadmin_fetch_system_flag
--
-- Fetch all the system-flag name-value pairs.
--

IF OBJECT_ID ('autoadmin_fetch_system_flags', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure autoadmin_fetch_system_flags...'
	DROP PROCEDURE autoadmin_fetch_system_flags;
END 
GO

PRINT 'Creating procedure autoadmin_fetch_system_flags...'
GO

CREATE PROCEDURE autoadmin_fetch_system_flags
AS
BEGIN
	BEGIN TRANSACTION
		DECLARE @value NVARCHAR(MAX)

		SELECT @value = value FROM autoadmin_system_flags WHERE LOWER(name) = LOWER(N'SSMBackup2WAEverConfigured')
		
		IF (LOWER(ISNULL(@value, '')) <> N'true')
		BEGIN
			DECLARE @is_configured BIT
			SET @is_configured = 0
			
			IF EXISTS (SELECT TOP 1 container_url FROM managed_backup.fn_backup_db_config(NULL) WHERE container_url IS NOT NULL)
			BEGIN
				SET @is_configured = 1	
			END
			ELSE IF EXISTS (SELECT TOP 1 container_url FROM managed_backup.fn_backup_instance_config() WHERE container_url IS NOT NULL)
			BEGIN
				SET @is_configured = 1	
			END
			ELSE IF EXISTS (SELECT TOP 1 credential_name FROM smart_admin.fn_backup_db_config(NULL) WHERE credential_name IS NOT NULL)
			BEGIN
				SET @is_configured = 1	
			END
			ELSE IF EXISTS (SELECT TOP 1 credential_name FROM smart_admin.fn_backup_instance_config() WHERE credential_name IS NOT NULL)
			BEGIN
				SET @is_configured = 1	
			END
			
			IF (@is_configured = 1)
			BEGIN
				MERGE autoadmin_system_flags AS target
				USING (SELECT LOWER(N'SSMBackup2WAEverConfigured') as name) AS source
				ON source.name = target.name
				WHEN MATCHED THEN UPDATE SET target.value = N'true'
				WHEN NOT MATCHED THEN INSERT VALUES (N'SSMBackup2WAEverConfigured', N'true');
			END
		END
	COMMIT TRANSACTION
	
    SELECT name,
	value 
	FROM autoadmin_system_flags
END
GO

IF OBJECT_ID ('autoadmin_restore_headeronly', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure autoadmin_restore_headeronly...'
	DROP PROCEDURE autoadmin_restore_headeronly;
END 
GO

PRINT 'Creating procedure autoadmin_restore_headeronly...'
GO

CREATE PROCEDURE autoadmin_restore_headeronly    
	@backupFile NVARCHAR(260),
	@credName SYSNAME,
	@blocksize INT = 65536
AS    
BEGIN    
    -- Validations    
    IF(@backupFile IS NULL)    
    BEGIN    
        RAISERROR ('@backupFile cannot be NULL', -- Message text    
           17, -- Severity,    
           1); -- State    
        RETURN    
    END    
     
    IF(@credName IS NULL)    
    BEGIN    
        RAISERROR ('@credName cannot be NULL', -- Message text    
           17, -- Severity,    
           1); -- State    
        RETURN    
    END    
     
 DECLARE @restore_sql  NVARCHAR(MAX);    
    
    -- Check if we are using SAS based credentials    
    IF EXISTS ( SELECT name FROM sys.credentials    
        WHERE name = @credName     
        AND  credential_identity = 'Shared Access Signature')    
    BEGIN    
        -- for SAS based credentials, it is not required to specify credential name in restore statement    
        SET @restore_sql = 'RESTORE HEADERONLY FROM URL = @backupFile WITH BLOCKSIZE = @blocksize'    
  EXEC sp_executesql @restore_sql, N'@backupFile NVARCHAR(260),  @blocksize INT', @backupFile, @blocksize    
    END    
    ELSE    
    BEGIN    
        -- Backup to Url - requires credential name to be specificed in RESTORE statement    
        SET @restore_sql = 'RESTORE HEADERONLY FROM URL = @backupFile WITH CREDENTIAL = @@credName, BLOCKSIZE = @@blocksize'    
  EXEC sp_executesql @restore_sql, N'@backupFile NVARCHAR(260), @@credName SYSNAME, @@blocksize INT', @backupFile, @credName, @blocksize    
    END    
    
    RETURN    
END    
GO

----------------------------------------------------------------------------------------------------
--                              Miscellaneous Smart Admin stored procedures
--
----------------------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------------------
-- autoadmin_set_master_switch
--
-- Set the state of Smart Admin master switch.
--

IF OBJECT_ID ('autoadmin_set_master_switch', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure autoadmin_set_master_switch...'
	DROP PROCEDURE autoadmin_set_master_switch;
END 
GO

PRINT 'Creating procedure autoadmin_set_master_switch...'
GO

CREATE PROCEDURE autoadmin_set_master_switch
		@state BIT 
AS
BEGIN
	IF @state IS NULL
	BEGIN
		RAISERROR (45203, 17, 1);
		RETURN
	END

	BEGIN TRAN 
		IF NOT EXISTS (SELECT TOP 1 state FROM autoadmin_master_switch)
		BEGIN
			INSERT INTO autoadmin_master_switch VALUES (@state)
		END
		ELSE
		BEGIN
			UPDATE autoadmin_master_switch SET [state] = @state
		END
	COMMIT
END
GO


IF OBJECT_ID ('managed_backup.sp_backup_config_basic', 'P') IS NOT NULL
BEGIN
	PRINT 'Dropping procedure managed_backup.sp_backup_config_basic...'
	DROP PROCEDURE managed_backup.sp_backup_config_basic;
END
GO

PRINT 'Creating procedure managed_backup.sp_backup_config_basic...'
GO

-- Set the basic (required) configuration details for Managed Backups V2 
-- 
CREATE PROCEDURE managed_backup.sp_backup_config_basic
	@database_name SYSNAME = NULL,
	@enable_backup BIT = NULL,
	@container_url NVARCHAR(1024) = NULL,
	@retention_days INT = NULL
AS
BEGIN
	IF (@enable_backup IS NULL) AND (@container_url IS NULL) AND (@retention_days IS NULL)
	BEGIN
		RAISERROR (45205, 17, 1);
		RETURN
	END
	
	SET @database_name = ISNULL(@database_name, '');
	SET @container_url = ISNULL(@container_url, '');
	DECLARE @retention_str NVARCHAR(32) = ISNULL(CAST(@retention_days AS NVARCHAR(32)), '');

	DECLARE @backup_setting NVARCHAR(1);
	DECLARE @input VARBINARY(MAX);
	DECLARE @params NVARCHAR(MAX);
	
	IF (@enable_backup IS NULL)
	BEGIN
		SET @backup_setting = '2'; -- 0 = Disable, 1 = Enable, 2 = Keep existing setting.
	END
	ELSE
	BEGIN
		SET @backup_setting = CAST(@enable_backup as NVARCHAR(1))
	END

	SET @input = CONVERT(VARBINARY(MAX), @database_name)
	DECLARE @db_name_base64 NVARCHAR(MAX) = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	SET @input = CONVERT(VARBINARY(MAX), @container_url)
	DECLARE @container_url_base64 NVARCHAR(MAX) = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	SET @params = N'configure_backup_basic' + N' ' + @db_name_base64 + N' ' + @backup_setting + N' ' + @container_url_base64 + N' ' + @retention_str

	EXEC managed_backup.sp_add_task_command @task_name='backup', @additional_params = @params
END
GO

IF OBJECT_ID ('managed_backup.sp_backup_config_advanced', 'P') IS NOT NULL
BEGIN
	PRINT 'Dropping procedure managed_backup.sp_backup_config_advanced...'
	DROP PROCEDURE managed_backup.sp_backup_config_advanced;
END
GO	

PRINT 'Creating procedure managed_backup.sp_backup_config_advanced...'
GO
-- Configure the advanced parameters for managed backups v2
--
CREATE PROCEDURE managed_backup.sp_backup_config_advanced
	@database_name SYSNAME = NULL,
	@encryption_algorithm SYSNAME = NULL, 
	@encryptor_type NVARCHAR(32) = NULL,
	@encryptor_name SYSNAME = NULL,
	@local_cache_path NVARCHAR(1024) = NULL
AS
BEGIN
	-- Local caching is not yet implemented. Throw error for now.
	--
	if (@local_cache_path IS NOT NULL)
	BEGIN
		RAISERROR (45215, 17, 1);
		RETURN
	END
	
	SET @database_name = ISNULL(@database_name, '');
	SET @encryption_algorithm = LTRIM(RTRIM(ISNULL(@encryption_algorithm, '')));
	SET @encryptor_type = LTRIM(RTRIM(ISNULL(@encryptor_type, '')));
	SET @encryptor_name = ISNULL(@encryptor_name, '');
	SET @local_cache_path = ISNULL(@local_cache_path, '');

	IF (CHARINDEX(' ', @encryption_algorithm) > 0)
	BEGIN
		RAISERROR (45212, 17, 1, N'@encryption_algorithm', N'encryption algorithm');
		RETURN
	END

 	IF (CHARINDEX(' ', @encryptor_type) > 0)
	BEGIN
		RAISERROR (45212, 17, 1, N'@encryptor_type', N'encryptor type');
		RETURN
	END

	IF (UPPER(@encryption_algorithm) = 'NO_ENCRYPTION')
	BEGIN
		IF (LEN(@encryptor_type) != 0) OR (LEN(@encryptor_name) != 0)
		BEGIN
			RAISERROR (45217, 17, 1);
			RETURN
		END
	END
	ELSE IF (UPPER(@encryption_algorithm) = 'AES_128') OR (UPPER(@encryption_algorithm) = 'AES_192') OR (UPPER(@encryption_algorithm) = 'AES_256') OR (UPPER(@encryption_algorithm) = 'TRIPLE_DES_3KEY')
	BEGIN
		IF (LEN(@encryptor_type) = 0) OR (LEN(@encryptor_name) = 0)
		BEGIN
			RAISERROR (45218, 17, 1);
			RETURN
		END
	END

	DECLARE @input VARBINARY(MAX);
	DECLARE @params NVARCHAR(MAX);

	SET @input = CONVERT(VARBINARY(MAX), @database_name)
	DECLARE @db_name_base64 NVARCHAR(MAX) = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	SET @input = CONVERT(VARBINARY(MAX), @encryptor_name)
	DECLARE @enc_name_base64 NVARCHAR(MAX) = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')
	
	SET @input = CONVERT(VARBINARY(MAX), @local_cache_path)
	DECLARE @local_cache_path_base64 NVARCHAR(MAX) = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	SET @params = N'configure_backup_advanced' + N' ' + @db_name_base64 + N' ' + @enc_name_base64 + N' ' + @encryption_algorithm + N' ' + @encryptor_type + N' ' + @local_cache_path_base64
	EXEC managed_backup.sp_add_task_command @task_name='backup', @additional_params=@params
END
GO


IF OBJECT_ID ('managed_backup.sp_backup_config_schedule', 'P') IS NOT NULL
BEGIN
	PRINT 'Dropping procedure managed_backup.sp_backup_config_schedule...'
	DROP PROCEDURE managed_backup.sp_backup_config_schedule
END
GO

PRINT 'Creating procedure managed_backup.sp_backup_config_schedule...'
GO
-- Set the schedule for managed backups
-- This is a Managed Backup V2 stored procedure.
-- 
CREATE PROCEDURE managed_backup.sp_backup_config_schedule
	@database_name			SYSNAME = NULL,
	-- @scheduling_option is essentially the only absolutely required variable when calling this sp
	-- The other parameters may only be required with the existence of other parameters
	--
	@scheduling_option		SYSNAME,
	@full_backup_freq_type	SYSNAME = NULL,
	@days_of_week			NVARCHAR(256) = NULL,
	@backup_begin_time		NVARCHAR(32) = NULL,
	@backup_duration		NVARCHAR(32) = NULL,
	@log_backup_freq		NVARCHAR(32) = NULL
AS
BEGIN
	SET @database_name = ISNULL(@database_name, '');
	SET @scheduling_option = LTRIM(RTRIM(ISNULL(@scheduling_option, '')));
	SET @full_backup_freq_type = LTRIM(RTRIM(ISNULL(@full_backup_freq_type, '')));
	SET @days_of_week = LTRIM(RTRIM(ISNULL(@days_of_week, '')));
	SET @backup_begin_time = LTRIM(RTRIM(ISNULL(@backup_begin_time, '')));
	SET @backup_duration = LTRIM(RTRIM(ISNULL(@backup_duration, '')));
	SET @log_backup_freq = LTRIM(RTRIM(ISNULL(@log_backup_freq, '')));

	IF (CHARINDEX(' ', @scheduling_option) > 0)
	BEGIN
		RAISERROR (45212, 17, 1, N'@scheduling_option', N'scheduling option');
		RETURN
	END

	IF (UPPER(@scheduling_option) = 'SYSTEM')
	BEGIN
		IF (LEN(@full_backup_freq_type) != 0) OR (LEN(@backup_begin_time) != 0) OR 
			(LEN(@backup_duration) != 0) OR (LEN(@log_backup_freq) != 0)
		BEGIN
			RAISERROR (45216, 17, 1);
			RETURN
		END
	END

	IF (UPPER(@scheduling_option) = 'CUSTOM')
	BEGIN
		IF (LEN(@full_backup_freq_type) = 0) OR (LEN(@backup_begin_time) = 0) OR (LEN(@backup_duration) = 0) OR (LEN(@log_backup_freq) = 0)
		BEGIN
			RAISERROR (45213, 17, 1);
			RETURN
		END

		IF (CHARINDEX(' ', @full_backup_freq_type) > 0)
		BEGIN
			RAISERROR (45212, 17, 1, N'@full_backup_freq_type', N'full backup frequency type');
			RETURN
		END

		IF (CHARINDEX(' ', @backup_begin_time) > 0)
		BEGIN
			RAISERROR (45212, 17, 1, N'@backup_begin_time', N'backup begin time');
			RETURN
		END

		IF (CHARINDEX(' ', @backup_duration) > 0)
		BEGIN
			RAISERROR (45212, 17, 1, N'@backup_duration', N'backup duration');
			RETURN
		END

		IF (CHARINDEX(' ', @log_backup_freq) > 0)
		BEGIN
			RAISERROR (45212, 17, 1, N'@log_backup_freq', N'log backup frequency');
			RETURN
		END
	END

	IF (UPPER(@full_backup_freq_type) = 'WEEKLY') AND (LEN(@days_of_week) = 0)
	BEGIN
		RAISERROR (45214, 17, 1);
		RETURN
	END

	IF (UPPER(@full_backup_freq_type) = 'DAILY') AND (LEN(@days_of_week) != 0)
	BEGIN
		RAISERROR (45219, 17, 1);
		RETURN
	END

	-- Remove all whitespace from the days of the week because there cannot be white spaces in a parameter when it is passed
	--
	SET @days_of_week = REPLACE(@days_of_week, ' ', '')

	DECLARE @input VARBINARY(MAX);
	DECLARE @params NVARCHAR(MAX);

	SET @input = CONVERT(VARBINARY(MAX), @database_name)
	DECLARE @db_name_base64 NVARCHAR(MAX) = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	SET @input = CONVERT(VARBINARY(MAX), @days_of_week)
	DECLARE @days_of_week_base64 NVARCHAR(MAX) = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	SET @input = CONVERT(VARBINARY(MAX), @backup_begin_time)
	DECLARE @backup_begin_time_base64 NVARCHAR(MAX) = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	SET @input = CONVERT(VARBINARY(MAX), @backup_duration)
	DECLARE @backup_duration_base64 NVARCHAR(MAX) = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	SET @input = CONVERT(VARBINARY(MAX), @log_backup_freq)
	DECLARE @log_backup_freq_base64 NVARCHAR(MAX) = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	SET @params = N'configure_backup_schedule' + N' ' + @db_name_base64 + N' ' + @scheduling_option + N' ' + @full_backup_freq_type + N' ' + @days_of_week_base64 + N' ' + @backup_begin_time_base64 + N' ' + @backup_duration_base64 + N' ' + @log_backup_freq_base64
	EXEC managed_backup.sp_add_task_command @task_name='backup', @additional_params = @params
END
GO

IF OBJECT_ID ('managed_backup.sp_create_job', 'P') IS NOT NULL
BEGIN
	PRINT 'Dropping procedure managed_backup.sp_create_job...'
	DROP PROCEDURE managed_backup.sp_create_job;
END
GO

PRINT 'Creating procedure managed_backup.sp_create_job...'
GO
CREATE PROCEDURE managed_backup.sp_create_job
    @task_command		NVARCHAR(MAX),
    @task_job_id		UNIQUEIDENTIFIER = NULL OUTPUT,
    @task_job_step_id	UNIQUEIDENTIFIER = NULL OUTPUT
AS
BEGIN
    BEGIN TRANSACTION
    DECLARE @ReturnCode INT
    SELECT @ReturnCode = 0

    DECLARE @jobId BINARY(16)

    DECLARE @jobname NVARCHAR(MAX);
    SET @jobname = 'smart_admin_job_' + CONVERT(NVARCHAR(MAX), NEWID());

    EXEC @ReturnCode = msdb.dbo.sp_agent_add_job @job_name=@jobname, 
        @enabled = 1, 
        @delete_level = 0, 
        @description=N'smart_admin maintenance job.', 
        @job_id = @jobId OUTPUT
    
    IF (@@ERROR <> 0 OR @ReturnCode <> 0) 
    BEGIN
        GOTO QuitWithRollback
    END

    SET @task_job_id = @jobId;

    EXEC @ReturnCode = msdb.dbo.sp_agent_add_jobstep @job_id = @jobId, 
            @step_name=N'smart_admin job step', 
            @step_id=1, 
            @cmdexec_success_code=0, 
            @on_success_action=1, 
            @on_success_step_id=0, 
            @on_fail_action=2, 
            @on_fail_step_id=0, 
            @retry_attempts=0, 
            @retry_interval=0, 
            @os_run_priority=0, 
            @subsystem=N'smartadmin', 
            @command=@task_command, 
            @server=NULL, 
            @database_name=N'master', 
            @flags=48,
            @step_uid = @task_job_step_id OUTPUT

    IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

    COMMIT TRANSACTION
    GOTO EndSave
    QuitWithRollback:
    IF (@@TRANCOUNT > 0) 
    BEGIN
        ROLLBACK TRANSACTION
    END
    EndSave:
END
GO

IF OBJECT_ID ('managed_backup.sp_add_task_command', 'P') IS NOT NULL
BEGIN
	PRINT 'Dropping procedure managed_backup.sp_add_task_command...'
	DROP PROCEDURE managed_backup.sp_add_task_command;
END
GO

PRINT 'Creating procedure managed_backup.sp_add_task_command...'
GO

CREATE PROCEDURE managed_backup.sp_add_task_command 
    @task_name			NVARCHAR(50), 
    @additional_params	NVARCHAR(MAX),
    @cmd_output			NVARCHAR(MAX) = NULL OUTPUT
AS 
BEGIN
    SET NOCOUNT ON

    -- Check if SQL Agent is running
    DECLARE @agent_service_status INT
    SELECT @agent_service_status = [status]
    FROM sys.dm_server_services
    WHERE servicename like'%SQL Server Agent%'
    
    -- Status 4 is running - http://msdn.microsoft.com/en-us/library/hh204542.aspx
    IF(@agent_service_status <> 4)
    BEGIN
        RAISERROR (45201, 17, 1);
        RETURN
    END

    DECLARE @task_command NVARCHAR(MAX);
    DECLARE @job_id UNIQUEIDENTIFIER;
    DECLARE @step_uid UNIQUEIDENTIFIER;
    DECLARE @total_delay INT;

    SELECT @task_command = 
    CASE @task_name
        WHEN 'masterswitch' 
            THEN 'masterswitch'
        WHEN 'backup' 
            THEN 'smartbackup'
        ELSE 
            'other'
    END  
    + ' ' + @additional_params

    EXEC managed_backup.sp_create_job 
        @task_command=@task_command, 
        @task_job_id = @job_id OUTPUT, 
        @task_job_step_id = @step_uid OUTPUT
        
    IF (@@ERROR <> 0)
    BEGIN
        GOTO Quit
    END

    -- start system job
    EXEC dbo.sp_agent_start_job @job_id = @job_id;
        
    IF (@@ERROR <> 0)
    BEGIN
        GOTO Quit
    END
    
SET @total_delay = 0; 
WaitForJobFinish:
    IF NOT EXISTS (SELECT [message] 
                    FROM sys.fn_sqlagent_job_history(@job_id, 0)
                  )
    BEGIN
        IF (@total_delay > 480)
        BEGIN
            RAISERROR (45202, 17, 3, 120);
            GOTO Quit;
        END
        
        WAITFOR DELAY '00:00:00.250';
        SET @total_delay += 1;
        GOTO WaitForJobFinish;
    END

    DECLARE @job_output NVARCHAR(MAX)

    DECLARE @xml_output XML
    DECLARE @error INT
    DECLARE @state INT
    DECLARE @msg NVARCHAR(MAX)

    SET @cmd_output = ''

    DECLARE job_output_cursor CURSOR FOR
    SELECT [log_text] 
    FROM sys.fn_sqlagent_jobsteps_logs(@step_uid)
    ORDER BY date_created

    OPEN job_output_cursor

    FETCH NEXT FROM job_output_cursor
    INTO @job_output

    WHILE @@FETCH_STATUS = 0
    BEGIN
        SELECT @xml_output = CAST(@job_output AS XML)
        SELECT @error = @xml_output.value('(/Message/Error)[1]', 'INT')
        SELECT @state = @xml_output.value('(/Message/State)[1]', 'INT')
        SELECT @msg = @xml_output.value('xs:base64Binary((/Message/Text)[1])', 'VARBINARY(MAX)')
    
        IF @error IS NOT NULL AND @error <> 0
        BEGIN
            RAISERROR (45207, 17, @state, @msg);
        END
        ELSE
        BEGIN
            PRINT @msg
        END

        SET @cmd_output += @job_output + ' '
		
	FETCH NEXT FROM job_output_cursor
	INTO @job_output
    END

    CLOSE job_output_cursor
    DEALLOCATE job_output_cursor
    
    -- delete the system job now
    EXEC dbo.sp_agent_delete_job @job_id = @job_id, @is_system= 1

Quit:
END
GO

IF OBJECT_ID ('managed_backup.sp_backup_on_demand', 'P') IS NOT NULL
BEGIN
	PRINT 'Dropping procedure managed_backup.sp_backup_on_demand...'
	DROP PROCEDURE managed_backup.sp_backup_on_demand;
END
GO

PRINT 'Creating procedure managed_backup.sp_backup_on_demand...'
GO
-- Do a backup on-demand by piggybacking on Smart Backup's backup mechanism.
-- @type can be either 'DATABASE' or 'LOG'
--
CREATE PROCEDURE managed_backup.sp_backup_on_demand
	@database_name SYSNAME,
	@type NVARCHAR(32)
AS
BEGIN
 	IF NOT (HAS_PERMS_BY_NAME(null, null, 'ALTER ANY CREDENTIAL') = 1 AND 
            IS_ROLEMEMBER('db_backupoperator') = 1)
	BEGIN
	   RAISERROR(15247,-1,-1)	
	   RETURN;
	END
	SET NOCOUNT ON

	IF (@database_name IS NULL) AND (@database_name = N'')
	BEGIN
        RAISERROR (45204, 17 ,1, N'@database_name', N'database name');
		RETURN
	END

	IF (UPPER(@type) <> 'DATABASE') AND (UPPER(@type) <> 'LOG')
	BEGIN
        RAISERROR (45206, 17, 2);
		RETURN
	END

	DECLARE @db_name_base64 NVARCHAR(MAX);
	DECLARE @input VARBINARY(MAX);
	DECLARE @params NVARCHAR(MAX);

	SET @input = CONVERT(VARBINARY(MAX), @database_name)
	SELECT @db_name_base64 = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	SELECT @params = N'backup_on_demand'+ N' ' + @db_name_base64 + N' ' + @type
	EXEC managed_backup.sp_add_task_command @task_name='backup', @additional_params = @params
END
GO	

IF OBJECT_ID ('managed_backup.sp_set_parameter', 'P') IS NOT NULL
BEGIN
	PRINT 'Dropping procedure managed_backup.sp_set_parameter...'
	DROP PROCEDURE managed_backup.sp_set_parameter;
END
GO

PRINT 'Creating procedure managed_backup.sp_set_parameter...'
GO
-- Set the value of an internal system flag. These values govern the behavior of smart-backup algorithms.
--
CREATE PROCEDURE managed_backup.sp_set_parameter
	@parameter_name NVARCHAR(128),
	@parameter_value NVARCHAR(128)
AS
BEGIN
	SET NOCOUNT ON

    IF (@parameter_name IS NULL) OR (LEN(@parameter_name) = 0)
	BEGIN
        RAISERROR (45204, 17, 1, N'@parameter_name', N'parameter name');
        RETURN
	END

    IF (@parameter_value IS NULL) OR (LEN(@parameter_value) = 0)
	BEGIN
        RAISERROR (45204, 17, 2, N'@parameter_value', N'parameter value');
        RETURN
	END

	SET @parameter_name = LTRIM(RTRIM(@parameter_name))
	IF (CHARINDEX(N' ', @parameter_name) > 0)
	BEGIN
        RAISERROR (45212, 17, 3, N'@parameter_name', N'parameter name');
        RETURN
	END
	
	DECLARE @parameter_value_base64 NVARCHAR(MAX)
	DECLARE @input VARBINARY(MAX);

	SET @input = CONVERT(VARBINARY(MAX), @parameter_value)
	SELECT @parameter_value_base64 = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	DECLARE @params NVARCHAR (512)
	SELECT @params = 'configure_backup_params' + ' ' + @parameter_name + ' ' + @parameter_value_base64
	EXEC managed_backup.sp_add_task_command @task_name = 'backup', @additional_params = @params
END
GO

IF OBJECT_ID ('managed_backup.sp_get_backup_diagnostics', 'P') IS NOT NULL
BEGIN
	PRINT 'Dropping procedure managed_backup.sp_get_backup_diagnostics...'
	DROP PROCEDURE managed_backup.sp_get_backup_diagnostics;
END
GO

PRINT 'Creating procedure managed_backup.sp_get_backup_diagnostics...'
GO

CREATE PROCEDURE managed_backup.sp_get_backup_diagnostics
	@xevent_channel VARCHAR(255) = 'Xevent',
	@begin_time DATETIME = NULL,
	@end_time DATETIME = NULL
AS
BEGIN
	DECLARE @logpath NVARCHAR(MAX);
	SELECT TOP 1 @logpath = [path] FROM sys.dm_os_server_diagnostics_log_configurations
	SET @logpath = @logpath + '\ManagedBackupEvents_Backup*.xel';

	if (@end_time IS NULL)
	BEGIN	
		SELECT @end_time = GETUTCDATE()
	END
	
	if (@begin_time IS NULL)
	BEGIN
		SELECT @begin_time = DATEADD(minute, -30, @end_time)
	END

	SELECT *
	FROM
	(
		SELECT CAST(event_data AS XML).value('(event/@name)[1]','NVARCHAR(512)') AS event_type, 
			CAST(event_data AS XML).value('(event/data[@name="summary"]/value[text()])[1]', 'NVARCHAR(512)') AS event,
			CAST(event_data AS XML).value('(event/@timestamp)[1]', 'NVARCHAR(512)') AS timestamp
		FROM sys.fn_xe_file_target_read_file(@logpath, NULL, NULL, NULL)
	) t 
	WHERE timestamp >  @begin_time AND timestamp <= @end_time
	AND event_type LIKE '%'+@xevent_channel+'%'
	ORDER BY timestamp DESC
END
GO


IF OBJECT_ID ('managed_backup.sp_backup_master_switch', 'P') IS NOT NULL
BEGIN
	PRINT 'Dropping procedure managed_backup.sp_backup_master_switch...'
	DROP PROCEDURE managed_backup.sp_backup_master_switch;
END
GO

PRINT 'Creating procedure managed_backup.sp_backup_master_switch...'
GO

CREATE PROCEDURE managed_backup.sp_backup_master_switch 
	@new_state bit
AS
BEGIN
	IF NOT (HAS_PERMS_BY_NAME(null, null, 'ALTER ANY CREDENTIAL') = 1 AND 
            IS_ROLEMEMBER('db_backupoperator') = 1  AND
	    HAS_PERMS_BY_NAME('sp_delete_backuphistory', 'OBJECT', 'EXECUTE') = 1)
	BEGIN
	   RAISERROR(15247,-1,-1)	
	   RETURN;
	END

	IF @new_state IS NULL
	BEGIN
        RAISERROR (45204, 17, 1, N'@new_state', N'state for master switch');
		RETURN
	END

	EXEC managed_backup.sp_add_task_command @task_name = 'masterswitch', @additional_params = @new_state
END
GO

IF OBJECT_ID ('managed_backup.sp_get_encryption_option', 'P') IS NOT NULL
BEGIN
	PRINT 'Dropping procedure managed_backup.sp_get_encryption_option...'
	DROP PROCEDURE managed_backup.sp_get_encryption_option;
END
GO

PRINT 'Creating procedure managed_backup.sp_get_encryption_option...'
GO

CREATE PROCEDURE managed_backup.sp_get_encryption_option
	@encryption_algorithm SYSNAME, -- NULL for NO_ENCRYPTION
	@encryptor_type TINYINT, -- 0 = CERTIFICATE, 1 = ASYMMETRIC_KEY, NULL for NO_ENCRYPTION
	@encryptor_name SYSNAME, -- NULL for NO_ENCRYPTION
	@encryption_option NVARCHAR(MAX) = NULL OUTPUT
AS
	IF (@encryption_algorithm IS NULL)
	BEGIN
		IF (@encryptor_type IS NOT NULL) OR (@encryptor_name IS NOT NULL)
		BEGIN
			RAISERROR ('@encryptor_type and @encryptor_name should be NULL when doing backup without encryption. Cannot complete auto-admin query for database.', -- Message text
						17, -- Severity,
						6); -- State
			RETURN
		END
		SET @encryption_option = N''
	END
	ELSE
	BEGIN
		IF (@encryptor_type IS NULL) OR (@encryptor_name IS NULL)
		BEGIN
			RAISERROR ('@encryptor_type and @encryptor_name should be non-NULL when doing backup with encryption. Cannot complete auto-admin query for database.', -- Message text
						17, -- Severity,
						7); -- State
			RETURN
		END
		IF (@encryptor_type = 0)
		BEGIN
			SET @encryption_option = ', ENCRYPTION (ALGORITHM = ' + @encryption_algorithm + ', SERVER CERTIFICATE = [' + @encryptor_name + '])'
		END
		ELSE IF (@encryptor_type = 1)
		BEGIN
			SET @encryption_option = ', ENCRYPTION (ALGORITHM = ' + @encryption_algorithm + ', SERVER ASYMMETRIC KEY = [' + @encryptor_name + '])'
		END
		ELSE
		BEGIN
			RAISERROR ('@encryptor_type cannot have values other than 0 or 1. Cannot complete auto-admin query for database.', -- Message text
						17, -- Severity,
						8); -- State
			RETURN
		END
	END
GO

IF OBJECT_ID ('managed_backup.sp_get_striping_option', 'P') IS NOT NULL
BEGIN
	PRINT 'Dropping procedure managed_backup.sp_get_striping_option...'
	DROP PROCEDURE managed_backup.sp_get_striping_option;
END
GO

PRINT 'Creating procedure managed_backup.sp_get_striping_option...'
GO

CREATE PROCEDURE managed_backup.sp_get_striping_option
	@file_count			INT	=	1,
	@backup_file_path	NVARCHAR(512),
	@backup_locally		TINYINT,	-- 0 = To URL, 1 = To Disk
	@striping_option 	NVARCHAR(MAX) = NULL OUTPUT
AS
BEGIN
	IF((@file_count IS NULL) OR (@file_count <= 0))
	BEGIN
		RAISERROR ('@file_count should be non-NULL and greater than zero. Cannot complete managed backup operation.', -- Message text
				   17, -- Severity,
				   3); -- State
		RETURN
	END
	IF(@backup_file_path IS NULL)
	BEGIN
		RAISERROR ('@backup_file_path should be non-NULL for doing backup. Cannot complete managed backup operation.', -- Message text
				   17, -- Severity,
				   3); -- State
		RETURN
	END
	IF ((@backup_locally <> 1) AND (@backup_locally <> 0))
	BEGIN
		RAISERROR ('@backup_locally cannot have values other than 0 or 1. Cannot complete managed backup operation.', -- Message text
					17, -- Severity,
					2); -- State
		RETURN
	END
	ELSE
	BEGIN
		DECLARE @backup_target NVARCHAR(10)
		SET @backup_target = CASE WHEN @backup_locally = 0 THEN 'URL = N''' WHEN @backup_locally = 1 THEN 'DISK = N''' END
	
		IF ((@file_count = 1) OR (@file_count IS NULL))
		BEGIN
			SET	@striping_option = @backup_target + @backup_file_path + ''''
		END
		ELSE
		BEGIN
			DECLARE @ext NVARCHAR(10)
			IF (PATINDEX('%.log', @backup_file_path) > 0)
				SET @ext = '.log'
			ELSE
				SET @ext = '.bak'

			SET	@striping_option = @backup_target + REPLACE(@backup_file_path, @ext,'_1' + @ext) + ''''

			DECLARE @counter INT = 2

			WHILE (@counter <= @file_count)
			BEGIN
				DECLARE @replstr NVARCHAR(MAX)
					SET @replstr = '_' + CAST(@counter as NVARCHAR(MAX)) + @ext
					SET	@striping_option = @striping_option + ', ' + @backup_target + REPLACE(@backup_file_path, @ext, @replstr) + ''''

				SET @counter = @counter + 1
			END
		END
	END
END
GO

IF OBJECT_ID ('managed_backup.sp_do_backup', 'P') IS NOT NULL
BEGIN
	PRINT 'Dropping procedure managed_backup.sp_do_backup...'
	DROP PROCEDURE managed_backup.sp_do_backup;
END
GO

PRINT 'Creating procedure managed_backup.sp_do_backup...'
GO

CREATE PROCEDURE managed_backup.sp_do_backup 
	@db_name       			SYSNAME,
	@backup_type			TINYINT,	-- 0 = Database, 1 = Log
	@backup_locally			TINYINT,	-- 0 = To URL, 1 = To Disk
	@copy_only				TINYINT,	
	@backup_file_path		NVARCHAR(512),
	@credential_name		SYSNAME = NULL, -- NULL for B2BB
	@encryption_algorithm	SYSNAME, -- NULL for NO_ENCRYPTION
	@encryptor_type			TINYINT, -- 0 = CERTIFICATE, 1 = ASYMMETRIC_KEY, NULL for NO_ENCRYPTION
	@encryptor_name   		SYSNAME, -- NULL for NO_ENCRYPTION
	@file_count				INT = 1
AS
BEGIN
	IF (@db_name IS NULL)
	BEGIN
	RAISERROR ('@db_name should be non-NULL. Cannot complete auto-admin query for database.', -- Message text
               17, -- Severity,
               1); -- State
	RETURN
	END

	IF ((@backup_type <> 0) AND (@backup_type <> 1)) OR ((@backup_locally <> 0) AND (@backup_locally <> 1)) OR ((@copy_only <> 0) AND (@copy_only <> 1))
	BEGIN
	RAISERROR ('@backup_type, @backup_locally and @copy_only cannot have values other than 0 or 1. Cannot complete auto-admin query for database.', -- Message text
               17, -- Severity,
               2); -- State
	RETURN
	END

	IF(@backup_file_path IS NULL)
	BEGIN
	RAISERROR ('@backup_file_path should be non-NULL for doing backup. Cannot complete auto-admin query for database.', -- Message text
               17, -- Severity,
               3); -- State
	RETURN
	END

	IF (@copy_only = 1) AND (@backup_type <> 0)
	BEGIN
	RAISERROR ('Copy-only mode is only supported for full database backups. Cannot complete auto-admin query for database.', -- Message text
               17, -- Severity,
               5); -- State
	RETURN
	END
	IF((@file_count IS NULL) OR (@file_count <= 0))
	BEGIN
		RAISERROR ('@file_count should be non-NULL and greater than zero. Cannot complete managed backup operation.', -- Message text
				   17, -- Severity,
				   3); -- State
		RETURN
	END

	DECLARE @encryption_option NVARCHAR(MAX);
	EXEC managed_backup.sp_get_encryption_option @encryption_algorithm, @encryptor_type, @encryptor_name, @encryption_option OUTPUT
	
	DECLARE @striping_option NVARCHAR(MAX);
	EXEC managed_backup.sp_get_striping_option @file_count, @backup_file_path, @backup_locally, @striping_option OUTPUT

	DECLARE @backup_sql NVARCHAR(MAX);
	IF (@backup_locally = 1)
	BEGIN
		IF (@backup_type = 0) -- Database backup
		BEGIN
			IF (@copy_only = 1)
			BEGIN
				SET @backup_sql = 'BACKUP DATABASE @db_name TO ' + @striping_option + ' WITH STATS = 5, NAME = ''' + @db_name + '-Managed Backup'', COMPRESSION, COPY_ONLY' + @encryption_option
			END
			ELSE
			BEGIN
				SET @backup_sql = 'BACKUP DATABASE @db_name TO ' + @striping_option + ' WITH STATS = 5, NAME = ''' + @db_name + '-Managed Backup'', COMPRESSION' + @encryption_option
			END
		END
		ELSE If (@backup_type = 1) -- Log backup
		BEGIN
			SET @backup_sql = 'BACKUP LOG @db_name TO ' + @striping_option + ' WITH STATS = 5, NAME = ''' + @db_name + '-Managed Backup'', COMPRESSION' + @encryption_option
		END
		EXEC sp_executesql @backup_sql, N'@db_name SYSNAME, @backup_file_path NVARCHAR(512)', @db_name, @backup_file_path
	END
	ELSE
	BEGIN
		IF (@backup_type = 0) -- Database backup
		BEGIN
			IF (@copy_only = 1)
			BEGIN
				IF (@credential_name IS NULL) -- Perform B2BB backup
					BEGIN
						SET @backup_sql = 'BACKUP DATABASE @db_name TO ' + @striping_option + ' WITH STATS = 5, MAXTRANSFERSIZE = 4194304, NAME = ''' + @db_name + '-Managed Backup'', COMPRESSION, COPY_ONLY' + @encryption_option
					END
				ELSE 
					BEGIN
						SET @backup_sql = 'BACKUP DATABASE @db_name TO URL = @backup_file_path WITH CREDENTIAL = @credential_name, STATS = 5, NAME = ''' + @db_name + '-Smart Backup'', COMPRESSION, COPY_ONLY' + @encryption_option
					END
			END
			ELSE
			BEGIN
				IF (@credential_name IS NULL) -- Perform B2BB backup
					BEGIN
						SET @backup_sql = 'BACKUP DATABASE @db_name TO ' + @striping_option + ' WITH STATS = 5, MAXTRANSFERSIZE = 4194304, NAME = ''' + @db_name + '-Managed Backup'', COMPRESSION' + @encryption_option
					END
				ELSE
					BEGIN
						SET @backup_sql = 'BACKUP DATABASE @db_name TO URL = @backup_file_path WITH CREDENTIAL = @credential_name, STATS = 5, NAME = ''' + @db_name + '-Smart Backup'', COMPRESSION' + @encryption_option
					END
			END
		END
		ELSE If (@backup_type = 1) -- Log backup
		BEGIN
			IF (@credential_name IS NULL) -- Perform B2BB backup
				BEGIN
					SET @backup_sql = 'BACKUP LOG @db_name TO ' + @striping_option + ' WITH STATS = 5, MAXTRANSFERSIZE = 4194304, NAME = ''' + @db_name + '-Managed Backup'', COMPRESSION' + @encryption_option
				END
			ELSE
				BEGIN
					SET @backup_sql = 'BACKUP LOG @db_name TO URL = @backup_file_path WITH CREDENTIAL = @credential_name, STATS = 5, NAME = ''' + @db_name + '-Smart Backup'', COMPRESSION' + @encryption_option
				END
		END
		EXEC sp_executesql @backup_sql, N'@db_name SYSNAME, @backup_file_path NVARCHAR(512), @credential_name SYSNAME', @db_name, @backup_file_path, @credential_name
	END
END
GO

IF OBJECT_ID ('smart_admin.sp_create_job', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure smart_admin.sp_create_job...'
	DROP PROCEDURE smart_admin.sp_create_job;
END 
GO

PRINT 'Creating procedure smart_admin.sp_create_job...'
GO

CREATE PROCEDURE smart_admin.sp_create_job
    @task_command NVARCHAR(MAX),
    @task_job_id UNIQUEIDENTIFIER = NULL OUTPUT,
    @task_job_step_id UNIQUEIDENTIFIER = NULL OUTPUT
AS
	EXECUTE managed_backup.sp_create_job 
		@task_command, 
		@task_job_id OUTPUT, 
		@task_job_step_id OUTPUT

GO

IF OBJECT_ID ('smart_admin.sp_add_task_command', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure smart_admin.sp_add_task_command...'
	DROP PROCEDURE smart_admin.sp_add_task_command 
END
GO

PRINT 'Creating procedure smart_admin.sp_add_task_command...'
GO

CREATE PROCEDURE smart_admin.sp_add_task_command 
    @task_name			NVARCHAR(50), 
    @additional_params	NVARCHAR(MAX),
    @cmd_output			NVARCHAR(MAX) = NULL OUTPUT
AS 
BEGIN
	EXECUTE managed_backup.sp_add_task_command 
		@task_name, 
		@additional_params, 
		@cmd_output OUTPUT
END
GO

IF OBJECT_ID ('smart_admin.sp_set_db_backup', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure smart_admin.sp_set_db_backup...'
	DROP PROCEDURE smart_admin.sp_set_db_backup;
END 
GO

PRINT 'Creating procedure smart_admin.sp_set_db_backup...'
GO

-- Set backup paramaters for a database or configure instance level defaults
--
CREATE PROCEDURE smart_admin.sp_set_db_backup 
    @database_name			SYSNAME,
    @enable_backup			BIT = NULL,
    @storage_url			NVARCHAR(1024) = NULL,
    @retention_days			INT = NULL,
    @credential_name		SYSNAME = NULL, 
    @encryption_algorithm	SYSNAME = NULL,
    @encryptor_type			NVARCHAR(32) = NULL,
    @encryptor_name			SYSNAME = NULL
AS 
BEGIN
    IF NOT (HAS_PERMS_BY_NAME(null, null, 'ALTER ANY CREDENTIAL') = 1 AND 
            IS_ROLEMEMBER('db_backupoperator') = 1  AND
	    HAS_PERMS_BY_NAME('sp_delete_backuphistory', 'OBJECT', 'EXECUTE') = 1)
	BEGIN
	   RAISERROR(15247,-1,-1)	
	   RETURN;
	END

	SET NOCOUNT ON

    IF (@database_name IS NULL) OR (LEN(@database_name) = 0)
	BEGIN
        RAISERROR (45204, 17, 1, N'@database_name', N'database name');
        RETURN
	END

    IF (@storage_url IS NULL) AND (@retention_days IS NULL) AND (@credential_name IS NULL) AND (@enable_backup IS NULL) AND (@encryption_algorithm IS NULL)
	BEGIN
        RAISERROR (45205, 17, 2);
        RETURN
	END
	
	DECLARE @retention_str NVARCHAR(32)

	SET @storage_url = ISNULL(@storage_url, '')
	SET @credential_name = ISNULL(@credential_name, '')
	SET @retention_str = ISNULL(CAST(@retention_days AS NVARCHAR(32)), '') 
	SET @encryption_algorithm = ISNULL(@encryption_algorithm, '')
	SET @encryptor_name = ISNULL(@encryptor_name, '')
	SET @encryptor_type = ISNULL(@encryptor_type, '')
	
	DECLARE @db_name_base64 NVARCHAR(MAX);
	DECLARE @storage_url_base64 NVARCHAR(MAX);
	DECLARE @cred_name_base64 NVARCHAR(MAX);
	DECLARE @backup_setting NVARCHAR(1);
	DECLARE @input VARBINARY(MAX);
	DECLARE @params NVARCHAR(MAX);
	DECLARE @encryptor_name_base64 NVARCHAR(MAX);
	DECLARE @encryption_alg SYSNAME;
	DECLARE @encryptor_type_name SYSNAME;

	IF (@enable_backup IS NULL)
	BEGIN
        SET @backup_setting = '2'; -- 0 = Disable, 1 = Enable, 2 = Keep existing setting.
	END
	ELSE
	BEGIN
        SET @backup_setting = CAST(@enable_backup as NVARCHAR(1))
	END

	SET @encryption_alg = LTRIM(RTRIM(@encryption_algorithm))
 	IF (CHARINDEX(' ', @encryption_alg) > 0)
	BEGIN
		RAISERROR (45212, 17, 1, N'@encryption_algorithm', N'encryption algorithm');
		RETURN
	END

	SET @encryptor_type_name = LTRIM(RTRIM(@encryptor_type))
 	IF (CHARINDEX(' ', @encryptor_type_name) > 0)
	BEGIN
		RAISERROR (45212, 17, 1, N'@encryptor_type', N'encryptor type');
		RETURN
	END

	-- Encode @database_name in base64 format
	--
	SET @input = CONVERT(VARBINARY(MAX), @database_name)
	SELECT @db_name_base64 = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	-- Encode @storage_url in base64 format
	--
	SET @input = CONVERT(VARBINARY(MAX), @storage_url)
	SELECT @storage_url_base64 = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	-- Encode @credential_name in base64 format
	--
	SET @input = CONVERT(VARBINARY(MAX), @credential_name)
	SELECT @cred_name_base64 = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	-- Encode @encryptor_name in base64 format
	--
	SET @input = CONVERT(VARBINARY(MAX), @encryptor_name)
	SET @encryptor_name_base64 = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	SET @params = N'configure_backup'+ N' ' + @db_name_base64 + N' ' + @backup_setting + N' ' + @storage_url_base64 + N' ' + @retention_str + N' '
				+ @cred_name_base64 + N' ' + @encryption_alg + N' ' + @encryptor_type_name + N' ' + @encryptor_name_base64
	EXEC smart_admin.sp_add_task_command @task_name='backup', @additional_params = @params
END
GO

IF OBJECT_ID ('smart_admin.sp_backup_on_demand', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure smart_admin.sp_backup_on_demand...'
	DROP PROCEDURE smart_admin.sp_backup_on_demand;
END 
GO

PRINT 'Creating procedure smart_admin.sp_backup_on_demand...'
GO

-- Do a backup on-demand by piggybacking on Smart Backup's backup mechanism.
-- @type can be either 'DATABASE' or 'LOG'
--
CREATE PROCEDURE smart_admin.sp_backup_on_demand
	@database_name	SYSNAME,
	@type			NVARCHAR(32)
AS
BEGIN
	EXECUTE managed_backup.sp_backup_on_demand @database_name, @type
END

GO	

IF OBJECT_ID ('smart_admin.sp_set_instance_backup', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure smart_admin.sp_set_instance_backup...'
	DROP PROCEDURE smart_admin.sp_set_instance_backup;
END 
GO

PRINT 'Creating procedure smart_admin.sp_set_instance_backup...'
GO

CREATE PROCEDURE smart_admin.sp_set_instance_backup 
    @enable_backup BIT = NULL,
    @retention_days INT = NULL,
    @storage_url NVARCHAR(1024) = NULL,
    @credential_name SYSNAME = NULL, 
    @encryption_algorithm SYSNAME = NULL,
    @encryptor_type NVARCHAR(32) = NULL,
    @encryptor_name SYSNAME = NULL
AS 
BEGIN
    IF NOT (HAS_PERMS_BY_NAME(null, null, 'ALTER ANY CREDENTIAL') = 1 AND 
            IS_ROLEMEMBER('db_backupoperator') = 1  AND
	    HAS_PERMS_BY_NAME('sp_delete_backuphistory', 'OBJECT', 'EXECUTE') = 1)
        BEGIN
        RAISERROR(15247,-1,-1)
        RETURN;
        END

    SET NOCOUNT ON

    IF (@storage_url IS NULL) AND (@retention_days IS NULL) AND (@credential_name IS NULL) AND (@enable_backup IS NULL) AND (@encryption_algorithm IS NULL)
	BEGIN
        RAISERROR (45205, 17, 1);
        RETURN
	END
	
	DECLARE @retention_str NVARCHAR(32)

	SET @storage_url = ISNULL(@storage_url, '')
	SET @credential_name = ISNULL(@credential_name, '')
	SET @retention_str = ISNULL(CAST(@retention_days AS NVARCHAR(32)), '') 
	SET @encryption_algorithm = ISNULL(@encryption_algorithm, '')
	SET @encryptor_name = ISNULL(@encryptor_name, '')
	SET @encryptor_type = ISNULL(@encryptor_type, '')

	DECLARE @db_name_base64 NVARCHAR(MAX);
	DECLARE @storage_url_base64 NVARCHAR(MAX);
	DECLARE @cred_name_base64 NVARCHAR(MAX);
	DECLARE @backup_setting NVARCHAR(1);
	DECLARE @input VARBINARY(MAX);
	DECLARE @params NVARCHAR(MAX);
 	DECLARE @encryptor_name_base64 NVARCHAR(MAX);
	DECLARE @encryption_alg SYSNAME;
	DECLARE @encryptor_type_name SYSNAME;
 	
	IF (@enable_backup IS NULL)
	BEGIN
        SET @backup_setting = '2'; -- 0 = Disable, 1 = Enable, 2 = Keep existing setting.
	END
	ELSE
	BEGIN
        SET @backup_setting = CAST(@enable_backup as NVARCHAR(1))
	END

	SET @encryption_alg = LTRIM(RTRIM(@encryption_algorithm))
 	IF (CHARINDEX(' ', @encryption_alg) > 0)
	BEGIN
		RAISERROR (45212, 17, 1, N'@encryption_algorithm', N'encryption algorithm');
		RETURN
	END

	SET @encryptor_type_name = LTRIM(RTRIM(@encryptor_type))
 	IF (CHARINDEX(' ', @encryptor_type_name) > 0)
	BEGIN
		RAISERROR (45212, 17, 1, N'@encryptor_type', N'encryptor type');
		RETURN
	END

	-- When database name is specified as an empty string, Smart Backup configures 
	-- the instance-wide defaults with the supplied values.
	--
	SET @db_name_base64 = ''

	-- Encode @storage_url in base64 format
	--
	SET @input = CONVERT(VARBINARY(MAX), @storage_url)
	SELECT @storage_url_base64 = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	-- Encode @credential_name in base64 format
	--
	SET @input = CONVERT(VARBINARY(MAX), @credential_name)
	SELECT @cred_name_base64 = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	-- Encode @encryptor_name in base64 format
	--
	SET @input = CONVERT(VARBINARY(MAX), @encryptor_name)
	SET @encryptor_name_base64 = CAST(N'' as XML).value('xs:base64Binary(sql:variable("@input"))', 'NVARCHAR(MAX)')

	SET @params = N'configure_backup'+ N' ' + @db_name_base64 + N' ' + @backup_setting + N' ' + @storage_url_base64 + N' ' + @retention_str + N' '
				+ @cred_name_base64 + N' ' + @encryption_alg + N' ' + @encryptor_type_name + N' ' + @encryptor_name_base64
	
	EXEC smart_admin.sp_add_task_command @task_name='backup', @additional_params = @params
END
GO

IF OBJECT_ID ('smart_admin.sp_set_parameter', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure smart_admin.sp_set_parameter...'
	DROP PROCEDURE smart_admin.sp_set_parameter;
END 
GO

PRINT 'Creating procedure smart_admin.sp_set_parameter...'
GO

-- Set the value of an internal system flag. These values govern the behavior of smart-backup algorithms.
--
CREATE PROCEDURE smart_admin.sp_set_parameter
	@parameter_name NVARCHAR(128),
	@parameter_value NVARCHAR(128)
AS
BEGIN
	EXECUTE managed_backup.sp_set_parameter @parameter_name, @parameter_value
END

GO

IF OBJECT_ID ('smart_admin.sp_get_backup_diagnostics', 'P') IS NOT NULL
BEGIN
	PRINT 'Dropping procedure smart_admin.sp_get_backup_diagnostics...' 
	DROP PROCEDURE smart_admin.sp_get_backup_diagnostics;
END 
GO

PRINT 'Creating procedure smart_admin.sp_get_backup_diagnostics...'
GO

CREATE PROCEDURE smart_admin.sp_get_backup_diagnostics
	@xevent_channel VARCHAR(255) = 'Xevent',
	@begin_time DATETIME = NULL,
	@end_time DATETIME = NULL
AS
BEGIN
	EXECUTE managed_backup.sp_get_backup_diagnostics @xevent_channel, @begin_time, @end_time
END

GO

IF OBJECT_ID ('smart_admin.sp_backup_master_switch', 'P') IS NOT NULL
BEGIN
	PRINT 'Dropping procedure smart_admin.sp_backup_master_switch...'  
	DROP PROCEDURE smart_admin.sp_backup_master_switch;
END 
GO

PRINT 'Creating procedure smart_admin.sp_backup_master_switch...'
GO

CREATE PROCEDURE smart_admin.sp_backup_master_switch 
	@new_state bit
AS
BEGIN
	EXECUTE [managed_backup].[sp_backup_master_switch] @new_state
END
GO

IF OBJECT_ID ('smart_admin.sp_get_encryption_option', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure smart_admin.sp_get_encryption_option...'  
	DROP PROCEDURE smart_admin.sp_get_encryption_option;
END 
GO

PRINT 'Creating procedure smart_admin.sp_get_encryption_option...'
GO

CREATE PROCEDURE smart_admin.sp_get_encryption_option
	@encryption_algorithm	SYSNAME, -- NULL for NO_ENCRYPTION
	@encryptor_type			TINYINT, -- 0 = CERTIFICATE, 1 = ASYMMETRIC_KEY, NULL for NO_ENCRYPTION
	@encryptor_name			SYSNAME, -- NULL for NO_ENCRYPTION
	@encryption_option		NVARCHAR(MAX) = NULL OUTPUT
AS
BEGIN
	EXECUTE [managed_backup].[sp_get_encryption_option] @encryption_algorithm, 
		@encryptor_type, 
		@encryptor_name, 
		@encryption_option OUTPUT
END

GO

IF OBJECT_ID ('smart_admin.sp_do_backup', 'P') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure smart_admin.sp_do_backup...'  
	DROP PROCEDURE smart_admin.sp_do_backup;
END 
GO

PRINT 'Creating procedure smart_admin.sp_do_backup...'
GO

CREATE PROCEDURE smart_admin.sp_do_backup 
	@db_name       			SYSNAME,
	@backup_type			TINYINT,	-- 0 = Database, 1 = Log
	@backup_locally			TINYINT,	-- 0 = To URL, 1 = To Disk
	@copy_only				TINYINT,	
	@backup_file_path		NVARCHAR(512),
	@credential_name		SYSNAME = NULL, -- NULL for B2BB
	@encryption_algorithm	SYSNAME, -- NULL for NO_ENCRYPTION
	@encryptor_type			TINYINT, -- 0 = CERTIFICATE, 1 = ASYMMETRIC_KEY, NULL for NO_ENCRYPTION
	@encryptor_name   		SYSNAME -- NULL for NO_ENCRYPTION
AS
BEGIN
	EXECUTE managed_backup.sp_do_backup @db_name, 
		@backup_type, 
		@backup_locally, 
		@copy_only, 
		@backup_file_path, 
		@credential_name, 
		@encryption_algorithm, 
		@encryptor_type, 
		@encryptor_name
END

GO

IF OBJECT_ID ('managed_backup.fn_backup_db_config','TF') IS  NOT NULL
BEGIN
	PRINT 'Dropping function managed_backup.fn_backup_db_config...'   
	DROP FUNCTION managed_backup.fn_backup_db_config;
END 
GO

PRINT 'Creating function managed_backup.fn_backup_db_config...'
GO
-- Returns Smart Backup configuration details for a given database,
-- when @db_name is NULL or an empty string, info about all databases is returned.
--
CREATE FUNCTION managed_backup.fn_backup_db_config (@db_name SYSNAME) 
	RETURNS @t TABLE
		(
			db_name						SYSNAME,
			db_guid						UNIQUEIDENTIFIER,
			is_availability_database	BIT,
			is_dropped					BIT,
			is_managed_backup_enabled	BIT,
			container_url				NVARCHAR(1024),
			retention_days				INT,
			encryption_algorithm		SYSNAME NULL,
			encryptor_type				NVARCHAR(32) NULL,
			encryptor_name				SYSNAME NULL,
			local_cache_path			NVARCHAR(1024),
			scheduling_option			SYSNAME NULL,
			full_backup_freq_type		SYSNAME NULL,
			days_of_week				NVARCHAR(256),
			backup_begin_time			NVARCHAR(32),
			backup_duration				NVARCHAR(32),
			log_backup_freq				NVARCHAR(32)
		)
AS
BEGIN
	IF  (HAS_PERMS_BY_NAME(null, null, 'ALTER ANY CREDENTIAL') = 1 AND 
            IS_ROLEMEMBER('db_backupoperator') = 1  AND
	    HAS_PERMS_BY_NAME(null, null, 'VIEW ANY DEFINITION') = 1)
	BEGIN	
		SET @db_name = ISNULL(@db_name, '')

		INSERT INTO @t
		SELECT  
		aamd.db_name, 
		aamd.db_guid,
		CASE 
			WHEN aamd.group_db_guid IS NULL
			THEN CONVERT(BIT, 'false')
			ELSE CONVERT(BIT, 'true')
		END,
		CASE 
			WHEN aamd.drop_date IS NULL 
			THEN CONVERT(BIT, 'false')
			ELSE CONVERT(BIT, 'true')
		END,
		CONVERT(BIT, aatm.task_agent_data.value('(/DBBackupRecordV2/autoBackupSetting)[1]', 'nvarchar(32)')),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecordV2/containerURL)[1]', 'nvarchar(1024)'), ''),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecordV2/retentionPeriod)[1]', 'int'), 0),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecordV2/encryptionAlgorithm)[1]', 'nvarchar(128)'), ''),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecordV2/encryptorType)[1]', 'nvarchar(32)'), ''),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecordV2/encryptorName)[1]', 'nvarchar(128)'), ''),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecordV2/localCachePath)[1]', 'nvarchar(1024)'), ''),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecordV2/schedulingOption)[1]', 'nvarchar(128)'), ''),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecordV2/fullBackupFreqType)[1]', 'nvarchar(128)'), ''),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecordV2/daysOfWeek)[1]', 'nvarchar(256)'), ''),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecordV2/backupBeginTime)[1]', 'nvarchar(32)'), ''),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecordV2/backupDuration)[1]', 'nvarchar(32)'), ''),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecordV2/logBackupFreq)[1]', 'nvarchar(32)'), '')
		FROM autoadmin_managed_databases aamd 
		RIGHT OUTER JOIN autoadmin_task_agent_metadata aatm
		ON aamd.autoadmin_id = aatm.autoadmin_id
		WHERE 
		(
			QUOTENAME(@db_name) = QUOTENAME('') OR
			QUOTENAME(@db_name) = QUOTENAME(aamd.db_name)
		) AND
		(
			aatm.task_agent_data.exist('/DBBackupRecordV2') = 1
		)
		AND aamd.autoadmin_id <> 0
		
	END
	RETURN
END
GO

IF OBJECT_ID ('managed_backup.fn_backup_instance_config','TF') IS NOT NULL
BEGIN 
	PRINT 'Dropping function managed_backup.fn_backup_instance_config...'   
	DROP FUNCTION managed_backup.fn_backup_instance_config;
END 
GO

PRINT 'Creating function managed_backup.fn_backup_instance_config...'
GO
-- Returns the V2 instance configuration parameters
--
CREATE FUNCTION managed_backup.fn_backup_instance_config () 
	RETURNS @t TABLE
		(
			is_managed_backup_enabled	BIT,
			container_url				NVARCHAR(1024),
			retention_days				INT,
			encryption_algorithm		SYSNAME NULL,
			encryptor_type				NVARCHAR(32) NULL,
			encryptor_name				SYSNAME NULL,
			local_cache_path			NVARCHAR(1024),
			scheduling_option			SYSNAME NULL,
			full_backup_freq_type		SYSNAME NULL,
			days_of_week				NVARCHAR(256),
			backup_begin_time			NVARCHAR(32),
			backup_duration				NVARCHAR(32),
			log_backup_freq				NVARCHAR(32)
		)
AS
BEGIN
	IF  (HAS_PERMS_BY_NAME(null, null, 'ALTER ANY CREDENTIAL') = 1 AND 
            IS_ROLEMEMBER('db_backupoperator') = 1  AND
	    HAS_PERMS_BY_NAME(null, null, 'VIEW ANY DEFINITION') = 1)
	BEGIN		   
	    INSERT INTO @t
	    SELECT
	    CONVERT(BIT, task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";
		(/PD:AutoBackupGlobalDataV2/PD:defaultAutoBackupSetting)[1]', 'nvarchar(32)')),
	    NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
	    (/PD:AutoBackupGlobalDataV2/PD:defaultContainerUrl)[1]', 'nvarchar(1024)'), ''),
	    NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";
		(/PD:AutoBackupGlobalDataV2/PD:defaultRetentionPeriod)[1]', 'int'), 0),
	    NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
		(/PD:AutoBackupGlobalDataV2/PD:defaultEncryptionAlgorithm)[1]', 'nvarchar(128)'), ''),
	    NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
		(/PD:AutoBackupGlobalDataV2/PD:defaultEncryptorType)[1]', 'nvarchar(32)'), ''),
	    NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
		(/PD:AutoBackupGlobalDataV2/PD:defaultEncryptorName)[1]', 'nvarchar(128)'), ''),
		NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
	    (/PD:AutoBackupGlobalDataV2/PD:defaultLocalCachePath)[1]', 'nvarchar(1024)'), ''),
		NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
		(/PD:AutoBackupGlobalDataV2/PD:defaultSchedulingOption)[1]', 'nvarchar(128)'), ''),
		NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
		(/PD:AutoBackupGlobalDataV2/PD:defaultFullBackupFreqType)[1]', 'nvarchar(128)'), ''),
		NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
		(/PD:AutoBackupGlobalDataV2/PD:defaultDaysOfWeek)[1]', 'nvarchar(256)'), ''),
		NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
		(/PD:AutoBackupGlobalDataV2/PD:defaultBackupBeginTime)[1]', 'nvarchar(32)'), ''),
		NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
		(/PD:AutoBackupGlobalDataV2/PD:defaultBackupDuration)[1]', 'nvarchar(32)'), ''),
		NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
		(/PD:AutoBackupGlobalDataV2/PD:defaultLogBackupFreq)[1]', 'nvarchar(32)'), '')
		FROM autoadmin_task_agent_metadata
		WHERE autoadmin_id = 0 

		IF NOT EXISTS(SELECT TOP 1 1 FROM @t)
		BEGIN
			INSERT INTO @t VALUES (NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL)
		END
	END
	RETURN
END
GO

IF OBJECT_ID ('managed_backup.fn_is_master_switch_on','FN') IS NOT NULL
BEGIN
	PRINT 'Dropping function managed_backup.fn_is_master_switch_on...'    
	DROP FUNCTION managed_backup.fn_is_master_switch_on;
END 
GO

PRINT 'Creating function managed_backup.fn_is_master_switch_on...'
GO
-- Returns the present state of the Smart Admin master switch.
-- 1 = ON, 0 = OFF. When the switch is OFF, all Smart Admin services are paused.
--
CREATE FUNCTION managed_backup.fn_is_master_switch_on () 
	RETURNS BIT
AS
BEGIN
	DECLARE @state BIT
	
	SELECT 	@state = [state] FROM autoadmin_master_switch
	
	IF @state IS NULL
	BEGIN
		SET @state = 1
	END

	RETURN @state
END
GO

IF OBJECT_ID ('managed_backup.fn_get_parameter','TF') IS NOT NULL
BEGIN 
	PRINT 'Dropping function managed_backup.fn_get_parameter...'    
	DROP FUNCTION managed_backup.fn_get_parameter;
END 
GO

PRINT 'Creating function managed_backup.fn_get_parameter...'
GO
CREATE FUNCTION managed_backup.fn_get_parameter(@parameter_name NVARCHAR(128))
       RETURNS @t table
       (
              parameter_name       NVARCHAR(128),
              parameter_value      NVARCHAR(MAX)
       )
AS
BEGIN
       SET @parameter_name = ISNULL(@parameter_name, '')

       INSERT INTO @t
       SELECT name, value 
       FROM autoadmin_system_flags
       WHERE 
       (
              QUOTENAME(@parameter_name) = QUOTENAME(N'') OR
              QUOTENAME(@parameter_name) = QUOTENAME(name)
       )

       RETURN
END
GO

IF OBJECT_ID ('managed_backup.fn_available_backups','TF') IS NOT NULL
BEGIN 
	PRINT 'Dropping function managed_backup.fn_available_backups...'    
	DROP FUNCTION managed_backup.fn_available_backups;
END 
GO

PRINT 'Creating function managed_backup.fn_available_backups...'
GO

CREATE FUNCTION managed_backup.fn_available_backups
                 ( @database_name NVARCHAR(512))
	RETURNS  @t TABLE(
		backup_path				NVARCHAR(260) COLLATE Latin1_General_CI_AS_KS_WS,
		backup_type				NVARCHAR(6),
		expiration_date			DATETIME,
		database_guid			UNIQUEIDENTIFIER,	
		first_lsn				NUMERIC(25, 0), 
		last_lsn				NUMERIC(25, 0), 
		backup_start_date		DATETIME,
		backup_finish_date		DATETIME,
		machine_name			NVARCHAR(128) NULL,
		last_recovery_fork_id	UNIQUEIDENTIFIER, --last_recovery_fork_id in backupset
		first_recovery_fork_id	UNIQUEIDENTIFIER NULL,
		fork_point_lsn			NUMERIC(25, 0) NULL,
		availability_group_guid UNIQUEIDENTIFIER NULL -- this is for Hadron
		Unique Clustered (database_guid, backup_start_date, first_lsn, backup_type, backup_path)
	)
AS
BEGIN
	-- helper to decide whether lsn is continuous
	DECLARE @logsWithRowNumber TABLE
	       (
		log_backup_id			INT,			
		backup_path				NVARCHAR(260) COLLATE Latin1_General_CI_AS_KS_WS,
		backup_type				NVARCHAR(6),
		expiration_date			DATETIME,
		database_guid			UNIQUEIDENTIFIER,	
		first_lsn				NUMERIC(25, 0), 
		last_lsn				NUMERIC(25, 0), 
		backup_start_date		DATETIME,
		backup_finish_date		DATETIME,
		machine_name			NVARCHAR(128) NULL,
		last_recovery_fork_id	UNIQUEIDENTIFIER, --last_recovery_fork_id in backupset
		first_recovery_fork_id	UNIQUEIDENTIFIER NULL,
		fork_point_lsn			NUMERIC(25, 0) NULL,
		availability_group_guid UNIQUEIDENTIFIER NULL, -- this is for Hadron
		adjusted_db_guid        UNIQUEIDENTIFIER NULL-- this is for Hadron
	)

	--existing backup files
	INSERT INTO @t SELECT 
	    	'https://' + backup_path AS backup_path, 
		CASE WHEN backup_type = 1 THEN 'DB' ELSE 'Log' END AS backup_type,
		expiration_date,
		database_guid,
		first_lsn,
		last_lsn,
		backup_start_date,
		backup_finish_date,
		machine_name,
		first_recovery_fork_id,
		last_recovery_fork_id,
		fork_point_lsn,
		availability_group_guid
	FROM smart_backup_files
	WHERE database_name = @database_name
		AND (status = 'A' OR status = 'U') 

	-- populate the helper table
	INSERT INTO @logsWithRowNumber
	SELECT 
		row_number() OVER (PARTITION BY adjusted_db_guid ORDER BY first_lsn) AS log_backup_id,
		* 
	FROM 
	(SELECT
		*,
		CASE WHEN availability_group_guid = '00000000-0000-0000-0000-000000000000' THEN database_guid 
			WHEN availability_group_guid is NULL THEN database_guid
			ELSE availability_group_guid END as adjusted_db_guid
	FROM @t) temp
	WHERE backup_type = 'Log' 

	-- insert gap rows
	INSERT into @t
	SELECT 'Broken_Chain_' + CONVERT(NVARCHAR(64), t1.last_lsn) + '_to_' + CONVERT(NVARCHAR(64), t2.first_lsn) AS backup_path, 
	'Log', 
	CONVERT(DateTime, '9999-12-31 23:59:59.000') AS expiration_date, 
	t1.database_guid AS database_guid, 
	t1.last_lsn, 
	t2.first_lsn, 
	t1.backup_finish_date, 
	t2.backup_start_date, 
	t1.machine_name, 
	NULL, 
	NULL, 
	NULL, 
	t1.availability_group_guid
	FROM @logsWithRowNumber t1 
		JOIN @logsWithRowNumber t2 ON t1.log_backup_id = t2.log_backup_id - 1 
			AND t1.adjusted_db_guid = t2.adjusted_db_guid
	WHERE t1.last_lsn != t2.first_lsn AND t1.first_lsn != t2.first_lsn

	RETURN
END
GO

IF OBJECT_ID ('managed_backup.fn_get_current_xevent_settings','TF') IS NOT NULL
BEGIN
	PRINT 'Dropping function managed_backup.fn_get_current_xevent_settings...'    
	DROP FUNCTION managed_backup.fn_get_current_xevent_settings;
END 
GO

PRINT 'Creating function managed_backup.fn_get_current_xevent_settings...'
GO

CREATE FUNCTION managed_backup.fn_get_current_xevent_settings()
	RETURNS  @t TABLE(
		event_name	NVARCHAR(128),
		is_configurable	NVARCHAR(128),
		is_enabled	NVARCHAR(128)
		)
AS
BEGIN
DECLARE @XEventNames TABLE
	(
	event_name NVARCHAR(128),
	configurable NVARCHAR(128)
	)

	INSERT INTO @t VALUES ('SSMBackup2WAAdminXevent', 'false', 'true')
	INSERT INTO @t VALUES ('SSMBackup2WAOperationalXevent', 'false', 'true')
	INSERT INTO @t VALUES ('SSMBackup2WAAnalyticXevent', 'false', 'true')
	INSERT INTO @t VALUES ('FileRetentionAdminXevent', 'false', 'true')
	INSERT INTO @t VALUES ('FileRetentionAnalyticXevent', 'false', 'true')
	INSERT INTO @XEventNames VALUES ('SSMBackup2WADebugXevent', 'true')
	INSERT INTO @XEventNames VALUES ('FileRetentionOperationalXevent', 'true')
	INSERT INTO @XEventNames VALUES ('FileRetentionDebugXevent', 'true')
	INSERT INTO @XEventNames VALUES ('StorageOperationDebugXevent', 'true')
	
	INSERT INTO @t
	SELECT event_name, configurable,
	CASE  WHEN value IS NULL THEN 'false' ELSE value END AS IsEnabled
	FROM
	(
		SELECT *
		FROM @XEventNames t1 LEFT JOIN
	     autoadmin_system_flags t2 ON t1.event_name = t2.name
	) t
	RETURN
END
GO

IF OBJECT_ID ('managed_backup.fn_get_health_status','TF') IS NOT NULL
BEGIN 
	PRINT 'Dropping function managed_backup.fn_get_health_status...'
	DROP FUNCTION managed_backup.fn_get_health_status;
END 
GO

PRINT 'Creating function managed_backup.fn_get_health_status...'
GO

--Look at a period of time, report aggregated number of several type of errors
--When @begin_time and @end_time are not specified, by default look at events in last 30 minutes
--
CREATE FUNCTION managed_backup.fn_get_health_status (
	@begin_time DATETIME = NULL,
	@end_time DATETIME = NULL
) 
RETURNS @t TABLE(
	number_of_storage_connectivity_errors int,
	number_of_sql_errors int,
	number_of_invalid_credential_errors int,
	number_of_other_errors int,
	number_of_corrupted_or_deleted_backups int,
	number_of_backup_loops int,
	number_of_retention_loops int
	)
AS 
BEGIN 
	DECLARE @logpath NVARCHAR(MAX);
	SELECT TOP 1 @logpath = [path] FROM sys.dm_os_server_diagnostics_log_configurations
	SET @logpath = @logpath + '\ManagedBackupEvents_Backup*.xel';
	DECLARE @adminAndAnalyticXevents TABLE
	(
	event_name NVARCHAR(512),
	event_type int,
	error_code int,
	timestamp DATETIME
	)

	if (@end_time IS NULL)
	BEGIN	
		SELECT @end_time = GETUTCDATE()
	END
	
	if (@begin_time IS NULL)
	BEGIN
		SELECT @begin_time = DATEADD(minute, -30, @end_time)
	END

	--Find most recent analytic events
	INSERT INTO @adminAndAnalyticXevents
	SELECT event_name, event_type, error_code, timestamp
	FROM
	(
		SELECT CAST(event_data AS XML).value('(event/@name)[1]','NVARCHAR(512)') AS event_name, 
			CASE WHEN CAST(event_data AS XML).value('(event/@name)[1]','NVARCHAR(512)') LIKE 'SSMBackup2WA%'		
				THEN
					CAST(event_data AS XML).value('(event/data[@name="error_code"]/value[text()])[1]', 'NVARCHAR(512)') 
				ELSE 
					CAST(event_data AS XML).value('(event/data[@name="event_type"]/value[text()])[1]', 'NVARCHAR(512)')  
				END AS event_type,
			CAST(event_data AS XML).value('(event/data[@name="error_code"]/value[text()])[1]', 'NVARCHAR(512)') 
				AS error_code,
			CAST(event_data AS XML).value('(event/@timestamp)[1]', 'NVARCHAR(512)') AS timestamp
		FROM sys.fn_xe_file_target_read_file(@logpath, NULL, NULL, NULL)
	) t 
	WHERE  (event_name LIKE '%Admin%' OR event_name LIKE '%Analytic%') 
 	AND timestamp >= @begin_time AND timestamp <= @end_time

	DECLARE @numberOfStorageErrors int
	DECLARE @numberOfSqlErrorsFromMainLoop int
	DECLARE @numberOfSqlErrorsFromRetention int	
	DECLARE @numberOfCorruptedOrDeletedBackups int
	DECLARE @numberOfCredentialErrorsFromMainLoop int
	DECLARE @numberOfCredentialErrorsFromRetention int
	DECLARE @numberOfTotalErrors int
	DECLARE @numberOfBackupLoops int
	DECLARE @numberOfRetentionLoops int

	SELECT @numberOfStorageErrors = COUNT(*)
	FROM @adminAndAnalyticXevents
	WHERE event_name = 'FileRetentionAdminXevent' AND event_type = 1 --xstoreError
	AND timestamp >= @begin_time AND timestamp <= @end_time

	-- 10107 = Smart Backup internal error. 
	-- 3288 = SQL Error due to invalid credential, we report them in a separate category.
	--
	SELECT @numberOfSqlErrorsFromMainLoop = COUNT(*)
	FROM @adminAndAnalyticXevents
	WHERE event_name = 'SSMBackup2WAAdminXevent' and error_code != 10107 and error_code != 3288  
	AND timestamp >= @begin_time AND timestamp <= @end_time

	SELECT @numberOfSqlErrorsFromRetention= COUNT(*)
	FROM @adminAndAnalyticXevents
	WHERE event_name = 'FileRetentionAdminXevent' AND event_type = 0 --sqlError
	AND timestamp >= @begin_time AND timestamp <= @end_time

	SELECT @numberOfCredentialErrorsFromRetention = COUNT(*)
	FROM @adminAndAnalyticXevents
	WHERE event_name = 'FileRetentionAdminXevent' AND event_type = 2 --InvalidCredential
	AND timestamp >= @begin_time AND timestamp <= @end_time

	SELECT @numberOfCredentialErrorsFromMainLoop = COUNT(*)
	FROM @adminAndAnalyticXevents
	WHERE event_name = 'SSMBackup2WAAdminXevent' and error_code = 3288 --InvalidCredential SQL Error code
	AND timestamp >= @begin_time AND timestamp <= @end_time

	SELECT @numberOfCorruptedOrDeletedBackups = COUNT(*)
	FROM @adminAndAnalyticXevents
	WHERE event_name = 'FileRetentionAdminXevent' AND event_type = 5 --CORRUPTEDORDELETED FILE
    	AND timestamp >= @begin_time AND timestamp <= @end_time

	SELECT @numberOfTotalErrors = COUNT(*)
	FROM @adminAndAnalyticXevents
	WHERE event_name LIKE '%Admin%'	
        AND timestamp >= @begin_time AND timestamp <= @end_time

	SELECT @numberOfRetentionLoops = COUNT(*)
	FROM @adminAndAnalyticXevents
	WHERE event_name = 'FileRetentionAnalyticXevent'
	AND timestamp >= @begin_time AND timestamp <= @end_time

	SELECT @numberOfBackupLoops = COUNT(*)
	FROM @adminAndAnalyticXevents
	WHERE event_name = 'SSMBackup2WAAnalyticXevent'
	AND timestamp >= @begin_time AND timestamp <= @end_time

	INSERT INTO @t Values(@numberOfStorageErrors, 
		@numberOfSqlErrorsFromMainLoop + @numberOfSqlErrorsFromRetention,
		@numberOfCredentialErrorsFromMainLoop + @numberOfCredentialErrorsFromRetention,
		@numberOfTotalErrors - (@numberOfStorageErrors + @numberOfSqlErrorsFromMainLoop + @numberOfSqlErrorsFromRetention + @numberOfCredentialErrorsFromMainLoop + @numberOfCredentialErrorsFromRetention + @numberOfCorruptedOrDeletedBackups),
		@numberOfCorruptedOrDeletedBackups,
		@numberOfBackupLoops,
		@numberOfRetentionLoops 
	)
	RETURN
END
GO

IF OBJECT_ID ('smart_admin.fn_backup_db_config','TF') IS NOT NULL
BEGIN 
	PRINT 'Dropping function smart_admin.fn_backup_db_config...'
	DROP FUNCTION smart_admin.fn_backup_db_config;
END 
GO

PRINT 'Creating function smart_admin.fn_backup_db_config...'
GO
-- Returns Smart Backup configuration details for a given database,
-- when @db_name is NULL or an empty string, info about all databases is returned.
--
CREATE FUNCTION smart_admin.fn_backup_db_config (@db_name SYSNAME) 
	RETURNS @t TABLE
		(
			db_name						SYSNAME,
			db_guid						UNIQUEIDENTIFIER,
			is_availability_database	BIT,
			is_dropped					BIT,
			is_managed_backup_enabled	BIT,
			credential_name				SYSNAME NULL,
			retention_days				INT,
			storage_url					NVARCHAR(1024),
			encryption_algorithm		SYSNAME NULL,
			encryptor_type				NVARCHAR(32) NULL,
			encryptor_name				SYSNAME NULL
		)
AS
BEGIN
	IF  (HAS_PERMS_BY_NAME(null, null, 'ALTER ANY CREDENTIAL') = 1 AND 
            IS_ROLEMEMBER('db_backupoperator') = 1  AND
	    HAS_PERMS_BY_NAME(null, null, 'VIEW ANY DEFINITION') = 1)
	BEGIN	
	   
		SET @db_name = ISNULL(@db_name, '')

		INSERT INTO @t
		SELECT  
		aamd.db_name, 
		aamd.db_guid,
		CASE 
			WHEN aamd.group_db_guid IS NULL
			THEN CONVERT(BIT, 'false')
			ELSE CONVERT(BIT, 'true')
		END,
		CASE 
			WHEN aamd.drop_date IS NULL 
			THEN CONVERT(BIT, 'false')
			ELSE CONVERT(BIT, 'true')
		END,
		CONVERT(BIT, aatm.task_agent_data.value('(/DBBackupRecord/autoBackupSetting)[1]', 'nvarchar(32)')),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecord/credentialName)[1]', 'nvarchar(128)'), ''),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecord/retentionPeriod)[1]', 'int'), 0),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecord/URL)[1]', 'nvarchar(128)'), ''),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecord/encryptionAlgorithm)[1]', 'nvarchar(128)'), ''),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecord/encryptorType)[1]', 'nvarchar(32)'), ''),
		NULLIF(aatm.task_agent_data.value('(/DBBackupRecord/encryptorName)[1]', 'nvarchar(128)'), '')
		FROM autoadmin_managed_databases aamd 
		RIGHT OUTER JOIN autoadmin_task_agent_metadata aatm
		ON aamd.autoadmin_id = aatm.autoadmin_id
		WHERE 
		(
			QUOTENAME(@db_name) = QUOTENAME('') OR
			QUOTENAME(@db_name) = QUOTENAME(aamd.db_name)
		) AND
		(
			aatm.task_agent_data.exist('/DBBackupRecord') = 1
		)
		AND aamd.autoadmin_id <> 0
	END
	RETURN
END
GO

IF OBJECT_ID ('smart_admin.fn_backup_instance_config','TF') IS NOT NULL
BEGIN 
	PRINT 'Dropping function smart_admin.fn_backup_instance_config...'
	DROP FUNCTION smart_admin.fn_backup_instance_config;
END 
GO

PRINT 'Creating function smart_admin.fn_backup_instance_config...'
GO
-- Returns the V1 instance configuration parameters
--
CREATE FUNCTION smart_admin.fn_backup_instance_config () 
	RETURNS @t TABLE
		(
			is_managed_backup_enabled	BIT,
			credential_name				SYSNAME NULL,
			retention_days				INT,
			storage_url					NVARCHAR(1024) NULL,
			encryption_algorithm		SYSNAME NULL,
			encryptor_type				NVARCHAR(32) NULL,
			encryptor_name				SYSNAME NULL
		)
AS
BEGIN
	IF  (HAS_PERMS_BY_NAME(null, null, 'ALTER ANY CREDENTIAL') = 1 AND 
            IS_ROLEMEMBER('db_backupoperator') = 1  AND
	    HAS_PERMS_BY_NAME(null, null, 'VIEW ANY DEFINITION') = 1)
	BEGIN		   
	    INSERT INTO @t
	    SELECT
	    CONVERT(BIT, task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";
		(/PD:AutoBackupGlobalData/PD:defaultAutoBackupSetting)[1]', 'nvarchar(32)')),
	    NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
	    	(/PD:AutoBackupGlobalData/PD:defaultCredentialName)[1]', 'nvarchar(128)'), ''),
	    NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";
		(/PD:AutoBackupGlobalData/PD:defaultRetentionPeriod)[1]', 'int'), 0),
	    NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
		(/PD:AutoBackupGlobalData/PD:defaultURL)[1]', 'nvarchar(1024)'), ''),
	    NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
		(/PD:AutoBackupGlobalData/PD:defaultEncryptionAlgorithm)[1]', 'nvarchar(128)'), ''),
	    NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
		(/PD:AutoBackupGlobalData/PD:defaultEncryptorType)[1]', 'nvarchar(32)'), ''),
	    NULLIF(task_agent_data.value('declare namespace PD="http://schemas.datacontract.org/2004/07/Microsoft.SqlServer.SmartAdmin.SmartBackupAgent";  
		(/PD:AutoBackupGlobalData/PD:defaultEncryptorName)[1]', 'nvarchar(128)'), '')
		FROM autoadmin_task_agent_metadata
		WHERE autoadmin_id = 0

		IF NOT EXISTS(SELECT TOP 1 1 FROM @t)
		BEGIN
			INSERT INTO @t VALUES (NULL, NULL, NULL, NULL, NULL, NULL, NULL)
		END
	END
	RETURN
END
GO

IF OBJECT_ID ('smart_admin.fn_is_master_switch_on','FN') IS NOT NULL
BEGIN 
	PRINT 'Dropping function smart_admin.fn_is_master_switch_on...'
	DROP FUNCTION smart_admin.fn_is_master_switch_on;
END 
GO

PRINT 'Creating function smart_admin.fn_is_master_switch_on...'
GO

-- Returns the present state of the Smart Admin master switch.
-- 1 = ON, 0 = OFF. When the switch is OFF, all Smart Admin services are paused.
--
CREATE FUNCTION smart_admin.fn_is_master_switch_on () 
	RETURNS BIT
AS
BEGIN
	DECLARE @state BIT
	
	EXEC @state = [managed_backup].[fn_is_master_switch_on]
	
	RETURN @state
END
GO

IF OBJECT_ID ('smart_admin.fn_get_parameter','TF') IS NOT NULL
BEGIN 
	PRINT 'Dropping function smart_admin.fn_get_parameter...'
	DROP FUNCTION smart_admin.fn_get_parameter;
END 
GO

PRINT 'Creating function smart_admin.fn_get_parameter...'
GO

CREATE FUNCTION smart_admin.fn_get_parameter(@parameter_name NVARCHAR(128))
       RETURNS @t table
       (
              parameter_name       NVARCHAR(128),
              parameter_value      NVARCHAR(MAX)
       )
AS
BEGIN
       INSERT INTO @t
       SELECT parameter_name, parameter_value 
       FROM managed_backup.fn_get_parameter (@parameter_name)

       RETURN
END
GO

IF OBJECT_ID ('smart_admin.fn_available_backups','TF') IS NOT NULL
BEGIN 
	PRINT 'Dropping function smart_admin.fn_available_backups...'
	DROP FUNCTION smart_admin.fn_available_backups;
END 
GO

PRINT 'Creating function smart_admin.fn_available_backups...'
GO
CREATE FUNCTION smart_admin.fn_available_backups
                 ( @database_name NVARCHAR(512))
	RETURNS  @t TABLE(
		backup_path				NVARCHAR(260) COLLATE Latin1_General_CI_AS_KS_WS,
		backup_type				NVARCHAR(6),
		expiration_date			DATETIME,
		database_guid			UNIQUEIDENTIFIER,	
		first_lsn				NUMERIC(25, 0), 
		last_lsn				NUMERIC(25, 0), 
		backup_start_date		DATETIME,
		backup_finish_date		DATETIME,
		machine_name			NVARCHAR(128) NULL,
		last_recovery_fork_id	UNIQUEIDENTIFIER, --last_recovery_fork_id in backupset
		first_recovery_fork_id	UNIQUEIDENTIFIER NULL,
		fork_point_lsn			NUMERIC(25, 0) NULL,
		availability_group_guid UNIQUEIDENTIFIER NULL -- this is for Hadron
		Unique Clustered (database_guid, backup_start_date, first_lsn, backup_type)
	)
AS
BEGIN

	INSERT INTO @t 
	SELECT backup_path, backup_type, expiration_date, database_guid, first_lsn, last_lsn, backup_start_date, backup_finish_date, 
		machine_name, last_recovery_fork_id, first_recovery_fork_id, fork_point_lsn, availability_group_guid
	FROM managed_backup.fn_available_backups (@database_name)

	RETURN
END
GO

IF OBJECT_ID ('smart_admin.fn_get_current_xevent_settings','TF') IS NOT NULL
BEGIN 
	PRINT 'Dropping function smart_admin.fn_get_current_xevent_settings...'
	DROP FUNCTION smart_admin.fn_get_current_xevent_settings;
END 
GO

PRINT 'Creating function smart_admin.fn_get_current_xevent_settings...'
GO

CREATE FUNCTION smart_admin.fn_get_current_xevent_settings()
	RETURNS  @t TABLE(
		event_name	NVARCHAR(128),
		is_configurable	NVARCHAR(128),
		is_enabled	NVARCHAR(128)
		)
AS
BEGIN
	INSERT INTO @t
	SELECT event_name, is_configurable, is_enabled
	FROM managed_backup.fn_get_current_xevent_settings()

	RETURN
END
GO

IF OBJECT_ID ('smart_admin.fn_get_health_status','TF') IS NOT NULL
BEGIN
	PRINT 'Dropping function smart_admin.fn_get_health_status...' 
	DROP FUNCTION smart_admin.fn_get_health_status
END
GO

PRINT 'Creating function smart_admin.fn_get_health_status...'
GO

--Look at a period of time, report aggregated number of several type of errors
--When @begin_time and @end_time are not specified, by default look at events in last 30 minutes
--
CREATE FUNCTION smart_admin.fn_get_health_status (
	@begin_time DATETIME = NULL,
	@end_time DATETIME = NULL
) 
RETURNS @t TABLE(
	number_of_storage_connectivity_errors int,
	number_of_sql_errors int,
	number_of_invalid_credential_errors int,
	number_of_other_errors int,
	number_of_corrupted_or_deleted_backups int,
	number_of_backup_loops int,
	number_of_retention_loops int
	)
AS 
BEGIN 

	INSERT INTO @t 
	SELECT 
		number_of_storage_connectivity_errors
		,number_of_sql_errors
		,number_of_invalid_credential_errors
		,number_of_other_errors
		,number_of_corrupted_or_deleted_backups
		,number_of_backup_loops
		,number_of_retention_loops
	FROM managed_backup.fn_get_health_status (@begin_time, @end_time)
	RETURN
END
GO

IF OBJECT_ID ('vw_autoadmin_health_status', 'V') IS NOT NULL
BEGIN 
	PRINT 'Dropping view vw_autoadmin_health_status...' 
	DROP VIEW vw_autoadmin_health_status
END 
GO

PRINT 'Creating view vw_autoadmin_health_status...'
GO

CREATE VIEW vw_autoadmin_health_status
AS
    SELECT CONVERT(VARCHAR, GETDATE()) 'Datetime', 
        @@servername 'Instance name', 
        CONVERT(VARCHAR, number_of_storage_connectivity_errors) AS 'Storage errors',
        CONVERT(VARCHAR, number_of_sql_errors) AS 'Sql errors',
        CONVERT(VARCHAR, number_of_invalid_credential_errors) AS 'Credential errors',
        CONVERT(VARCHAR, number_of_other_errors) AS 'Other errors',
        CONVERT(VARCHAR, number_of_corrupted_or_deleted_backups) AS 'Deleted or invalid backup files',
        CONVERT(VARCHAR, number_of_backup_loops) AS 'Number of backup loops',
        CONVERT(VARCHAR, number_of_retention_loops) AS 'Number of retention loops'
    FROM smart_admin.fn_get_health_status(default, default)
GO

----------------------------------------------------------------------------------------------------
-- sp_check_smartadmin_notification_enabled
--
-- This procedure Check if smartadmin is enabled ( master switch on & smartadmin instance config enabled).
--
IF OBJECT_ID ('sp_check_smartadmin_notification_enabled') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure sp_check_smartadmin_notification_enabled...' 
	DROP PROCEDURE sp_check_smartadmin_notification_enabled
END 
GO

PRINT 'Creating procedure sp_check_smartadmin_notification_enabled...'
GO

CREATE PROCEDURE sp_check_smartadmin_notification_enabled
AS
BEGIN
    -- Check if master switch is on
    IF (0 =  msdb.smart_admin.fn_is_master_switch_on ())
    BEGIN
        RAISERROR (45208, 17, 1);
        RETURN
    END

    -- Check if notification Email was set
    DECLARE @notification_email_ids NVARCHAR(MAX)
    SELECT @notification_email_ids = value
    FROM [msdb].[dbo].[autoadmin_system_flags]
    WHERE name = 'SSMBackup2WANotificationEmailIds'

    IF (@notification_email_ids IS NULL) OR (@notification_email_ids = N'')
    BEGIN
        RAISERROR (45209, 17, 2);
        RETURN
    END

END
GO

----------------------------------------------------------------------------------------------------
-- sp_autoadmin_notification_job_send_email
--
-- This procedure runs Health report T-SQL query and send results in email
--
IF OBJECT_ID ('sp_autoadmin_notification_job_send_email') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure sp_autoadmin_notification_job_send_email...' 
	DROP PROCEDURE sp_autoadmin_notification_job_send_email
END 
GO

PRINT 'Creating procedure sp_autoadmin_notification_job_send_email...'
GO

CREATE PROCEDURE sp_autoadmin_notification_job_send_email
    @profile_name SYSNAME = null  -- If null, default mail profile is used
AS
BEGIN
    DECLARE @tableHTML  NVARCHAR(MAX) ;
    DECLARE @notification_email_ids NVARCHAR(MAX)

    SELECT @notification_email_ids = value
    FROM [msdb].[dbo].[autoadmin_system_flags]
    WHERE name = 'SSMBackup2WANotificationEmailIds'

    IF (@notification_email_ids IS NULL) OR (@notification_email_ids = N'')
    BEGIN
        RAISERROR (45209, 17, 1);
        RETURN
    END

        -- Construct HTML table; $ISSUE - Replace with Weiyun's Query
    SET @tableHTML =
        N'<H1>Smartadmin health check report</H1>' +
        N'<table border="1">' +
        N'<tr><th>Datetime</th>' +
        N'<th>Instance name</th>' +
        N'<th>Storage errors</th>' +
        N'<th>Sql errors</th>' +
        N'<th>Credential errors</th>' +
        N'<th>Other errors</th>' +
        N'<th>Deleted or invalid backup files</th>' +
        N'<th>Number of backup loops</th>' +
        N'<th>Number of retention loops</th></tr>' +
        CAST ( ( select  td = [Datetime],        '',
            td = [Instance name],       '',
            td =  [Storage errors],         '',
            td =  [Sql errors],         '',
            td =  [Credential errors],         '',
            td =  [Other errors],         '',
            td =  [Deleted or invalid backup files] ,  '',
            td =  [Number of backup loops],         '',
            td =  [Number of retention loops],         ''
            FROM msdb.dbo.vw_autoadmin_health_status
            FOR XML PATH('tr'), TYPE 
            ) AS NVARCHAR(MAX) ) +
        N'</table>' ;

    -- $ISSUE - Localize message
    DECLARE @subject NVARCHAR(255)
    SET @subject = 'Smartadmin health check on ' + @@servername + '  at ' + CONVERT(VARCHAR, GETDATE())

    EXEC [msdb].[dbo].[sp_send_dbmail]
            @profile_name = @profile_name,
            @recipients = @notification_email_ids,
            @subject = @subject,
            @body = @tableHTML,
            @body_format = 'HTML' ;

END
GO

----------------------------------------------------------------------------------------------------
-- sp_autoadmin_create_notification_job 
--
-- Creates SQL Agent job to check smartadmin health and send email using DBMail
-- Step 1 : Check if smartadmin is enabled ( master switch on & smartadmin instance config enabled)
-- Step 2 : Run Health policy - Test-SqlSmartAdmin
-- Step 3 : If Step 2 reported unhealthy, Send Email notification with Query output in HTML
--
IF OBJECT_ID ('sp_autoadmin_create_notification_job') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure sp_autoadmin_create_notification_job...' 
	DROP PROCEDURE sp_autoadmin_create_notification_job
END 
GO

PRINT 'Creating procedure sp_autoadmin_create_notification_job...'
GO

CREATE PROCEDURE sp_autoadmin_create_notification_job
AS
BEGIN
    DECLARE @TranCounter INT
    SET @TranCounter = @@TRANCOUNT
    IF (@TranCounter > 0)
    BEGIN
        SAVE TRANSACTION tran_create_notification_job
    END
    ELSE
    BEGIN
        BEGIN TRANSACTION
    END

    BEGIN TRY
        IF EXISTS (SELECT name from msdb.dbo.sysjobs WHERE name = N'smartadmin health check job')
        BEGIN
            EXEC msdb.dbo.sp_delete_job @job_name = N'smartadmin health check job'
        END

        DECLARE @ReturnCode INT
        SELECT @ReturnCode = 0

        DECLARE @jobId BINARY(16)
        EXEC @ReturnCode =  msdb.dbo.sp_add_job @job_name=N'smartadmin health check job', 
                @enabled=1, 
                @notify_level_eventlog=0, 
                @notify_level_email=0, 
                @notify_level_netsend=0, 
                @notify_level_page=0, 
                @delete_level=0, 
                @owner_login_name=N'sa', 
                @job_id = @jobId OUTPUT

        EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Check smartadmin is enabled and email notification is configured', 
                @step_id=1, 
                @cmdexec_success_code=0, 
                @on_success_action=3, 
                @on_success_step_id=0, 
                @on_fail_action=1, 
                @on_fail_step_id=0, 
                @retry_attempts=0, 
                @retry_interval=0, 
                @os_run_priority=0, @subsystem=N'TSQL', 
                @command=N'EXEC [msdb].[dbo].[sp_check_smartadmin_notification_enabled]', 
                @database_name=N'master', 
                @flags=0
    
        EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Health policy checks', 
                @step_id=2, 
                @cmdexec_success_code=0, 
                @on_success_action=1, 
                @on_success_step_id=0, 
                @on_fail_action=3, 
                @on_fail_step_id=0, 
                @retry_attempts=0, 
                @retry_interval=0, 
                @os_run_priority=0, @subsystem=N'PowerShell', 
                @command=N'if (''$(ESCAPE_SQUOTE(INST))'' -eq ''MSSQLSERVER'') {$a = ''\DEFAULT''} ELSE {$a = ''''};
        CD SQLSERVER:\SQL\$(ESCAPE_NONE(SRVR))$a
        $healthResult = Get-SqlSmartAdmin | Test-SqlSmartAdmin
         if( $healthResult.HealthState -ne "Healthy")
        {
            throw ''SmartAdmin policy checks failed''
        }
         ', 
        @database_name=N'', 
        @flags=0
    
        EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Send email notification', 
            @step_id=3, 
            @cmdexec_success_code=0, 
            @on_success_action=1, 
            @on_success_step_id=0, 
            @on_fail_action=2, 
            @on_fail_step_id=0, 
            @retry_attempts=0, 
            @retry_interval=0, 
            @os_run_priority=0, @subsystem=N'TSQL', 
            @command=N'EXEC [msdb].[dbo].[sp_autoadmin_notification_job_send_email] @profile_name = N''$(ESCAPE_SQUOTE(DBMAILPROFILE))''', 
            @database_name=N'master', 
            @flags=0

        EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1

        EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'

        -- Run this job every 15 minutes
        DECLARE @schedule_uid UNIQUEIDENTIFIER
        DECLARE @schedule_id INT
        DECLARE @schedule_name SYSNAME
        SET @schedule_name = N'smartadmin health check every 15 minutes'

        SELECT @schedule_id = schedule_id 
        FROM msdb.dbo.sysschedules
        WHERE name = @schedule_name

        IF (@schedule_id IS NULL)
        BEGIN
            EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id = @jobId, @name=@schedule_name, 
                @enabled=1, 
                @freq_type=4, 
                @freq_interval=1, 
                @freq_subday_type=4, 
                @freq_subday_interval=15, 
                @freq_relative_interval=0, 
                @freq_recurrence_factor=0, 
                @active_start_date=20130623, 
                @active_end_date=99991231, 
                @active_start_time=0, 
                @active_end_time=235959,
                @schedule_uid = @schedule_uid OUTPUT

            SELECT @schedule_id = schedule_id FROM msdb.dbo.sysschedules
            WHERE @schedule_uid = @schedule_uid
        END

        EXEC @ReturnCode = msdb.dbo.sp_attach_schedule @job_id = @jobId, @schedule_id = @schedule_id

        IF (@TranCounter = 0)
            COMMIT TRANSACTION
        RETURN (0)

    END TRY
    BEGIN CATCH
        IF (@TranCounter = 0 OR XACT_STATE() = -1)
            ROLLBACK TRANSACTION
        ELSE IF (XACT_STATE() = 1)
            ROLLBACK TRANSACTION tran_create_notification_job

        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)
    END CATCH

END
GO

----------------------------------------------------------------------------------------------------
-- sp_autoadmin_configure_notification
--
-- Checks is database mails is enabled, agent is setup for notifications and configures notification job
--
IF OBJECT_ID ('sp_autoadmin_configure_notification') IS NOT NULL
BEGIN 
	PRINT 'Dropping procedure sp_autoadmin_configure_notification...' 
	DROP PROCEDURE sp_autoadmin_configure_notification
END 
GO

PRINT 'Creating procedure sp_autoadmin_configure_notification...'
GO

CREATE PROCEDURE sp_autoadmin_configure_notification
AS
BEGIN
    -- Check if Database Mail was enabled
    IF NOT EXISTS (SELECT cfg.Name
            FROM
            sys.configurations AS cfg
            WHERE cfg.Name = 'Database Mail XPs'
            AND cfg.Value  = 1)
    BEGIN
        RAISERROR (45210, 17, 1);
        RETURN
    END

    -- Check if Database mail profile is setup for Agent Notifications
    DECLARE @databasemail_profile              NVARCHAR(255)
    EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                            N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                            N'DatabaseMailProfile',
                                            @databasemail_profile OUTPUT,
                                            N'no_output'

    IF (@databasemail_profile IS NULL)
    BEGIN
		RAISERROR (45211, 17, 2);
		RETURN
    END
    -- Configure Smartadmin notification agent job
    EXEC [msdb].[dbo].[sp_autoadmin_create_notification_job]
    
END
GO

-----------------------------------------------------
-- Smartadmin built-in policies     
-----------------------------------------------------
SET NOCOUNT ON
GO
-------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create temp procedure to handle the cascade delete issue in procedure sp_syspolicy_delete_policy, see VSTS 904601 for more details.
--- basically this procedure is to simulate the sp_syspolicy_delete_policy + syspolicy_instead_delete_policy_trigger, so if there is any logic change
--- there, we need to update this temp proc too.
--- In case a failure occurs, procedure may have not been dropped, and the create will fail, so execute CREATE OR ALTER.
-------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE OR ALTER PROCEDURE #sp_syspolicy_cascade_delete_policy
   @name sysname = NULL
AS
BEGIN
    DECLARE @policyid int;
    DECLARE @retval int;
    EXEC @retval = [msdb].[dbo].[sp_syspolicy_verify_policy_identifiers] @name, @policyid OUTPUT
    IF (@retval = 0)
	BEGIN
		DELETE msdb.dbo.syspolicy_policy_execution_history_internal	WHERE policy_id = @policyid
		DELETE msdb.dbo.syspolicy_system_health_state_internal		WHERE policy_id = @policyid
		EXEC msdb.dbo.sp_syspolicy_delete_policy @policy_id = @policyid
	END
END
GO

BEGIN
    DECLARE @smartadmin_target_set_id int
    DECLARE @smartadmin_policy_helptext NVARCHAR(1024)
    DECLARE @smartadmin_policy_description NVARCHAR(max)
    DECLARE @condition_name NVARCHAR(1024)
    DECLARE @expression NVARCHAR(max)
    DECLARE @smartadmin_policy_helptext_prefix NVARCHAR(100)
    DECLARE @smartadmin_policy_description_prefix NVARCHAR(100)

    SELECT @smartadmin_policy_helptext_prefix = 'Smartadmin Policy Helptext ID:'
    SELECT @smartadmin_policy_description_prefix = 'Smartadmin Policy Description ID:'

    PRINT ''
    PRINT 'Deleting existing policies, objectsets...'

    -- Critical Error Policy - SmartAdminSystemHealthPolicy
    PRINT ''
    PRINT 'Deleting SmartAdminSystemHealthPolicy'
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'SmartAdminSystemHealthPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'SmartAdminSystemHealthPolicy', @marker=0
        EXECUTE #sp_syspolicy_cascade_delete_policy @name=N'SmartAdminSystemHealthPolicy'
    END    

    PRINT ''
    PRINT 'Deleting SmartAdminSystemHealthPolicy_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'SmartAdminSystemHealthPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'SmartAdminSystemHealthPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'SmartAdminSystemHealthPolicy_ObjectSet'
    END    

    -- Warning Policy - SmartAdminUserActionsHealthPolicy
    PRINT ''
    PRINT 'Deleting SmartAdminUserActionsHealthPolicy'
    IF EXISTS(SELECT policy_id FROM msdb.dbo.syspolicy_policies WHERE name=N'SmartAdminUserActionsHealthPolicy')
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'SmartAdminUserActionsHealthPolicy', @marker=0
        EXECUTE #sp_syspolicy_cascade_delete_policy @name=N'SmartAdminUserActionsHealthPolicy'
    END    

    PRINT ''
    PRINT 'Deleting SmartAdminUserActionsHealthPolicy_ObjectSet'
    IF EXISTS(SELECT object_set_id FROM msdb.dbo.syspolicy_object_sets WHERE object_set_name=N'SmartAdminUserActionsHealthPolicy_ObjectSet')
    BEGIN   
        EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'SmartAdminUserActionsHealthPolicy_ObjectSet', @marker=0
        EXEC msdb.dbo.sp_syspolicy_delete_object_set @object_set_name=N'SmartAdminUserActionsHealthPolicy_ObjectSet'
    END    

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Category: Smartadmin errors
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    PRINT ''
    PRINT 'Creating category: SmartAdmin errors...'
    IF EXISTS(SELECT policy_category_id FROM msdb.dbo.syspolicy_policy_categories WHERE name=N'SmartAdmin errors')	
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_rename_policy_category @name=N'SmartAdmin errors', @new_name=N'SmartAdmin errors'	
    END
    ELSE
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_add_policy_category @name=N'SmartAdmin errors', @policy_category_id=0	
    END

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Category: Smartadmin errors
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    PRINT ''
    PRINT 'Creating category: SmartAdmin warnings...'
    IF EXISTS(SELECT policy_category_id FROM msdb.dbo.syspolicy_policy_categories WHERE name=N'SmartAdmin warnings')	
    BEGIN
    EXEC msdb.dbo.sp_syspolicy_rename_policy_category @name=N'SmartAdmin warnings', @new_name=N'SmartAdmin warnings'	
    END
    ELSE	
    BEGIN
    EXEC msdb.dbo.sp_syspolicy_add_policy_category @name=N'SmartAdmin warnings', @policy_category_id=0	
    END

    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: SmartAdminSystemHealthCondition  
    --   IsMasterSwitchEnabled = True
    --   AND
    --   NumberOfInvalidCredentialErrors = 0
    --   AND
    --   NumberOfSqlErrors = 0
    --   AND
    --   NumberOfStorageConnectivityErrors = 0
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    PRINT ''
    PRINT 'Creating Condition: SmartAdminSystemHealthCondition...'
    SET @condition_name = N'SmartAdminSystemHealthCondition'
    SET @expression = N'<Operator>
  <TypeClass>Bool</TypeClass>
  <OpType>AND</OpType>
  <Count>2</Count>
  <Operator>
    <TypeClass>Bool</TypeClass>
    <OpType>AND</OpType>
    <Count>2</Count>
    <Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>AND</OpType>
      <Count>2</Count>
      <Operator>
        <TypeClass>Bool</TypeClass>
        <OpType>AND</OpType>
        <Count>2</Count>
        <Operator>
          <TypeClass>Bool</TypeClass>
          <OpType>AND</OpType>
          <Count>2</Count>
          <Operator>
            <TypeClass>Bool</TypeClass>
            <OpType>AND</OpType>
            <Count>2</Count>
            <Operator>
              <TypeClass>Bool</TypeClass>
              <OpType>EQ</OpType>
              <Count>2</Count>
              <Attribute>
                <TypeClass>Bool</TypeClass>
                <Name>IsMasterSwitchEnabled</Name>
              </Attribute>
              <Function>
                <TypeClass>Bool</TypeClass>
                <FunctionType>True</FunctionType>
                <ReturnType>Bool</ReturnType>
                <Count>0</Count>
              </Function>
            </Operator>
            <Operator>
              <TypeClass>Bool</TypeClass>
              <OpType>EQ</OpType>
              <Count>2</Count>
              <Attribute>
                <TypeClass>Numeric</TypeClass>
                <Name>NumberOfInvalidCredentialErrors</Name>
              </Attribute>
              <Constant>
                <TypeClass>Numeric</TypeClass>
                <ObjType>System.Double</ObjType>
                <Value>0</Value>
              </Constant>
            </Operator>
          </Operator>
          <Operator>
            <TypeClass>Bool</TypeClass>
            <OpType>EQ</OpType>
            <Count>2</Count>
            <Attribute>
              <TypeClass>Numeric</TypeClass>
              <Name>NumberOfSqlErrors</Name>
            </Attribute>
            <Constant>
              <TypeClass>Numeric</TypeClass>
              <ObjType>System.Double</ObjType>
              <Value>0</Value>
            </Constant>
          </Operator>
        </Operator>
        <Operator>
          <TypeClass>Bool</TypeClass>
          <OpType>EQ</OpType>
          <Count>2</Count>
          <Attribute>
            <TypeClass>Numeric</TypeClass>
            <Name>NumberOfStorageConnectivityErrors</Name>
          </Attribute>
          <Constant>
            <TypeClass>Numeric</TypeClass>
            <ObjType>System.Double</ObjType>
            <Value>0</Value>
          </Constant>
        </Operator>
      </Operator>
      <Operator>
        <TypeClass>Bool</TypeClass>
        <OpType>EQ</OpType>
        <Count>2</Count>
        <Attribute>
          <TypeClass>Numeric</TypeClass>
          <Name>NumberOfOtherErrors</Name>
        </Attribute>
        <Constant>
          <TypeClass>Numeric</TypeClass>
          <ObjType>System.Double</ObjType>
          <Value>0</Value>
        </Constant>
      </Operator>
    </Operator>
    <Operator>
      <TypeClass>Bool</TypeClass>
      <OpType>NE</OpType>
      <Count>2</Count>
      <Attribute>
        <TypeClass>Numeric</TypeClass>
        <Name>NumberOfBackupLoops</Name>
      </Attribute>
      <Constant>
        <TypeClass>Numeric</TypeClass>
        <ObjType>System.Double</ObjType>
        <Value>0</Value>
      </Constant>
    </Operator>
  </Operator>
  <Operator>
    <TypeClass>Bool</TypeClass>
    <OpType>NE</OpType>
    <Count>2</Count>
    <Attribute>
      <TypeClass>Numeric</TypeClass>
      <Name>NumberOfRetentionLoops</Name>
    </Attribute>
    <Constant>
      <TypeClass>Numeric</TypeClass>
      <ObjType>System.Double</ObjType>
      <Value>0</Value>
    </Constant>
  </Operator>
</Operator>'
    
    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'', 
        @facet=N'ISmartAdminState', @expression=@expression, @is_name_condition=0, @obj_name=N''
    END
    ELSE
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'', 
        @facet=N'ISmartAdminState', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
    END
    
    -- mark condition as system
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1


    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Policy : SmartAdminSystemHealthPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    PRINT ''
    PRINT 'Creating Policy: SmartAdminSystemHealthPolicy...'

    -- $Issue - Fix help IDs
    select @smartadmin_policy_helptext = @smartadmin_policy_helptext_prefix + '41413'
    select @smartadmin_policy_description = @smartadmin_policy_description_prefix + '41414'

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'SmartAdminSystemHealthPolicy_ObjectSet', @facet=N'ISmartAdminState', @object_set_id=0

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'SmartAdminSystemHealthPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'SmartAdminSystemHealthPolicy_ObjectSet', @type_skeleton=N'Server/SmartAdmin', @type=N'SMARTADMIN', @enabled=True, @target_set_id=@smartadmin_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@smartadmin_target_set_id, @type_skeleton=N'Server/SmartAdmin', @level_name=N'SmartAdmin', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'SmartAdminSystemHealthPolicy', @condition_name=N'SmartAdminSystemHealthCondition', @policy_category=N'SmartAdmin errors', 
    @description=@smartadmin_policy_description, 
    @help_text=@smartadmin_policy_helptext, 
    @help_link=N'',                                          -- $Issue - Fix help link
    @schedule_uid=N'00000000-0000-0000-0000-000000000000', 
    @execution_mode=0, 
    @is_enabled=False, 
    @policy_id=0, 
    @object_set=N'SmartAdminSystemHealthPolicy_ObjectSet'
   
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'SmartAdminSystemHealthPolicy', @marker=1 


    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Condition: SmartAdminUserActionsHealthCondition  
    --   IsMasterSwitchEnabled = True
    --   AND
    --   NumberOfCorruptedOrDeletedBackups = 0
    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    PRINT ''
    PRINT 'Creating Condition: SmartAdminUserActionsHealthCondition...'
    SET @condition_name = N'SmartAdminUserActionsHealthCondition'
    SET @expression = N'<Operator>
  <TypeClass>Bool</TypeClass>
  <OpType>AND</OpType>
  <Count>2</Count>
  <Operator>
    <TypeClass>Bool</TypeClass>
    <OpType>EQ</OpType>
    <Count>2</Count>
    <Attribute>
      <TypeClass>Bool</TypeClass>
      <Name>IsMasterSwitchEnabled</Name>
    </Attribute>
    <Function>
      <TypeClass>Bool</TypeClass>
      <FunctionType>True</FunctionType>
      <ReturnType>Bool</ReturnType>
      <Count>0</Count>
    </Function>
  </Operator>
  <Operator>
    <TypeClass>Bool</TypeClass>
    <OpType>EQ</OpType>
    <Count>2</Count>
    <Attribute>
      <TypeClass>Numeric</TypeClass>
      <Name>NumberOfCorruptedOrDeletedBackups</Name>
    </Attribute>
    <Constant>
      <TypeClass>Numeric</TypeClass>
      <ObjType>System.Double</ObjType>
      <Value>0</Value>
    </Constant>
  </Operator>
</Operator>'
    
    IF EXISTS(SELECT condition_id FROM msdb.dbo.syspolicy_conditions WHERE name=@condition_name)
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_update_condition @name=@condition_name,  @description=N'', 
        @facet=N'ISmartAdminState', @expression=@expression, @is_name_condition=0, @obj_name=N''
    END
    ELSE
    BEGIN
        EXEC msdb.dbo.sp_syspolicy_add_condition @name=@condition_name,  @description=N'', 
        @facet=N'ISmartAdminState', @expression=@expression, @is_name_condition=0, @obj_name=N'',@condition_id=0
    END
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'CONDITION', @name=@condition_name, @marker=1


    -------------------------------------------------------------------------------------------------------------------------------------------------------------
    -- Policy : SmartAdminUserActionsHealthPolicy
    -------------------------------------------------------------------------------------------------------------------------------------------------------------       
    PRINT ''
    PRINT 'Creating Policy: SmartAdminUserActionsHealthPolicy...'

    -- $Issue - Fix help IDs
    select @smartadmin_policy_helptext = @smartadmin_policy_helptext_prefix + '41413'
    select @smartadmin_policy_description = @smartadmin_policy_description_prefix + '41414'

    EXEC msdb.dbo.sp_syspolicy_add_object_set @object_set_name=N'SmartAdminUserActionsHealthPolicy_ObjectSet', @facet=N'ISmartAdminState', @object_set_id=0

    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'OBJECTSET', @name=N'SmartAdminUserActionsHealthPolicy_ObjectSet', @marker=1

    EXEC msdb.dbo.sp_syspolicy_add_target_set @object_set_name=N'SmartAdminUserActionsHealthPolicy_ObjectSet', @type_skeleton=N'Server/SmartAdmin', @type=N'SMARTADMIN', @enabled=True, @target_set_id=@smartadmin_target_set_id OUTPUT

    EXEC msdb.dbo.sp_syspolicy_add_target_set_level @target_set_id=@smartadmin_target_set_id, @type_skeleton=N'Server/SmartAdmin', @level_name=N'SmartAdmin', @condition_name=N'', @target_set_level_id=0

    EXEC msdb.dbo.sp_syspolicy_add_policy @name=N'SmartAdminUserActionsHealthPolicy', @condition_name=N'SmartAdminUserActionsHealthCondition', @policy_category=N'SmartAdmin warnings', 
    @description=@smartadmin_policy_description, 
    @help_text=@smartadmin_policy_helptext, 
    @help_link=N'',                                          -- $Issue - Fix help link
    @schedule_uid=N'00000000-0000-0000-0000-000000000000', 
    @execution_mode=0, 
    @is_enabled=False, 
    @policy_id=0, 
    @object_set=N'SmartAdminUserActionsHealthPolicy_ObjectSet'
   
    EXEC msdb.dbo.sp_syspolicy_mark_system @type=N'POLICY', @name=N'SmartAdminUserActionsHealthPolicy', @marker=1 
  
  
END

-- Cleanup
DROP PROCEDURE #sp_syspolicy_cascade_delete_policy 
GO

PRINT 'Completed creating all required Managed Backup objects...'

/**********************************************************************/
/* MSDB_SECURITY.SQL                                                  */
/*                                                                    */
/* This is run after all the components are installed in MSDB         */
/* It will set up the security and certificates.                      */
/* TODO: Split this too based on each component.                      */
/*                                                                    */
/*                                                                    */
/* Copyright (c) Microsoft Corporation                                */
/* All Rights Reserved.                                               */
/*                                                                    */
/**********************************************************************/

PRINT ''
PRINT '-------------------------------------------'
PRINT 'Starting execution of MSDB_POST_INSTALL.SQL'
PRINT '-------------------------------------------'
GO

/**************************************************************/
/* Mark system objects                                        */
/**************************************************************/
DECLARE  @start DATETIME
DECLARE  @name  SYSNAME
DECLARE  @schema_name  SYSNAME
DECLARE  @schema_qualified_name SYSNAME

SELECT @start = start FROM #InstMsdb

DECLARE newsysobjs CURSOR FOR 
SELECT name, SCHEMA_NAME(schema_id) 
FROM sys.objects so
WHERE (SCHEMA_NAME(schema_id) IN (SELECT DISTINCT [schema_name] FROM #sp_table))
AND create_date >= @start

OPEN newsysobjs
FETCH NEXT FROM newsysobjs INTO @name, @schema_name

WHILE @@FETCH_STATUS = 0
BEGIN
   SET @schema_qualified_name = QUOTENAME(@schema_name) + '.'  + QUOTENAME(@name)

   EXEC sp_MS_marksystemobject @schema_qualified_name

   UPDATE #sp_table 
   SET bNewProc = 1 
   WHERE name = @name
   AND [schema_name] = @schema_name

   FETCH NEXT FROM newsysobjs INTO  @name, @schema_name
END

DEALLOCATE newsysobjs
DROP TABLE #InstMsdb

GO

PRINT 'Signing sps ...'
-- Create certificate to sign Agent sps
--
DECLARE @certError int

IF EXISTS (SELECT * FROM sys.certificates WHERE name = '##MS_AgentSigningCertificate##')
BEGIN
   PRINT 'Dropping existing Agent certificate ...'
   DROP CERTIFICATE [##MS_AgentSigningCertificate##]

   SELECT @certError = @@error
   IF (@certError <> 0)
   BEGIN
       SELECT 'Cannot drop existing certificate. Objects still signed by ##MS_AgentSigningCertificate##' = object_name(crypts.major_id) 
       FROM sys.crypt_properties crypts, sys.certificates certs
       WHERE crypts.thumbprint = certs.thumbprint
       AND crypts.class = 1
       AND certs.name = '##MS_AgentSigningCertificate##'
   
       RAISERROR('Cannot drop existing ##MS_AgentSigningCertificate## in msdb. INSTMSDB.SQL terminating.', 20, 127) WITH LOG
   END
end

-- If the temp table #SqlAgentSignatures exists, then this script is running as part
-- of an upgrade from a previous build of sql server. In this case we want to sign
-- the Agent procedures using the same signatures that exist in a clean install
-- of this same build. Those signatures are in #SqlAgentSignatures. To do this
-- we create the certificate from the .cer file.
--
-- In the non-upgrade case, meaning that this script is being run during the build
-- of the SQL Server product itself, we simply create the certificate from scratch.
--
-- In the case of Managed Instance, .cer file is not available so make sure we create
-- the certificate from scratch, just like in mamaster case.

CREATE TABLE #InstmsdbAgentCertPath (AgentCert NVARCHAR(1100))
DECLARE @certificate_name NVARCHAR(1100) -- space for path + MS_AgentSigningCertificate.cer
IF ((NOT OBJECT_ID(N'tempdb.dbo.#SqlAgentSignatures', 'U') IS NULL) AND (SERVERPROPERTY('EngineEdition') <> 8))
BEGIN

    -- We need agent XP's to be on in order to lookup the path to our instance install folder.
    DECLARE @advopt_old_value int
    DECLARE @comp_old_value int
    EXEC #sp_enable_component 'Agent XPs', @advopt_old_value out, @comp_old_value out

    DECLARE @InstallRootPath nvarchar(1024)

    -- Note: This is an _instance_ registry lookup, so it will
    -- automatically insert the right instance name after the "Microsoft
    -- SQL Server" part of the path, thus looking in a path like
    -- 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL10.INST1\Setup'
    EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\Microsoft SQL Server\Setup', N'SQLPath', @InstallRootPath OUTPUT

    -- Restore Agent XPs to previous state
    EXECUTE #sp_restore_component_state 'Agent XPs', @advopt_old_value, @comp_old_value

    IF @InstallRootPath IS NULL
    BEGIN
       RAISERROR('Cannot find instance-specific registry path for SOFTWARE\Microsoft\Microsoft SQL Server\Setup\SqlPath. INSTMSDB.SQL terminating.', 20, 127) WITH LOG
    END

    -- Now find our certificate in the Install folder, placed there by setup.
    SELECT @InstallRootPath = @InstallRootPath + N'\Install'

    SELECT @certificate_name = @InstallRootPath + N'\MS_AgentSigningCertificate.cer'

    -- Remember this file path so we can import it into master later
    INSERT #InstmsdbAgentCertPath(AgentCert) VALUES (@certificate_name)
    PRINT 'Updated #InstmsdbAgentCertPath with value ' + @certificate_name

    -- Handle single quotes in the directory name since this string is going to be passed to EXECUTE
    DECLARE @certificate_nameQuoted NVARCHAR(2200)
    SELECT  @certificate_nameQuoted = '''' + REPLACE(@certificate_name, '''', '''''') + ''''

    PRINT 'Creating ##MS_AgentSigningCertificate## from ' + @certificate_name
    EXECUTE(N'create certificate [##MS_AgentSigningCertificate##] from file = ' + @certificate_nameQuoted)
    select @certError = @@error

    IF (@certError <> 0)
    BEGIN
       RAISERROR('Cannot create ##MS_AgentSigningCertificate## from file. INSTMSDB.SQL terminating.', 20, 127) WITH LOG
    END
END
ELSE
BEGIN
--   **** Security Note **** 
-- The private key for the certificate will be dropped at the end of this script
-- after the certificate is used to sign SPs as part of MkMaster. This part of the
-- is only run as part of MkMaster so is not used outside of the build process.
   DBCC TRACEON(4606,-1) -- Ignore domain policy about weak password
   CREATE CERTIFICATE [##MS_AgentSigningCertificate##] 
      ENCRYPTION BY PASSWORD = 'Yukon90_'
      WITH SUBJECT = 'MS_AgentSigningCertificate'
   SELECT @certError = @@error
   DBCC TRACEOFF(4606,-1)

   IF (@certError <> 0)
   BEGIN
      RAISERROR('Cannot create ##MS_AgentSigningCertificate## in msdb. INSTMSDB.SQL terminating.', 20, 127) WITH LOG
   END

   -- Get the file path to DATA folder
   DECLARE @data_dir_path NVARCHAR(260)
   
   IF (SERVERPROPERTY('EngineEdition') <> 8)
   BEGIN
      SELECT @data_dir_path = REVERSE(SUBSTRING(REVERSE(physical_name), CHARINDEX(CAST(SERVERPROPERTY('pathseparator') as nvarchar(2)), REVERSE(physical_name)), LEN(physical_name))) 
      FROM sys.master_files 
      WHERE [database_id] = 1
      AND [file_id] = 1
      AND [data_space_id] = 1
   END
   ELSE
   BEGIN
      SELECT @data_dir_path = CONVERT(NVARCHAR(260), SERVERPROPERTY('InstanceDefaultDataPath'))
   END
   
   IF (@data_dir_path is null)
   BEGIN
      RAISERROR('Cannot deduce master database data path. This path is needed for temporary certificate ##MS_AgentSigningCertificate##. INSTMSDB.SQL terminating.', 20, 127) WITH LOG
   END
   
   SELECT @certificate_name = @data_dir_path + 'MS_AgentSigningCertificate.cer'

   -- Remember this file path so we can import it into master later
   INSERT #InstmsdbAgentCertPath(AgentCert) VALUES (@certificate_name)
   PRINT 'Created #InstmsdbAgentCertPath with value ' + @certificate_name
END

GO

BEGIN TRANSACTION
DECLARE @schema_qualified_name SYSNAME
DECLARE @schema_name SYSNAME
DECLARE @sp SYSNAME
DECLARE @exec_str NVARCHAR(1024)
DECLARE @sign INT
DECLARE @obdComponent INT
DECLARE @bNewProc INT
DECLARE @bUseExistingSignature INT

SET @bUseExistingSignature = 0
IF (NOT OBJECT_ID(N'tempdb.dbo.#SqlAgentSignatures', 'U') IS NULL)
BEGIN
    SET @bUseExistingSignature = 1
END

DECLARE @err_str NVARCHAR(1024)
DECLARE ms_crs_sps CURSOR GLOBAL FOR 
SELECT 
    [schema_name], 
    [name], 
    [sign], 
    obdComponent, 
    bNewProc 
    FROM #sp_table 

OPEN ms_crs_sps
FETCH NEXT FROM ms_crs_sps INTO 
    @schema_name, 
    @sp, 
    @sign, 
    @obdComponent, 
    @bNewProc

WHILE @@fetch_status = 0
BEGIN
   IF EXISTS(SELECT * FROM sys.objects 
                WHERE 
                name = @sp AND 
                SCHEMA_NAME(schema_id) = @schema_name
                )
   BEGIN
      SET @schema_qualified_name = QUOTENAME(@schema_name) + '.'  + QUOTENAME(@sp)

      PRINT 'Processing sp: ' + @schema_qualified_name
      IF (@sign = 1)
      BEGIN
         IF (@bUseExistingSignature = 1)
         BEGIN
            DECLARE @signature VARBINARY(max)
            SELECT @signature = [signature]
               FROM #SqlAgentSignatures
               WHERE [schema_name] = @schema_name
               AND [object_name] = @sp

            IF (@signature is null)
            BEGIN
               SET @err_str = 'Cannot find existing signature for stored procedure ' + @schema_qualified_name + '. Terminating.'
               RAISERROR(@err_str, 20, 127) WITH LOG
               ROLLBACK TRANSACTION
               RETURN
            END

            SET @exec_str = N'ADD SIGNATURE TO ' + @schema_qualified_name + N' BY CERTIFICATE [##MS_AgentSigningCertificate##] WITH SIGNATURE = ' + CONVERT(NVARCHAR(MAX), @signature, 1)
         END
         ELSE
         BEGIN
            SET @exec_str = N'ADD SIGNATURE TO ' + @schema_qualified_name + N' BY CERTIFICATE [##MS_AgentSigningCertificate##] WITH PASSWORD = ''Yukon90_'''
         END

         EXECUTE(@exec_str)
         
         IF (@@error <> 0)
         BEGIN
            SET @err_str = 'Cannot sign stored procedure ' + @schema_qualified_name + '. Terminating.'
            RAISERROR(@err_str, 20, 127) WITH LOG
            ROLLBACK TRANSACTION
            RETURN
         END
      END

      -- IF there is a new procedure that goes in a component, put it there
      IF (@obdComponent > 0 and @bNewProc > 0)
      BEGIN
         IF (@obdComponent = 1) -- SQLAgent
         BEGIN
             SET @exec_str = N'EXEC sp_AddFunctionalUnitToComponent N''Agent XPs'', N''' + @schema_qualified_name + N''''
         END
         ELSE IF (@obdComponent = 2) -- DbMail
         BEGIN
             SET @exec_str = N'EXEC sp_AddFunctionalUnitToComponent N''Database Mail XPs'', N''' + @schema_qualified_name + N''''
         END
 
         EXECUTE(@exec_str)

         IF (@@error <> 0)
         BEGIN
            RAISERROR('Cannot add stored procedure to component. INSTMSDB.SQL terminating.', 20, 127) WITH LOG
            ROLLBACK TRANSACTION
            RETURN
         END
      END
   END
   
   FETCH NEXT FROM ms_crs_sps INTO 
    @schema_name, 
    @sp, 
    @sign, 
    @obdComponent, 
    @bNewProc

END

CLOSE ms_crs_sps
DEALLOCATE ms_crs_sps
COMMIT TRANSACTION

GO
DROP TABLE #sp_table
GO

PRINT 'Succesfully signed sps'

DECLARE @certificate_name NVARCHAR(1100)
select @certificate_name = AgentCert
   from #InstmsdbAgentCertPath

-- Handle single quotes in the directory name since this string is going to be passed to EXECUTE
-- in both BACKUP CERT and CREATE CERT below.
DECLARE @certificate_nameQuoted NVARCHAR(2200)
SELECT  @certificate_nameQuoted = '''' + REPLACE(@certificate_name, '''', '''''') + ''''

-- If this is not an upgrade, then we made our certificate from scratch.
-- Export it to a new file so that it can be imported into the master database.
IF (OBJECT_ID(N'tempdb.dbo.#SqlAgentSignatures', 'U') IS NULL)
BEGIN
   -- Drop certificate private key. When we export to a file just below,
   -- the file will not include the private key.
   alter certificate [##MS_AgentSigningCertificate##] remove private key

   IF (@@error <> 0)
      RAISERROR('Cannot alter ##MS_AgentSigningCertificate## in msdb. INSTMSDB.SQL terminating.', 20, 127) WITH LOG

   -- Now we export the certificate to a file so that it can be imported into the master database.
   -- Use the data directory to persist the file.
   --
   if (@certificate_name is null)
      RAISERROR('Cannot deduce path for temporary certificate ##MS_AgentSigningCertificate##.', 20, 127) WITH LOG

   declare @certError int

   PRINT 'Exporting ##MS_AgentSigningCertificate## to ' + @certificate_name
   BEGIN TRY
      EXECUTE(N'backup certificate [##MS_AgentSigningCertificate##] to file = ' + @certificate_nameQuoted)
      select @certError = @@error
   END TRY
   BEGIN CATCH
      -- Error 15240 happens when instmsdb.sql is run repeatedly and directly on a sql server, so the
      -- certificate already exists on disk and so the file cannot be written.
      -- It should not happen during the mkmastr build stage since the .cer file was already deleted.
      if (ERROR_NUMBER() != 15240)
      BEGIN
         select @certError = ERROR_NUMBER()
         PRINT 'Error ' + CONVERT(NVARCHAR(20), @certError) + ' backing up certificate.'
      END
      ELSE
      BEGIN
         PRINT 'Could not export certificate, trying again with random name. If this is a mkmastr build then this is an error.'
         SELECT @certificate_name = SUBSTRING(@certificate_name, 1, CHARINDEX(N'.cer', @certificate_name) - 1) + 
                                    CONVERT(NVARCHAR(520), NEWID()) + N'.cer' 
         SELECT @certificate_nameQuoted = '''' + REPLACE(@certificate_name, '''', '''''') + ''''

         update #InstmsdbAgentCertPath set AgentCert = @certificate_name

         PRINT 'Exporting ##MS_AgentSigningCertificate## to ' + @certificate_name 
         EXECUTE(N'backup certificate [##MS_AgentSigningCertificate##] to file = ' + @certificate_nameQuoted)
         select @certError = @@error
      END
   END CATCH
   
   IF (@certError <> 0)
      RAISERROR('Cannot backup ##MS_AgentSigningCertificate##. INSTMSDB.SQL terminating.', 20, 127) WITH LOG
END

drop table #InstmsdbAgentCertPath

-- Now we want to grant the signed stored procedures access to the
-- master database. To allow the cross-database call from msdb, we
-- will recreate the Agent certificate in the master database and
-- grant execute permission to it.

-- Switch to master so the certificate is recreated there
use master

if exists (select * from sys.database_principals where name = '##MS_AgentSigningCertificate##')
   drop user [##MS_AgentSigningCertificate##]

if exists (select * from sys.server_principals where name = '##MS_AgentSigningCertificate##')
   drop login [##MS_AgentSigningCertificate##]

if exists (select * from sys.certificates where name = '##MS_AgentSigningCertificate##')
   drop certificate [##MS_AgentSigningCertificate##]

-- Recreate the certificate (minus the private key, which we dropped earlier) from the file
-- into the master database.
PRINT 'Creating ##MS_AgentSigningCertificate## in master from ' + @certificate_name
execute(N'create certificate [##MS_AgentSigningCertificate##] from file = ' + @certificate_nameQuoted)
IF (@@error <> 0)
   RAISERROR('Cannot create ##MS_AgentSigningCertificate## certificate in master. INSTMSDB.SQL terminating.', 20, 127) WITH LOG

-- create a login in the master database based on this certicate.
--
create login [##MS_AgentSigningCertificate##] from certificate [##MS_AgentSigningCertificate##]
IF (@@error <> 0)
   RAISERROR('Cannot create ##MS_AgentSigningCertificate## login. INSTMSDB.SQL terminating.', 20, 127) WITH LOG

-- create certificate-based user for execution granting
--
create user [##MS_AgentSigningCertificate##] for certificate [##MS_AgentSigningCertificate##]
IF (@@error <> 0)
   RAISERROR('Cannot create ##MS_AgentSigningCertificate## user. INSTMSDB.SQL terminating.', 20, 127) WITH LOG

-- enable certificate for OBD
--
exec sys.sp_SetOBDCertificate N'##MS_AgentSigningCertificate##',N'ON'

grant execute to [##MS_AgentSigningCertificate##]
PRINT 'Successfully granted execute permission in master to ##MS_AgentSigningCertificate##.'

use msdb
go

--
-- End of signing sps
go

if not exists (select * from [dbo].[sysssispackagefolders] where [folderid] = '00000000-0000-0000-0000-000000000000')
BEGIN
-- Insert our root folder
DECLARE  @advopt_old_value    INT 
DECLARE  @comp_old_value   INT
EXECUTE #sp_enable_component 'Agent XPs', @advopt_old_value out, @comp_old_value out
EXEC sp_ssis_addfolder NULL, '', '00000000-0000-0000-0000-000000000000'
-- Insert the 'Maintenance Plans' node at the root.
-- Note this GUID must never change - 08aa12d5-8f98-4dab-a4fc-980b150a5dc8
EXEC sp_ssis_addfolder '00000000-0000-0000-0000-000000000000', 'Maintenance Plans', '08aa12d5-8f98-4dab-a4fc-980b150a5dc8'
EXECUTE #sp_restore_component_state 'Agent XPs', @advopt_old_value, @comp_old_value
END
GO

/**************************************************************/
/* Enable Logshipping (Yukon)                                 */
/**************************************************************/
declare @retcode int
exec @retcode = sys.sp_logshippinginstallmetadata
if (@retcode = 0)
begin
raiserror('Logshipping enabled successfully', 10, 1)
end
else
begin
raiserror('Logshipping not enabled for this edition', 10, 1)
end
go

/**************************************************************/
/* Drop auxilary procedure to enable OBD component          */
/**************************************************************/
DROP PROCEDURE #sp_enable_component 
go
DROP PROCEDURE #sp_restore_component_state 
go

-- enable policy checking 
IF EXISTS (SELECT * FROM sys.server_triggers WHERE name = N'syspolicy_server_trigger')
    ENABLE TRIGGER [syspolicy_server_trigger] ON ALL SERVER 

-- Restore old state of 'allow updates'
-- Skip on Managed Instance since it's not supported.
IF (SERVERPROPERTY('EngineEdition') <> 8)
EXECUTE master.dbo.sp_configure N'allow updates', 0
go
RECONFIGURE WITH OVERRIDE
go

-- Indicator that the entire script completed successfully.
UPDATE msdb.dbo.msdb_version SET upgrade_script_completed = 1
go

-- On Managed Instance we may have skipped execution of the script
-- by setting NOEXEC to ON. Reverting it here.
SET NOEXEC OFF
go

PRINT ''
PRINT '-------------------------------------------'
PRINT 'Execution of MSDB_POST_INSTALL.SQL complete'
PRINT '-------------------------------------------'
go

CHECKPOINT
go



use msdb
go

DECLARE @CMDExec        sysname

EXECUTE master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE',
                                        N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent\SubSystems',
                                        N'CmdExec',
                                        @CMDExec OUTPUT,
                                        N'no_output'
IF @CMDExec IS NOT NULL                                        
BEGIN                                        
  PRINT ''
  PRINT 'Remove subsystem definition from registry...'
  --remove subsystem definition from registry and populate subsystem table
  DECLARE @ret INT
  EXEC @ret = master..xp_instance_regdeletekey N'HKEY_LOCAL_MACHINE',
                          N'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent\SubSystems'
  IF @ret <> 0
    RAISERROR('Failed to remove subsystems definition from registry', 10, 127) 
END

PRINT ''
PRINT 'Populate syssubsystem table...'

EXEC @ret = sp_enum_sqlagent_subsystems
IF @ret <> 0
  RAISERROR('Failed to add subsystems definition to syssubsystem table. Upgrade script terminating', 20, 127) WITH LOG
go
--restore Shiloh object permission
DECLARE @state_desc			  nvarchar(60)
DECLARE @permission_name	sysname
DECLARE @object_name		  sysname
DECLARE @grantee_name		  sysname 

DECLARE perms_set_cursor CURSOR LOCAL FOR 
	SELECT state_desc, permission_name, object_name, grantee_name from msdb.dbo.upgrade_perms

OPEN perms_set_cursor
   FETCH NEXT FROM perms_set_cursor INTO @state_desc, @permission_name, @object_name, @grantee_name
   WHILE (@@fetch_status = 0)
   BEGIN
      --PRINT @state_desc + N' ' + @permission_name + N' ON ' + @object_name + N' TO ' + @grantee_name
      BEGIN TRY
        EXEC (@state_desc + N' ' + @permission_name + N' ON ' + @object_name + N' TO ' + @grantee_name)
      END TRY
      BEGIN CATCH
      END CATCH
      FETCH NEXT FROM perms_set_cursor INTO @state_desc, @permission_name, @object_name, @grantee_name
   END
DEALLOCATE perms_set_cursor
--remove public from SQLAgent Shiloh procedures
PRINT ''
PRINT 'Revoke any permission to public role...'
BEGIN TRY
REVOKE ALL ON sp_add_alert																	FROM PUBLIC
REVOKE ALL ON sp_add_category																FROM PUBLIC
REVOKE ALL ON sp_add_job																		FROM PUBLIC
REVOKE ALL ON sp_add_jobschedule														FROM PUBLIC
REVOKE ALL ON sp_add_jobserver															FROM PUBLIC
REVOKE ALL ON sp_add_jobstep																FROM PUBLIC
REVOKE ALL ON sp_add_notification														FROM PUBLIC
REVOKE ALL ON sp_add_operator																FROM PUBLIC
REVOKE ALL ON sp_add_targetservergroup											FROM PUBLIC
REVOKE ALL ON sp_add_targetsvrgrp_member										FROM PUBLIC
REVOKE ALL ON sp_apply_job_to_targets												FROM PUBLIC
REVOKE ALL ON sp_convert_jobid_to_char											FROM PUBLIC
REVOKE ALL ON sp_delete_alert																FROM PUBLIC
REVOKE ALL ON sp_delete_all_msx_jobs												FROM PUBLIC
REVOKE ALL ON sp_delete_category														FROM PUBLIC
REVOKE ALL ON sp_delete_job																	FROM PUBLIC
REVOKE ALL ON sp_delete_job_references											FROM PUBLIC
REVOKE ALL ON sp_delete_jobschedule													FROM PUBLIC
REVOKE ALL ON sp_delete_jobserver														FROM PUBLIC
REVOKE ALL ON sp_delete_jobstep															FROM PUBLIC
REVOKE ALL ON sp_delete_notification												FROM PUBLIC
REVOKE ALL ON sp_delete_operator														FROM PUBLIC
REVOKE ALL ON sp_delete_targetserver												FROM PUBLIC
REVOKE ALL ON sp_delete_targetservergroup										FROM PUBLIC
REVOKE ALL ON sp_delete_targetsvrgrp_member									FROM PUBLIC
REVOKE ALL ON sp_downloaded_row_limiter											FROM PUBLIC
REVOKE ALL ON sp_generate_server_description								FROM PUBLIC
REVOKE ALL ON sp_generate_target_server_job_assignment_sql	FROM PUBLIC
REVOKE ALL ON sp_get_chunked_jobstep_params									FROM PUBLIC
REVOKE ALL ON sp_get_composite_job_info											FROM PUBLIC
REVOKE ALL ON sp_get_job_alerts															FROM PUBLIC
REVOKE ALL ON sp_get_jobstep_db_username										FROM PUBLIC
REVOKE ALL ON sp_get_message_description										FROM PUBLIC
REVOKE ALL ON sp_get_schedule_description										FROM PUBLIC
REVOKE ALL ON sp_get_sqlagent_properties										FROM PUBLIC
REVOKE ALL ON sp_help_alert																	FROM PUBLIC
REVOKE ALL ON sp_help_category															FROM PUBLIC
REVOKE ALL ON sp_help_downloadlist													FROM PUBLIC
REVOKE ALL ON sp_help_job																		FROM PUBLIC
REVOKE ALL ON sp_help_jobhistory														FROM PUBLIC
REVOKE ALL ON sp_help_jobschedule														FROM PUBLIC
REVOKE ALL ON sp_help_jobserver															FROM PUBLIC
REVOKE ALL ON sp_help_jobstep																FROM PUBLIC
REVOKE ALL ON sp_help_notification													FROM PUBLIC
REVOKE ALL ON sp_help_operator															FROM PUBLIC
REVOKE ALL ON sp_help_operator_jobs													FROM PUBLIC
REVOKE ALL ON sp_help_targetserver													FROM PUBLIC
REVOKE ALL ON sp_help_targetservergroup											FROM PUBLIC
REVOKE ALL ON sp_is_sqlagent_starting												FROM PUBLIC
REVOKE ALL ON sp_jobhistory_row_limiter											FROM PUBLIC
REVOKE ALL ON sp_manage_jobs_by_login												FROM PUBLIC
REVOKE ALL ON sp_msx_defect																	FROM PUBLIC
REVOKE ALL ON sp_msx_enlist																	FROM PUBLIC
REVOKE ALL ON sp_multi_server_job_summary										FROM PUBLIC
REVOKE ALL ON sp_post_msx_operation													FROM PUBLIC
REVOKE ALL ON sp_purge_jobhistory														FROM PUBLIC
REVOKE ALL ON sp_remove_job_from_targets										FROM PUBLIC
REVOKE ALL ON sp_resync_targetserver												FROM PUBLIC
REVOKE ALL ON sp_sem_add_message														FROM PUBLIC
REVOKE ALL ON sp_sem_drop_message														FROM PUBLIC
REVOKE ALL ON sp_set_local_time															FROM PUBLIC
REVOKE ALL ON sp_set_sqlagent_properties										FROM PUBLIC
REVOKE ALL ON sp_sqlagent_check_msx_version									FROM PUBLIC
REVOKE ALL ON sp_sqlagent_get_perf_counters									FROM PUBLIC
REVOKE ALL ON sp_sqlagent_get_startup_info									FROM PUBLIC
REVOKE ALL ON sp_sqlagent_has_server_access									FROM PUBLIC
REVOKE ALL ON sp_sqlagent_log_jobhistory										FROM PUBLIC
REVOKE ALL ON sp_sqlagent_notify														FROM PUBLIC
REVOKE ALL ON sp_sqlagent_probe_msx													FROM PUBLIC
REVOKE ALL ON sp_sqlagent_refresh_job												FROM PUBLIC
REVOKE ALL ON sp_start_job																	FROM PUBLIC
REVOKE ALL ON sp_stop_job																		FROM PUBLIC
REVOKE ALL ON sp_target_server_summary											FROM PUBLIC
REVOKE ALL ON sp_uniquetaskname															FROM PUBLIC
REVOKE ALL ON sp_update_alert																FROM PUBLIC
REVOKE ALL ON sp_update_category														FROM PUBLIC
REVOKE ALL ON sp_update_job																	FROM PUBLIC
REVOKE ALL ON sp_update_jobschedule													FROM PUBLIC
REVOKE ALL ON sp_update_jobstep															FROM PUBLIC
REVOKE ALL ON sp_update_notification												FROM PUBLIC
REVOKE ALL ON sp_update_operator														FROM PUBLIC
REVOKE ALL ON sp_update_targetservergroup										FROM PUBLIC
REVOKE ALL ON sp_verify_alert																FROM PUBLIC
REVOKE ALL ON sp_verify_category														FROM PUBLIC
REVOKE ALL ON sp_verify_job																	FROM PUBLIC
REVOKE ALL ON sp_verify_job_date														FROM PUBLIC
REVOKE ALL ON sp_verify_job_identifiers											FROM PUBLIC
REVOKE ALL ON sp_verify_job_time														FROM PUBLIC
REVOKE ALL ON sp_verify_jobproc_caller											FROM PUBLIC
REVOKE ALL ON sp_verify_jobstep															FROM PUBLIC
REVOKE ALL ON sp_verify_notification												FROM PUBLIC
REVOKE ALL ON sp_verify_operator														FROM PUBLIC
REVOKE ALL ON sp_verify_performance_condition								FROM PUBLIC
REVOKE ALL ON sp_verify_subsystem														FROM PUBLIC
END TRY
BEGIN CATCH
END CATCH
go
--remove public permission from Shiloh objects that have been secured in Shiloh and overwritten during instmsdb.sql
--create a temporary table with objects granted to public during execution of instmsdb.sql
CREATE TABLE #instmsdb_public_objects(object_name sysname)

INSERT INTO #instmsdb_public_objects VALUES (N'backupfile')
INSERT INTO #instmsdb_public_objects VALUES (N'backupmediafamily')
INSERT INTO #instmsdb_public_objects VALUES (N'backupmediaset')
INSERT INTO #instmsdb_public_objects VALUES (N'backupset')
INSERT INTO #instmsdb_public_objects VALUES (N'restorehistory')
INSERT INTO #instmsdb_public_objects VALUES (N'restorefile')
INSERT INTO #instmsdb_public_objects VALUES (N'restorefilegroup')
INSERT INTO #instmsdb_public_objects VALUES (N'logmarkhistory')
INSERT INTO #instmsdb_public_objects VALUES (N'suspect_pages')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_get_dtsversion')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_make_dtspackagename')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_add_dtspackage')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_drop_dtspackage')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_reassign_dtspackageowner')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_get_dtspackage')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_enum_dtspackages')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_log_dtspackage_begin')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_log_dtspackage_end')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_log_dtsstep_begin')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_log_dtsstep_end')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_log_dtstask')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_enum_dtspackagelog')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_enum_dtssteplog')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_enum_dtstasklog')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_dump_dtslog_all')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_dump_dtspackagelog')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_dump_dtssteplog')
INSERT INTO #instmsdb_public_objects VALUES (N'sp_dump_dtstasklog')
go

DECLARE @object_name		  sysname
DECLARE @tsql             nvarchar(300)
DECLARE public_remove_cursor CURSOR LOCAL FOR 
	SELECT object_name FROM #instmsdb_public_objects

OPEN public_remove_cursor
   FETCH NEXT FROM public_remove_cursor INTO @object_name
   WHILE (@@fetch_status = 0)
   BEGIN
      --PRINT @state_desc + N' ' + @permission_name + N' ON ' + @object_name + N' TO ' + @grantee_name
      BEGIN TRY
      --if this object exists in 8.0 msdb and is granted to public no op otherwise remove permission to public on it
        IF (EXISTS (SELECT * FROM msdb.dbo.upgrade_perms 
              WHERE UPPER(object_name collate SQL_Latin1_General_CP1_CS_AS ) = UPPER(@object_name collate SQL_Latin1_General_CP1_CS_AS )))
            AND (NOT EXISTS (SELECT * FROM msdb.dbo.upgrade_perms WHERE object_name = @object_name 
                 AND UPPER(grantee_name collate SQL_Latin1_General_CP1_CS_AS ) = N'PUBLIC'))
          BEGIN
            SELECT @tsql = N'REVOKE ALL ON ' + QUOTENAME(@object_name) + N' FROM PUBLIC'
            PRINT @tsql
            EXEC (@tsql)
          END
      END TRY
      BEGIN CATCH
      END CATCH
      FETCH NEXT FROM public_remove_cursor INTO @object_name
   END
DEALLOCATE public_remove_cursor
go
DROP TABLE #instmsdb_public_objects
DROP TABLE msdb.dbo.upgrade_perms
go

--------------------------------------------------------------------------------
--update proxy account
--proxy update batch
--read sysadminonly flag
DECLARE @ret              INT
DECLARE @proxy_id         INT
DECLARE @job_id           UNIQUEIDENTIFIER
DECLARE @step_id          INT
DECLARE @subsystem        sysname
DECLARE @owner_name       NVARCHAR(256)
DECLARE @full_name        sysname
DECLARE @owner_sid        VARBINARY(85)
DECLARE @is_sysadmin      INT

--walk throu all job steps excluding TSQL jobsteps
DECLARE jobsteps_cursor CURSOR LOCAL FOR
SELECT js.job_id, js.step_id, js.subsystem, SUSER_SNAME(j.owner_sid)
FROM sysjobs j JOIN sysjobsteps js ON j.job_id = js.job_id
WHERE UPPER(js.subsystem collate SQL_Latin1_General_CP1_CS_AS) <> N'TSQL'
AND SUSER_SNAME(j.owner_sid) IS NOT NULL
FOR READ ONLY

--wals thru all non sysadmin job owners
DECLARE job_nonsysadmin_owners_cursor CURSOR LOCAL FOR
SELECT DISTINCT j.owner_sid FROM sysjobs j 
FOR READ ONLY

OPEN job_nonsysadmin_owners_cursor
FETCH NEXT FROM job_nonsysadmin_owners_cursor INTO @owner_sid
WHILE (@@fetch_status = 0)
BEGIN
	SELECT @owner_name = SUSER_SNAME(@owner_sid)
  IF @owner_name IS NOT NULL
  BEGIN
    --is job owner member of sysadmin role?
    BEGIN TRY
      EXECUTE AS LOGIN=@owner_name -- impersonate 
      SELECT @is_sysadmin = ISNULL(IS_SRVROLEMEMBER('sysadmin'),0) -- check role membership 
      REVERT -- revert back
    END TRY
    BEGIN CATCH
      SET @is_sysadmin = 0
    END CATCH
          
    IF @is_sysadmin = 0
    BEGIN
	    --add job_owner to the SQLAgentUserRole msdb role in order to permit the job owner to handle his jobs
	    --has this login a user in msdb?
	    IF NOT EXISTS(SELECT * FROM sys.database_principals WHERE (sid = @owner_sid) OR (LOWER(name collate SQL_Latin1_General_CP1_CS_AS) = LOWER(@owner_name collate SQL_Latin1_General_CP1_CS_AS)))
	    BEGIN
		    PRINT ''
		    PRINT 'Granting login access''' + @owner_name + ''' to msdb database...'
		    BEGIN TRY
		      EXEC sp_grantdbaccess @loginame = @owner_name
        END TRY
        BEGIN CATCH
          RAISERROR('A problem was encountered granting access to MSDB database for login ''%s''. Make sure this login is provisioned with SQLServer and rerun sqlagent_msdb_upgrade.sql ', 10, 127) WITH LOG
        END CATCH		      
	    END

	    PRINT ''
	    PRINT 'Adding user ''' + @owner_name + ''' to SQLAgentUserRole msdb role...'
		  BEGIN TRY
	      EXEC sp_addrolemember @rolename = 'SQLAgentUserRole', @membername = @owner_name
      END TRY
      BEGIN CATCH
        RAISERROR('A problem was encountered adding user ''%s'' to SQLAgentUserRole. Make sure this is a valid user in MSDB database and rerun sqlagent_msdb_upgrade.sql ', 10, 127) WITH LOG
      END CATCH		      
	  END
	END
	FETCH NEXT FROM job_nonsysadmin_owners_cursor INTO @owner_sid
END
DEALLOCATE job_nonsysadmin_owners_cursor
  

--credential created from LSA values in the file src\upgrade_scripts\sqlagent100_upgrade.sql
IF EXISTS (SELECT credential_id FROM master.sys.credentials  WHERE  name = N'UpgradedCredential')
BEGIN
  IF (NOT EXISTS(SELECT * FROM sysproxies WHERE name = N'UpgradedProxyAccount'))
  BEGIN  
    PRINT 'Update proxy account'
    SELECT @ret = 0

    --create the proxy first
    PRINT ''
    PRINT 'Adding Proxy...'
    BEGIN TRY --prevent sp_add_proxy raising severity 16 error that will terminate upgrade scritp when proxy account has bad info
      EXEC @ret = sp_add_proxy @proxy_name      = N'UpgradedProxyAccount',
                                @credential_name = N'UpgradedCredential',
                                @proxy_id        = @proxy_id OUTPUT
    END TRY
    BEGIN CATCH
      SET @ret = 1
    END CATCH      
                              
    IF @ret <> 0
    BEGIN
      RAISERROR('A problem was encountered updating proxy account. Verify that user name and secret of credential [UpgradedCredential] are correct and rerun sqlagent_msdb_upgrade.sql ', 10, 127) WITH LOG
    END
    ELSE
    BEGIN
      OPEN jobsteps_cursor
      FETCH NEXT FROM jobsteps_cursor INTO @job_id, @step_id, @subsystem, @owner_name

      WHILE (@@fetch_status = 0)
      BEGIN
        --is job owner member of sysadmin role?
        BEGIN TRY
          EXECUTE AS LOGIN = @owner_name -- impersonate 
          SELECT @is_sysadmin = ISNULL(IS_SRVROLEMEMBER('sysadmin'),0) -- check role membership 
          REVERT -- revert back
        END TRY
        BEGIN CATCH
          SET @is_sysadmin = 0
        END CATCH
        
        IF @is_sysadmin = 0
        BEGIN        
          IF NOT EXISTS(SELECT * FROM sysproxysubsystem ps JOIN syssubsystems s 
                        ON ps.subsystem_id = s.subsystem_id
                        WHERE s.subsystem = @subsystem
                        AND ps.proxy_id = @proxy_id)
          BEGIN
            PRINT 'Grant permission to proxy ''UpgradedProxyAccount'' for subsystem ''' + @subsystem + '''...'
            BEGIN TRY
              EXEC @ret = sp_grant_proxy_to_subsystem @subsystem_name = @subsystem,
											          @proxy_id       = @proxy_id
					  END TRY
					  BEGIN CATCH
					    SET @ret = 1
					  END CATCH
            IF @ret <> 0
            BEGIN
              RAISERROR('FAILED TO GRANT PERMISSION TO PROXY (%d) FOR SUBSYSTEM ''%s''.', 10, 127, @proxy_id, @subsystem ) WITH LOG
            END
	        END

          IF NOT EXISTS(SELECT * FROM sysproxylogin WHERE proxy_id = @proxy_id AND sid = SUSER_SID(@owner_name))
          BEGIN
            PRINT 'Grant login ''' + @owner_name + '''  permission to use proxy ''UpgradedProxyAccount'' for subsystem ''' + @subsystem + '''...'
            BEGIN TRY
              EXEC @ret = sp_grant_login_to_proxy @proxy_id       = @proxy_id,
                                                  @login_name     = @owner_name
            END TRY
					  BEGIN CATCH
					    SET @ret = 1
					  END CATCH
            IF @ret <> 0
            BEGIN
              RAISERROR('FAILED TO GRANT PERMISSION TO LOGIN ''%s'' FOR PROXY (%d).', 10, 127, @owner_name, @proxy_id) WITH LOG
            END
          END

          --PRINT 'Update sysjobsteps...'
          UPDATE sysjobsteps SET proxy_id = @proxy_id WHERE job_id = @job_id and step_id = @step_id
        END --is_sysadmin = 0
        FETCH NEXT FROM jobsteps_cursor INTO @job_id, @step_id, @subsystem, @owner_name
      END --WHILE (@@fetch_status = 0)
      CLOSE jobsteps_cursor
    END
  END -- NOT EXISTS(SELECT * FROM sysproxies WHERE name = N'UpgradedProxyAccount')
END  -- EXISTS (SELECT credential_id FROM master.sys.credentials  WHERE  name = N'AgentCred80') 
DEALLOCATE jobsteps_cursor                          
go

--msx proxy upgrade
DECLARE @credential_id          INT
--credential created from 80 lSA values in the file src\upgrade_scripts\sqlagent100_upgrade.sql
IF EXISTS (SELECT credential_id FROM master.sys.credentials  WHERE  name = N'UpgradedMSXCredential')
BEGIN

    SELECT @credential_id = credential_id FROM master.sys.credentials
    WHERE  name = N'UpgradedMSXCredential'

    --set credential_id to agent registry
      EXECUTE master.dbo.xp_instance_regwrite  'HKEY_LOCAL_MACHINE',
                                      'SOFTWARE\Microsoft\MSSQLServer\SQLServerAgent',
                                      'MSXCredentialID',
                                      'REG_DWORD', 
                                      @credential_id
END      
go

-- Complete replication job security meta-data upgrades
IF OBJECT_ID('sys.sp_vupgrade_replsecurity_metadata', 'P') IS NOT NULL
BEGIN
	PRINT 'Performing replication job security meta-data upgrades...'
	EXECUTE master.sys.sp_vupgrade_replsecurity_metadata
END
ELSE
BEGIN
	-- "The replication security meta-data could not be upgraded for all database(s). Please ensure that entire server is upgraded and re-execute sp_vupgrade_replsecurity_metadata."
	RAISERROR(21450, 10, -1, 'security meta-data', 'all',  'entire server', 'master.sys.sp_vupgrade_replsecurity_metadata') WITH LOG	
END
GO


-- Log Shipping Upgrade
declare @retcode int
PRINT ''
PRINT 'Upgrading SQL Server Log Shipping...'
exec @retcode = master.sys.sp_upgrade_log_shipping
if @retcode != 0 or @@error != 0
begin
  RAISERROR('Upgrade of Log Shipping for SQL Server did not complete successfully. After upgrade, re-execute sp_upgrade_log_shipping from master database.', 10, 1) WITH LOG
end
PRINT 'Upgraded SQL Server Log Shipping successfully.'
go

-----------------------------------------------------------------------------
--Drop legacy objects
CREATE TABLE #instmsdb_legacy_objects(object_name sysname, type_name sysname, type_id nvarchar(2))
--tables
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.mswebtasks', N'TABLE', N'T' )
--procedures
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_helphistory', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_helptask', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_insmswebtask', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_purgehistory', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_sem_get_perf_counter_help', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_updatetask', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_updmswebtask', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_verify_jobschedule', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_verifytaskid', N'PROCEDURE', N'P' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sp_check_localserver_name', N'PROCEDURE', N'P' )

--views
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.sysalternates', N'VIEW', N'V' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.systasks', N'VIEW', N'V' )
INSERT INTO #instmsdb_legacy_objects VALUES (N'dbo.systasks_view', N'VIEW', N'V' )
go

DECLARE @object_name		  sysname
DECLARE @type_name		    sysname
DECLARE @type_id 		      nvarchar(2)

DECLARE @tsql             nvarchar(300)
DECLARE legacy_remove_cursor CURSOR LOCAL FOR 
	SELECT object_name, type_name, type_id FROM #instmsdb_legacy_objects

OPEN legacy_remove_cursor
   FETCH NEXT FROM legacy_remove_cursor INTO @object_name, @type_name, @type_id
   WHILE (@@fetch_status = 0)
   BEGIN
      BEGIN TRY
        IF (OBJECT_ID(@object_name, @type_id) IS NOT NULL)
          BEGIN
            SELECT @tsql = N'DROP ' + @type_name + N' ' +  @object_name
            PRINT @tsql
            EXEC (@tsql)
          END
      END TRY
      BEGIN CATCH
      END CATCH
      FETCH NEXT FROM legacy_remove_cursor INTO @object_name, @type_name, @type_id
   END
CLOSE legacy_remove_cursor   
DEALLOCATE legacy_remove_cursor
go
DROP TABLE #instmsdb_legacy_objects
go
--other legacy objects CTP15 to RTM only
BEGIN TRY
if exists (select * from sys.database_principals where name = 'MS_AgentSigningCertificateUser')
   drop user MS_AgentSigningCertificateUser

if exists (select * from sys.server_principals where name = 'MS_AgentSigningCertificateLogin')
   drop login MS_AgentSigningCertificateLogin
   
-- remove the MaintenanceUserRole role CTP15 to RTM only
IF (EXISTS (SELECT *
            FROM msdb.dbo.sysusers
            WHERE (name = N'MaintenanceUserRole')
              AND (issqlrole = 1)))
BEGIN
  BEGIN
    EXECUTE msdb.dbo.sp_droprole @rolename = N'MaintenanceUserRole'
  END
END
END TRY
BEGIN CATCH
END CATCH
go


PRINT ''
PRINT '------------------------------------------'
PRINT 'Execution of POST_SQLAGENT100.SQL complete'
PRINT '------------------------------------------'
go

-------------------------------------------------------------

PRINT '------------------------------------'
PRINT 'Moving 2005 SSIS Data to 2008 tables'
PRINT '------------------------------------'

--
-- Add the old roles to the new SSIS role membership
--

PRINT 'Mapping SSIS yukon roles to katmai roles...'
GO

if exists (select * from dbo.sysusers where [name] = N'db_dtsadmin' and [issqlrole] = 1)
BEGIN
EXEC sp_addrolemember N'db_ssisadmin', N'db_dtsadmin'
END
GO

if exists (select * from dbo.sysusers where [name] = N'db_dtsltduser' and [issqlrole] = 1)
BEGIN
EXEC sp_addrolemember N'db_ssisltduser', N'db_dtsltduser'
END
GO

if exists (select * from dbo.sysusers where [name] = N'db_dtsoperator' and [issqlrole] = 1)
BEGIN
EXEC sp_addrolemember N'db_ssisoperator', N'db_dtsoperator'
END
GO

--
-- Move folders from sysdtspackagefolders90 to sysssispackagefolders
--

PRINT 'Moving package folders...'
GO

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sysdtspackagefolders90]') AND type = N'U') 
INSERT INTO [msdb].[dbo].[sysssispackagefolders]
           ([folderid]
           ,[parentfolderid]
           ,[foldername])
    SELECT [folderid]
           ,[parentfolderid]
           ,[foldername]
    FROM [msdb].[dbo].[sysdtspackagefolders90]
    WHERE [folderid] != '00000000-0000-0000-0000-000000000000' -- Root folder
      AND [folderid] != '08aa12d5-8f98-4dab-a4fc-980b150a5dc8' -- Maintenance Plans

GO

--
-- Move packages from sysdtspackages90 to sysssispackages
--

PRINT 'Moving packages...'
GO

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sysdtspackages90]') AND type = N'U') 
INSERT INTO [msdb].[dbo].[sysssispackages]
           ([name]
           ,[id]
           ,[description]
           ,[createdate]
           ,[folderid]
           ,[ownersid]
           ,[packagedata]
           ,[packageformat]
           ,[packagetype]
           ,[vermajor]
           ,[verminor]
           ,[verbuild]
           ,[vercomments]
           ,[verid]
           ,[isencrypted]
           ,[readrolesid]
           ,[writerolesid])
    SELECT [name]
           ,[id]
           ,[description]
           ,[createdate]
           ,[folderid]
           ,[ownersid]
           ,[packagedata]
           ,[packageformat]
           ,[packagetype]
           ,[vermajor]
           ,[verminor]
           ,[verbuild]
           ,[vercomments]
           ,[verid]
           ,[isencrypted]
           ,[readrolesid]
           ,[writerolesid]
    FROM [msdb].[dbo].[sysdtspackages90]

GO

--
-- Move log data from sysdtslog90 to sysssislog
--

PRINT 'Moving logs...'
GO

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sysdtslog90]') AND type = N'U') 
INSERT INTO [msdb].[dbo].[sysssislog]
           ([event]
           ,[computer]
           ,[operator]
           ,[source]
           ,[sourceid]
           ,[executionid]
           ,[starttime]
           ,[endtime]
           ,[datacode]
           ,[databytes]
           ,[message])
    SELECT [event]
           ,[computer]
           ,[operator]
           ,[source]
           ,[sourceid]
           ,[executionid]
           ,[starttime]
           ,[endtime]
           ,[datacode]
           ,[databytes]
           ,[message]
    FROM [msdb].[dbo].[sysdtslog90]

GO

--
-- Drop yukon objects
--

PRINT 'Dropping yukon stored procedures...'
GO

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_setpackageroles]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_setpackageroles]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_getpackageroles]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_getpackageroles]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_deletepackage]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_deletepackage] 

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_getpackage]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_getpackage]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_putpackage]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_putpackage]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_checkexists]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_checkexists]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_listpackages]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_listpackages]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_addlogentry]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_addlogentry]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_deletefolder]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_deletefolder] 

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_addfolder]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_addfolder]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_renamefolder]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_renamefolder]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_getfolder]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_getfolder]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sp_dts_listfolders]') AND type = N'P') 
DROP PROCEDURE [dbo].[sp_dts_listfolders]

GO

PRINT 'Dropping yukon tables...'
GO

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sysdtslog90]') AND type = N'U') 
DROP TABLE [dbo].[sysdtslog90]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sysdtspackages90]') AND type = N'U') 
DROP TABLE [dbo].[sysdtspackages90]

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sysdtspackagefolders90]') AND type = N'U')  
DROP TABLE [dbo].[sysdtspackagefolders90]

GO

--
-- Create a view for the logs table for backwards compatibility
--

PRINT 'Creating sysdtslog90 view...'
GO

IF EXISTS (select * from sys.objects where object_id = object_id(N'[dbo].[sysdtslog90]') and type = N'V')
BEGIN
	DROP VIEW [dbo].[sysdtslog90]
END

GO

CREATE VIEW [dbo].[sysdtslog90]
AS
	SELECT [id]
		  ,[event]
		  ,[computer]
		  ,[operator]
		  ,[source]
		  ,[sourceid]
		  ,[executionid]
		  ,[starttime]
		  ,[endtime]
		  ,[datacode]
		  ,[databytes]
		  ,[message]
	  FROM [msdb].[dbo].[sysssislog]

GO

-------------------------------------------------------------

/*********************************************************************/
/* Create auxilary procedure to enable OBD (Off By Default component */
/*********************************************************************/
IF (OBJECT_ID(N'tempdb..#sp_enable_component', 'P') IS NOT NULL)
BEGIN
    DROP PROCEDURE [tempdb]..[#sp_enable_component]
END
GO 

CREATE PROCEDURE #sp_enable_component     
   @comp_name     sysname, 
   @advopt_old_value    INT OUT, 
   @comp_old_value   INT OUT 
AS
   BEGIN
   SELECT @advopt_old_value=cast(value_in_use as int) from sys.configurations where name = 'show advanced options';
   SELECT @comp_old_value=cast(value_in_use as int) from sys.configurations where name = @comp_name; 
   EXEC sp_configure 'show advanced options',1;
   RECONFIGURE WITH OVERRIDE;
   EXEC sp_configure @comp_name, 1; 
   RECONFIGURE WITH OVERRIDE;
   END
go

IF (OBJECT_ID(N'tempdb..#sp_restore_component_state ', 'P') IS NOT NULL)
BEGIN
    DROP PROCEDURE [tempdb]..[#sp_restore_component_state ]
END
GO 

CREATE PROCEDURE #sp_restore_component_state 
   @comp_name     sysname, 
   @advopt_old_value    INT, 
   @comp_old_value   INT 
AS
   BEGIN
   EXEC sp_configure @comp_name, @comp_old_value; 
   RECONFIGURE WITH OVERRIDE;
   EXEC sp_configure 'show advanced options',@advopt_old_value;
   RECONFIGURE WITH OVERRIDE;
   END
GO


/**************************************************************/
/* DMF Post-upgrade steps                                      */
/**************************************************************/
--
-- >>> CTP5 -> CTP6 Upgrade
--
-- Restoring previously saved data and converting TargetSets to ObjectSets
IF (OBJECT_ID('dbo.dmf_upgrade', 'U') IS NOT NULL)
BEGIN
	BEGIN TRANSACTION 

	-- Restore policies
	SET IDENTITY_INSERT dbo.syspolicy_policies_internal ON
	INSERT dbo.syspolicy_policies_internal (policy_id,name,condition_id,root_condition_id,date_created,execution_mode,policy_category_id,schedule_uid,description,help_text,help_link,is_enabled,job_id,created_by,modified_by,date_modified)
		SELECT policy_id,name,condition_id,root_condition_id,date_created,execution_mode,policy_category_id,schedule_uid,description,help_text,help_link,is_enabled,job_id,created_by,modified_by,date_modified
		FROM msdb.dbo.tmp_syspolicy_policies_internal
	SET IDENTITY_INSERT dbo.syspolicy_policies_internal OFF
	
	-- Restore Health state 
	SET IDENTITY_INSERT dbo.syspolicy_system_health_state_internal ON
	INSERT dbo.syspolicy_system_health_state_internal (health_state_id,policy_id,last_run_date,target_query_expression_with_id,target_query_expression,result)
		SELECT * FROM msdb.dbo.tmp_syspolicy_system_health_state_internal
	SET IDENTITY_INSERT dbo.syspolicy_system_health_state_internal OFF
	
	-- Restore Execution history
	SET IDENTITY_INSERT dbo.syspolicy_policy_execution_history_internal ON
	INSERT dbo.syspolicy_policy_execution_history_internal (history_id,policy_id,start_date,end_date,result,is_full_run,exception_message,exception) 
		SELECT * FROM msdb.dbo.tmp_syspolicy_policy_execution_history_internal
	SET IDENTITY_INSERT dbo.syspolicy_policy_execution_history_internal OFF

	SET IDENTITY_INSERT dbo.syspolicy_policy_execution_history_details_internal ON
	INSERT dbo.syspolicy_policy_execution_history_details_internal (detail_id,history_id,target_query_expression,target_query_expression_with_id,execution_date,result,result_detail,exception_message,exception) 
		SELECT * FROM msdb.dbo.tmp_syspolicy_policy_execution_history_details_internal
	SET IDENTITY_INSERT dbo.syspolicy_policy_execution_history_details_internal OFF
	
	-- Convert TargetSets to ObjectSets
	SET IDENTITY_INSERT dbo.syspolicy_target_sets_internal ON

	DECLARE @policy_id int, @policy_name sysname, @facet nvarchar(max), @os_name sysname, @os_id int, @ts_id int

	DECLARE ts_cur CURSOR FOR
		SELECT p.policy_id, p.name, c.facet, ts.target_set_id
		FROM msdb.dbo.tmp_syspolicy_target_sets_internal ts JOIN dbo.syspolicy_policies p ON (ts.policy_id = p.policy_id)
			JOIN dbo.syspolicy_conditions c ON (p.condition_id = c.condition_id)
		ORDER BY p.policy_id, ts.target_set_id
		
	OPEN ts_cur
	FETCH ts_cur INTO @policy_id , @policy_name , @facet, @ts_id
	WHILE @@FETCH_STATUS = 0
	BEGIN
		-- make sure ObjectSet name is unique
		SET @os_name = LEFT(@policy_name,118) + '_ObjectSet'
		IF EXISTS (SELECT * FROM dbo.syspolicy_object_sets WHERE object_set_name = @os_name)
		BEGIN
			SET @os_name = LEFT(@policy_name,82) + CAST(NEWID() AS nchar(36)) + '_ObjectSet'
		END
		
		-- if we go through multi-TS ObjectSet we only need to add it once
		-- cursor sort order guarantees @os_id remains correct 
		IF 0 = (SELECT ISNULL(object_set_id, 0) FROM syspolicy_policies WHERE policy_id = @policy_id)
		BEGIN
			Exec sp_syspolicy_add_object_set @os_name, @facet, @os_id OUTPUT 
			UPDATE syspolicy_policies_internal
				SET object_set_id = @os_id
				WHERE policy_id = @policy_id
		END
		INSERT syspolicy_target_sets_internal (target_set_id,object_set_id,type_skeleton,type,enabled) 
			SELECT target_set_id,@os_id,type_skeleton,type,1 
			FROM msdb.dbo.tmp_syspolicy_target_sets_internal WHERE target_set_id = @ts_id
		FETCH ts_cur INTO @policy_id , @policy_name , @facet, @ts_id
	END

	CLOSE ts_cur
	DEALLOCATE ts_cur

	SET IDENTITY_INSERT dbo.syspolicy_target_sets_internal OFF

	SET IDENTITY_INSERT dbo.syspolicy_target_set_levels_internal ON
	INSERT syspolicy_target_set_levels_internal (target_set_level_id,target_set_id,type_skeleton,condition_id,level_name)
		SELECT target_set_level_id,target_set_id,type_skeleton,condition_id,level_name 
		FROM msdb.dbo.tmp_syspolicy_target_set_levels_internal

	SET IDENTITY_INSERT dbo.syspolicy_target_set_levels_internal OFF

	-- Add missing levels for MultipartName sets
	DECLARE @tsl_id int
	SET @ts_id = 0
	SET @tsl_id = 0

	DECLARE @mpn_facet_id int
	SELECT @mpn_facet_id = management_facet_id FROM dbo.syspolicy_management_facets 
		WHERE name = 'IMultipartNameFacet'

	DECLARE os_cur CURSOR FOR
		SELECT object_set_id
		FROM dbo.syspolicy_object_sets_internal  
		WHERE facet_id = @mpn_facet_id 

	OPEN os_cur
	FETCH os_cur INTO @os_id
	WHILE @@FETCH_STATUS = 0
	BEGIN
		IF NOT EXISTS (SELECT * FROM syspolicy_target_sets_internal WHERE object_set_id = @os_id AND type = 'PROCEDURE')
		BEGIN
			Exec sp_syspolicy_add_target_set @object_set_id=@os_id, @type_skeleton='Server/Database/StoredProcedure', @type='PROCEDURE', @enabled=0, @target_set_id = @ts_id OUTPUT
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database', @condition_id=NULL, @level_name='Database', @target_set_level_id=@tsl_id  
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database/StoredProcedure', @condition_id=NULL, @level_name='StoredProcedure', @target_set_level_id=@tsl_id  
		END
		IF NOT EXISTS (SELECT * FROM syspolicy_target_sets_internal WHERE object_set_id = @os_id AND type = 'SYNONYM')
		BEGIN
			Exec sp_syspolicy_add_target_set @object_set_id=@os_id, @type_skeleton='Server/Database/Synonym', @type='SYNONYM', @enabled=0, @target_set_id = @ts_id OUTPUT
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database', @condition_id=NULL, @level_name='Database', @target_set_level_id=@tsl_id  
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database/Synonym', @condition_id=NULL, @level_name='Synonym', @target_set_level_id=@tsl_id  
		END
		IF NOT EXISTS (SELECT * FROM syspolicy_target_sets_internal WHERE object_set_id = @os_id AND type = 'TABLE')
		BEGIN
			Exec sp_syspolicy_add_target_set @object_set_id=@os_id, @type_skeleton='Server/Database/Table', @type='TABLE', @enabled=0, @target_set_id = @ts_id OUTPUT
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database', @condition_id=NULL, @level_name='Database', @target_set_level_id=@tsl_id  
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database/Table', @condition_id=NULL, @level_name='Table', @target_set_level_id=@tsl_id  
		END
		IF NOT EXISTS (SELECT * FROM syspolicy_target_sets_internal WHERE object_set_id = @os_id AND type = 'FUNCTION')
		BEGIN
			Exec sp_syspolicy_add_target_set @object_set_id=@os_id, @type_skeleton='Server/Database/UserDefinedFunction', @type='FUNCTION', @enabled=0, @target_set_id = @ts_id OUTPUT
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database', @condition_id=NULL, @level_name='Database', @target_set_level_id=@tsl_id  
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database/UserDefinedFunction', @condition_id=NULL, @level_name='UserDefinedFunction', @target_set_level_id=@tsl_id  
		END
		IF NOT EXISTS (SELECT * FROM syspolicy_target_sets_internal WHERE object_set_id = @os_id AND type = 'TYPE')
		BEGIN
			Exec sp_syspolicy_add_target_set @object_set_id=@os_id, @type_skeleton='Server/Database/UserDefinedType', @type='TYPE', @enabled=0, @target_set_id = @ts_id OUTPUT
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database', @condition_id=NULL, @level_name='Database', @target_set_level_id=@tsl_id  
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database/UserDefinedType', @condition_id=NULL, @level_name='UserDefinedType', @target_set_level_id=@tsl_id  
		END
		IF NOT EXISTS (SELECT * FROM syspolicy_target_sets_internal WHERE object_set_id = @os_id AND type = 'VIEW')
		BEGIN
			Exec sp_syspolicy_add_target_set @object_set_id=@os_id, @type_skeleton='Server/Database/View', @type='VIEW', @enabled=0, @target_set_id = @ts_id OUTPUT
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database', @condition_id=NULL, @level_name='Database', @target_set_level_id=@tsl_id  
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database/View', @condition_id=NULL, @level_name='View', @target_set_level_id=@tsl_id  
		END
		IF NOT EXISTS (SELECT * FROM syspolicy_target_sets_internal WHERE object_set_id = @os_id AND type = 'XMLSCHEMACOLLECTION')
		BEGIN
			Exec sp_syspolicy_add_target_set @object_set_id=@os_id, @type_skeleton='Server/Database/XmlSchemaCollection', @type='XMLSCHEMACOLLECTION', @enabled=0, @target_set_id = @ts_id OUTPUT
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database', @condition_id=NULL, @level_name='Database', @target_set_level_id=@tsl_id  
			Exec sp_syspolicy_add_target_set_level @target_set_id = @ts_id, @type_skeleton='Server/Database/XmlSchemaCollection', @condition_id=NULL, @level_name='XmlSchemaCollection', @target_set_level_id=@tsl_id  
		END
		
		FETCH os_cur INTO @os_id
	END

	CLOSE os_cur
	DEALLOCATE os_cur

	-- Clean up upgrade marker
	DROP TABLE dmf_upgrade
	
	-- And temp tables
	DROP TABLE msdb.dbo.tmp_syspolicy_target_sets_internal
	DROP TABLE msdb.dbo.tmp_syspolicy_target_set_levels_internal
	DROP TABLE msdb.dbo.tmp_syspolicy_policies_internal 
	DROP TABLE msdb.dbo.tmp_syspolicy_system_health_state_internal 
	DROP TABLE msdb.dbo.tmp_syspolicy_policy_execution_history_internal 
	DROP TABLE msdb.dbo.tmp_syspolicy_policy_execution_history_details_internal 
	
	COMMIT TRANSACTION
	PRINT 'DMF successfully upgraded' 
END

--
-- <<< CTP5 -> CTP6 Upgrade
--

-- Make sure Agent XPs are enabled regardless of the state of the server
-- We will restove the state afterwards

DECLARE @advopt_old_value int
DECLARE @comp_old_value int
EXECUTE #sp_enable_component 'Agent XPs', @advopt_old_value out, @comp_old_value out


-- sproc that creates a job for PBM. This covers the creation of job for all upgrade scenarios.
PRINT 'Executing msdb.dbo.sp_syspolicy_create_purge_job'
EXEC msdb.dbo.sp_syspolicy_create_purge_job

-- Now restore the Agent XPs to their old state
EXECUTE #sp_restore_component_state 'Agent XPs', @advopt_old_value, @comp_old_value

GO


-- Final upgrade step does not depend on any schema changes, and thus can be run for any upgrade

-- Make sure that the policies are using a valid evaluation mode
-- If a policy is using an evaluation mode that is not supported for a facet, then the evaluation mode is None and it is not enabled

UPDATE p
	SET p.execution_mode = 0, p.is_enabled = 0
	FROM 
		[msdb].[dbo].[syspolicy_policies_internal] p 
		INNER JOIN 
		[msdb].[dbo].[syspolicy_conditions] c 
		ON (p.condition_id = c.condition_id)
		INNER JOIN
		[msdb].[dbo].[syspolicy_management_facets] f
		ON (c.facet = f.name)
		WHERE p.execution_mode <> (p.execution_mode & f.execution_mode)

-- Upgrade from CTP5 & CTP6 to RC0 - correct typo in the management facet name
UPDATE [msdb].[dbo].[syspolicy_management_facets]
SET name = 'MessageType' 
WHERE name = 'Messagetype'



		
GO

/**************************************************************/
/* End of DMF Post-upgrade steps                               */
/**************************************************************/

IF (NOT OBJECT_ID(N'tempdb.dbo.#SqlAgentSignatures', 'U') IS NULL)
BEGIN
    DROP TABLE #SqlAgentSignatures
END
-------------------------------------------------------------------------
--
/**************************************************************/
/* Data Collector Post-upgrade steps                          */
/**************************************************************/
GO
-- Procedure to print Data collector status and collection set status
CREATE PROCEDURE #printdatacollectorstatus
AS
BEGIN
	DECLARE @parameter_name SYSNAME
	DECLARE @parameter_value sql_variant

	PRINT 'Data Collector Status'
	SELECT @parameter_name = parameter_name, @parameter_value = parameter_value 
	FROM  msdb.dbo.syscollector_config_store_internal
	WHERE parameter_name = 'CollectorEnabled'

	PRINT @parameter_name + ':'  + convert(varchar , @parameter_value)

	PRINT 'Collection set status'
	DECLARE @collection_set_uid UNIQUEIDENTIFIER
	DECLARE @name SYSNAME
	DECLARE @is_running BIT

	DECLARE @collection_set_cursor CURSOR
	SET @collection_set_cursor = CURSOR FOR
	SELECT collection_set_uid, name, is_running 
	FROM msdb.dbo.syscollector_collection_sets_internal

	OPEN @collection_set_cursor
	FETCH NEXT FROM @collection_set_cursor INTO @collection_set_uid, @name, @is_running

	WHILE @@FETCH_STATUS = 0
	BEGIN
		PRINT 'Uid:' + convert(varchar(50),@collection_set_uid)  + ', Name:' + @name + ', IsRunning:' + convert(varchar, @is_running)
	
		FETCH NEXT FROM @collection_set_cursor INTO @collection_set_uid, @name, @is_running
	END
	CLOSE @collection_set_cursor
	DEALLOCATE @collection_set_cursor
END
GO

-- 'Agent XPs' setting should be turned on if not error is thrown while restoring 
-- data collector status
PRINT 'post_dc100::Enabling Agent XPs before restoring data collector original state'

DECLARE @advopt_old_value int
DECLARE @comp_old_value int
EXECUTE #sp_enable_component 'Agent XPs', @advopt_old_value out, @comp_old_value out

EXECUTE #printdatacollectorstatus

-- Load the instmdw.sql script into the blob
PRINT 'post_dc100::uploading instmdw.sql to msdb ...'
EXECUTE [dbo].[sp_syscollector_upload_instmdw]

-- Restore collection set running status
PRINT 'post_dc100::Checking if collection set status were captured in temp table...'
IF (OBJECT_ID('tempdb..#data_collector_collectionset_status', 'U') IS NOT NULL)
BEGIN
	PRINT 'post_dc100::Restoring collection set running status...'

	UPDATE [dbo].[syscollector_collection_sets_internal]
	SET [dbo].[syscollector_collection_sets_internal].is_running = #data_collector_collectionset_status.is_running
	FROM #data_collector_collectionset_status 
	WHERE [dbo].[syscollector_collection_sets_internal].is_running <> #data_collector_collectionset_status.is_running
	AND [dbo].[syscollector_collection_sets_internal].collection_set_uid = #data_collector_collectionset_status.collection_set_uid
END


-- Check if temp table that holds data collector's old status before upgrade exists
-- and enable data collector if DC was enabled before running upgrade scripts
PRINT 'post_dc100::Checking if Data collector was enabled before upgrade...'
IF (OBJECT_ID('tempdb..#data_collector_status', 'U') IS NOT NULL)
BEGIN
    DECLARE @old_state_enabled_status INT

	-- Get the old state of data collector - predicate checks if the old and current state is not the same
	-- Expected:  @old_state_enabled_status = NULL if old and current states are same
	--            @old_state_enabled_status = 0  if old state was disabled and current state is enabled
	--            @old_state_enabled_status = 1  if old state was enabled and current state is disabled
	SELECT @old_state_enabled_status = oldstatus.data_collector_old_status 
	FROM [dbo].[syscollector_config_store_internal] cs,
	#data_collector_status oldstatus
	WHERE cs.parameter_name = 'CollectorEnabled'
	AND oldstatus.data_collector_old_status <> CONVERT(int, cs.parameter_value)

	-- @old_state_enabled_status is not null only if old and current states are not the same 
	IF(@old_state_enabled_status IS NOT NULL)
	BEGIN
		-- If pre-upgrade state was disabled and current state is enabled, Restore to disabled state
		IF(@old_state_enabled_status = 0)
		BEGIN
			PRINT 'post_dc100::Data collector was disabled before upgrade, Restoring Disabled state...'
			EXEC sp_syscollector_disable_collector
		END
		-- If pre-upgrade state was enabled and current state is disables, enabled data collector
		ELSE IF(@old_state_enabled_status = 1)
		BEGIN
			PRINT 'post_dc100::Data collector was enabled before upgrade, Restoring Enabled State ...'
			EXEC sp_syscollector_enable_collector
		END
		ELSE
		-- If pre-upgrade state was not 1 or 0, print message ( we should not get into this issue )
		BEGIN
			PRINT 'post_dc100::Invalid enable/disable state:' + convert(varchar, @old_state_enabled_status)
		END
	END
	ELSE
	BEGIN
		PRINT 'post_dc100::Current data collector state is same as pre-upgrade state'
	END
END

EXECUTE #printdatacollectorstatus

-- Now restore the Agent XPs to their old state
EXECUTE #sp_restore_component_state 'Agent XPs', @advopt_old_value, @comp_old_value

GO
/**************************************************************/
/* End of Data Collector Post-upgrade steps                   */
/**************************************************************/

DROP PROCEDURE #printdatacollectorstatus
DROP PROCEDURE #sp_enable_component
DROP PROCEDURE #sp_restore_component_state
GO

-------------------------------------------------------------------------
--
/**************************************************************/
/* Utility Post-upgrade steps                                 */
/**************************************************************/


-----------------------------------------------
-- SQL 2008 R2 CTP2 --> CTP3 or later
-----------------------------------------------

-- Drop the CTP2 MI job "sysutility_performance_information_collection" if it exists.  
-- This job was renamed in CTP3. 
IF EXISTS (SELECT * FROM msdb.dbo.sysjobs WHERE name = 'sysutility_performance_information_collection')
BEGIN
   EXEC msdb.dbo.sp_delete_job @job_name = 'sysutility_performance_information_collection';
END;
GO

/**************************************************************/
/* End of Utility Post-upgrade steps                          */
/**************************************************************/


/**************************************************************/
/* MSDB Post-upgrade steps                                    */
/**************************************************************/



-- Restoring implicit transactions state
DECLARE @is_implicit_transactions_set BIT

SELECT @is_implicit_transactions_set = CONVERT(BIT, value)
FROM fn_listextendedproperty(N'IMPLICIT_TRANSACTIONS', default, default, default, default, default, default);

IF (@is_implicit_transactions_set IS NOT NULL)
BEGIN
	EXEC sp_dropextendedproperty N'IMPLICIT_TRANSACTIONS'

  IF @is_implicit_transactions_set = 1
  BEGIN
    SET IMPLICIT_TRANSACTIONS ON
    PRINT 'Restored implicit transactions state to ON'
  END
  ELSE
  BEGIN
    SET IMPLICIT_TRANSACTIONS OFF
    PRINT 'Restored implicit transactions state to OFF'
  END
END 


GO
Print 'Upgrading Database Mail related objects...'

/*
	One of the main functions of this script is to handle migration
	of multiple Mail Host database to a single Mail Host Database in MSDB.
	Pre Beta 3 (CPT 15) versions of SQL Server 2005 allowed 
	Databae Mail object to exist in any database. This script will migrate 
        previously created profile, SentItems and log information to MSDB. 
	Finally the script will remove Database Mail objects in these other databases.

	Another goal of this script is to handle all metadata changes between different
	releases of the product starting with B-2 through subsequent CTPs
*/


USE msdb

SET NOCOUNT ON

-- remove obsolete configuration parameter
IF EXISTS(SELECT * FROM msdb.dbo.sysmail_configuration WHERE paramname = N'MaxNumberOfMailsPerDay')
  DELETE FROM msdb.dbo.sysmail_configuration WHERE paramname=N'MaxNumberOfMailsPerDay'

/*
 	Upgrade objects in MSDB if it was previously used as mailhost database
*/
BEGIN TRY
	IF EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='profile_name' and id =
			(SELECT OBJECT_ID(N'dbo.sysmail_mailitems', 'U')))
	BEGIN
		  -- convert data from profile_name column to profile_id column
		  exec sp_executesql N'
		  DECLARE @profile_name sysname
		  DECLARE @profile_id int 

		  DECLARE profile_name_cursor CURSOR LOCAL 
		  FOR
			 SELECT sp.profile_id, sp.name
			 FROM dbo.sysmail_profile sp, dbo.sysmail_mailitems mi
			 WHERE sp.name = mi.profile_name AND mi.profile_name IS NOT NULL

		  OPEN profile_name_cursor
		  FETCH NEXT FROM profile_name_cursor INTO @profile_id, @profile_name
		  WHILE (@@fetch_status = 0)
		  BEGIN
			 UPDATE dbo.sysmail_mailitems SET profile_id = @profile_id WHERE profile_name = @profile_name
			 FETCH NEXT FROM profile_name_cursor INTO @profile_id, @profile_name
		  END
		  Close profile_name_cursor
		  DEALLOCATE profile_name_cursor'

		  -- remove obsolete column
		  ALTER TABLE dbo.sysmail_mailitems DROP COLUMN profile_name
	END
END TRY
BEGIN CATCH
	print 'There was a problem upgrading MSDB mail host database.'
	print 'Unable to map existing profile name values to profile_id column'
	print 'Error State: ' + convert(varchar(10),ERROR_STATE() )
	print 'Error Message: ' + ERROR_MESSAGE()
	print 'Error Number: ' + convert(varchar(10),ERROR_NUMBER()) 
	print 'Error Severity: ' + convert(varchar(10),ERROR_SEVERITY())
END CATCH
GO

if EXISTS(Select * from msdb.dbo.sysmail_principalprofile)
BEGIN
	BEGIN TRY
		-- add existing mail users defined in MSDB to new role
		DECLARE @DBMailUser     sysname
		DECLARE principal_profile_cursor CURSOR LOCAL FOR
				SELECT dp.name FROM dbo.sysmail_principalprofile pp INNER JOIN sys.database_principals dp ON pp.principal_sid = dp.sid and dp.principal_id > 4

		OPEN principal_profile_cursor
		FETCH NEXT FROM principal_profile_cursor INTO @DBMailUser
		WHILE (@@fetch_status = 0)
		BEGIN
			EXEC msdb..sp_addrolemember @rolename = 'DatabaseMailUserRole',@membername = @DBMailUser
			FETCH NEXT FROM principal_profile_cursor INTO @DBMailUser
		END
		CLOSE principal_profile_cursor
		DEALLOCATE principal_profile_cursor
	END TRY
	BEGIN CATCH
		print 'There was a problem upgrading MSDB mail host database.'
		print 'Unable to add existing mail user to DatabaseMailUserRole'
		print 'Error State: ' + convert(varchar(10),ERROR_STATE() )
		print 'Error Message: ' + ERROR_MESSAGE()
		print 'Error Number: ' + convert(varchar(10),ERROR_NUMBER()) 
		print 'Error Severity: ' + convert(varchar(10),ERROR_SEVERITY())
	END CATCH
END
GO

if EXISTS(Select * from msdb.dbo.sysmail_principalprofile)
BEGIN
	BEGIN TRY
		-- cleaning up database mail related broker conversations
		if( SELECT is_broker_enabled from msdb.sys.databases WHERE name = 'msdb' ) = 0
		BEGIN
			PRINT 'NEED TO RE-ENABLE SSB'
			WHILE(1=1)
			BEGIN
				DECLARE @handle UNIQUEIDENTIFIER
				SET @handle = NULL
				SELECT TOP(1) @handle=conversation_handle FROM msdb.sys.conversation_endpoints
				WHERE (msdb.sys.conversation_endpoints.far_service IN 
							('SQL/Notifications/SysMailNotification/v1.0',
							'ExternalMailService',
							'InternalMailService',
							'SqlQueryNotificationService',
							'iMailRequestorService',
							'iMailResponderService',
							'http://schemas.microsoft.com/SQL/Notifications/EventNotificationService',
							'http://schemas.microsoft.com/SQL/Notifications/PostEventNotification'
							)
						)

				IF @handle IS NULL BREAK
				PRINT 'ENDING CONVERSATION ' + convert(varchar(256),@handle)
				END CONVERSATION @handle WITH CLEANUP
			END
		END

		-- We cannot turn the broker on, because we don't know whether it was disabled by the user
		-- ALTER DATABASE msdb SET ENABLE_BROKER
	END TRY
	BEGIN CATCH
		print 'There was a problem upgrading Mail Host databases.'
		print 'Unable to re-enable server broker. You might need to manually'
		print 'end any pending secure conversations.'
		print 'Error State: ' + convert(varchar(10),ERROR_STATE() )
		print 'Error Message: ' + ERROR_MESSAGE()
		print 'Error Number: ' + convert(varchar(10),ERROR_NUMBER()) 
		print 'Error Severity: ' + convert(varchar(10),ERROR_SEVERITY())
	END CATCH
END
GO

if EXISTS(SELECT * FROM msdb.dbo.sysmail_principalprofile) AND
   EXISTS(SELECT * FROM msdb.dbo.syscolumns WHERE name='database_id' and id =(SELECT OBJECT_ID(N'dbo.sysmail_principalprofile', 'U')))
BEGIN
	/*
 		Handle migration of multiple mail host databases into MSDB
	*/
	BEGIN TRY
		DECLARE @DBName		sysname
		DECLARE @DBNameQuote	sysname
		DECLARE @sql 		nvarchar(max)

		DECLARE @new_mailitem_id 	int
		DECLARE @old_mailitem_id 	int
		DECLARE @profile_name_exists 	bit
		DECLARE @Error			bit
		DECLARE @db_id 			int

		DECLARE  DBName_Cursor CURSOR FOR
			select name from sys.databases WHERE name NOT IN ('msdb', 'tempdb', 'master')
				and HAS_DBACCESS([name]) <> 0

		OPEN DBName_Cursor 

		FETCH NEXT FROM DBName_Cursor INTO @DBName

		WHILE @@FETCH_STATUS = 0
		BEGIN
			Print 'Checking database: ' + @DBName
			SET @DBNameQuote = QUOTENAME(@DBName)
			SELECT @db_id = db_id(@DBName)
			
			IF ( OBJECT_ID(@DBNameQuote + '.dbo.sysmail_log') IS NOT NULL) --Determine if Database Mail objects exist
			BEGIN
				Print @DBName + ' is a MailHost database.'
				
				--Going to migrate profiles from database to MSDB
						DECLARE @DBMailUser				sysname
				DECLARE @sid_MSDB				varbinary(85)
				DECLARE @sid_principal				varbinary(85)
				DECLARE @old_profile_id				int
				DECLARE @new_principal_id			int
				DECLARE @old_principal_id			int
				DECLARE @LoginName					sysname
				
				SET @sql = N'DECLARE  DBMail_Cursor CURSOR FOR
					SELECT p.name
						, pp.profile_id
						, msdb_p.sid
						, p.sid
						, pp.principal_id
					FROM msdb..sysmail_principalprofile pp 
						JOIN '+ @DBNameQuote +'.sys.database_principals p 
							ON pp.principal_id = p.principal_id
						LEFT JOIN msdb.sys.database_principals msdb_p 
							ON p.sid = msdb_p.sid
					where pp.database_id = ' + convert(nvarchar(10),@db_id)
				--print @sql
				EXEC(@sql)

				OPEN DBMail_Cursor

				FETCH NEXT FROM DBMail_Cursor INTO @DBMailUser, @old_profile_id, @sid_MSDB, @sid_principal, @old_principal_id

				WHILE @@FETCH_STATUS = 0
				BEGIN	
					
					Print 'Going to process user: ' + @DBMailUser
				
					IF (@sid_MSDB IS NULL) -- does not already exist in MSDB
					BEGIN
						IF (NOT EXISTS(Select * from sysusers where name = @DBMailUser))
						BEGIN
							IF (EXISTS(Select * from master.dbo.syslogins where sid = @sid_principal))
							BEGIN

								/* Determine the Login Name from the SID	*/
								SELECT @LoginName = name 
								FROM	master.dbo.syslogins
								WHERE	sid = @sid_principal
								
								PRINT 'Add USER to MSDB: ' + @DBMailUser

								SET @sql = N'CREATE USER ' + QUOTENAME(@DBMailUser) + ' FOR LOGIN ' + QUOTENAME(@LoginName)
								EXEC (@sql)
			
								IF (@old_principal_id > 4) -- do not add special accounts such as dbo, public, sys
								BEGIN
									Print 'Grant USER permission to send mail.'
									exec msdb..sp_addrolemember @rolename = 'DatabaseMailUserRole',@membername = @DBMailUser
								END
							END
							ELSE
							BEGIN
								PRINT 'Can not add user as the login does not exist.'
							END
							
						END
						ELSE
						BEGIN
							Print 'User has already been added to MSDB: ' + @DBMailUser
						
							IF (@old_principal_id > 4) -- do not add special accounts such as dbo, public, sys
							BEGIN
								PRINT 'Ensure user has the right to send mail.'
							
								exec msdb..sp_addrolemember @rolename = 'DatabaseMailUserRole',@membername = @DBMailUser

							END

						END


					END
					ELSE
					BEGIN
						Print 'Login already mapped to a user in MSDB'
					END

					IF (EXISTS(Select * from master.dbo.syslogins where sid = @sid_principal))
					BEGIN
						--Get principle_id
						SELECT @new_principal_id = principal_id 
						FROM msdb.sys.database_principals
						Where sid = @sid_principal

						IF (@new_principal_id is not Null)
						BEGIN
							print 'New Principal_id: ' + Convert(varchar(10),@new_principal_id) + '  Old profile_id: ' + convert(varchar(10),@old_profile_id) + '  Old principal id: ' + convert(varchar(10),@old_principal_id)

							SET @sql = N'
							IF NOT EXISTS(select * from msdb..sysmail_principalprofile
											Where profile_id = ' + Convert(varchar(10),@old_profile_id) + '
												AND database_id = 4
												AND principal_id = ' + Convert(varchar(10),@new_principal_id) + ')
							BEGIN
								--Update the Profile
								UPDATE msdb..sysmail_principalprofile
								SET database_id = 4
									, principal_id = ' + Convert(varchar(10),@new_principal_id) + '
								Where profile_id = ' + Convert(varchar(10),@old_profile_id) + '
									AND database_id = ' + Convert(varchar(10),@db_id) + '
									AND principal_id = ' + Convert(varchar(10),@old_principal_id) + '

								IF (@@rowcount = 1)
								BEGIN
									Print ''sysmail_principalprofile updated based on moving user to MSDB.''
								END
							END
							ELSE
							BEGIN
								Print ''This user already has already been configured with this profile in MSDB.''
							END'
							EXEC(@sql)
						END
						ELSE
						BEGIN
							Print 'sysmail_principalprofile table can not be updated for sid: ' + convert(varchar(100),@sid_principal)
						END
					END

					FETCH NEXT FROM DBMail_Cursor INTO @DBMailUser, @old_profile_id, @sid_MSDB, @sid_principal, @old_principal_id
					
				END

				CLOSE DBMail_Cursor
				DEALLOCATE DBMail_Cursor
						
				--/*	Move Data from user Mail Host database to MSDB */
				--Print 'Move Data from user Mail Host database to MSDB.'

				SET @sql = N'DECLARE  mailitem_id_Cursor CURSOR FOR
					SELECT mailitem_id FROM ' + @DBNameQuote + '.dbo.sysmail_mailitems'
				
				EXEC(@sql)
				
				OPEN mailitem_id_Cursor
				
				Set @Error = 0	--Assume no errors

				BEGIN TRANSACTION

				/* Disable Trigger so the "last_mod_date" and "last_mod_user colums" are not updated during transfer. */
				EXEC ('DISABLE TRIGGER [dbo].[trig_sysmail_attachments] ON [dbo].[sysmail_attachments]')
				EXEC ('DISABLE TRIGGER [dbo].[trig_sysmail_log] ON [dbo].[sysmail_log]')
				EXEC ('DISABLE TRIGGER [dbo].[trig_sysmail_mailitems]ON [dbo].[sysmail_mailitems]')

				Print 'Going to move ALL sysmail_log items not associated with a mailitem'

				SET @sql = N'INSERT INTO msdb.dbo.sysmail_log
					(event_type, log_date, description, process_id, mailitem_id, account_id, last_mod_date, last_mod_user)
					SELECT event_type, log_date, description, process_id, NULL, account_id, last_mod_date, last_mod_user
					FROM '+	@DBNameQuote +'.dbo.sysmail_log
					WHERE mailitem_id IS NULL '
				
				exec(@sql)

				FETCH NEXT FROM mailitem_id_Cursor INTO @old_mailitem_id
				WHILE @@FETCH_STATUS = 0
				BEGIN
					/****************************************/
					/*	MOVE dbo.sysmail_mailitems DATA	*/
					/****************************************/

					/* Need to check the schema defination of the table	*/
					SET @sql = N'USE ' + @DBNameQuote + '
							DECLARE @sql nvarchar(max) 
							IF (EXISTS(select * from syscolumns
							where id = object_id(''[dbo].[sysmail_mailitems]'')
								AND name = ''profile_name''))
							BEGIN						
							SET @sql = N''INSERT INTO msdb.dbo.sysmail_mailitems
							(profile_id, recipients, copy_recipients, blind_copy_recipients, subject, body, body_format, importance, sensitivity, file_attachments, attachment_encoding, query, execute_query_database, attach_query_result_as_file, query_result_header, query_result_width, query_result_separator, exclude_query_output, append_query_error, send_request_date, send_request_user, sent_account_id, sent_status, sent_date, last_mod_date, last_mod_user)
							SELECT CASE WHEN p.profile_id IS NULL THEN -1 ELSE p.profile_id END, recipients, copy_recipients, blind_copy_recipients, subject, body, body_format, importance, sensitivity, file_attachments, attachment_encoding, query, execute_query_database, attach_query_result_as_file, query_result_header, query_result_width, query_result_separator, exclude_query_output, append_query_error, send_request_date, send_request_user, sent_account_id, sent_status, sent_date, last_mod_date, mi.last_mod_user
							FROM '+	@DBNameQuote +'.dbo.sysmail_mailitems mi LEFT JOIN msdb..sysmail_profile p
								ON mi.profile_name = p.name
							WHERE mailitem_id = ' + CONVERT(VARCHAR(20),@old_mailitem_id) + ''' 
							END
							ELSE
							BEGIN
							SET @sql = N''INSERT INTO msdb.dbo.sysmail_mailitems
							(profile_id, recipients, copy_recipients, blind_copy_recipients, subject, body, body_format, importance, sensitivity, file_attachments, attachment_encoding, query, execute_query_database, attach_query_result_as_file, query_result_header, query_result_width, query_result_separator, exclude_query_output, append_query_error, send_request_date, send_request_user, sent_account_id, sent_status, sent_date, last_mod_date, last_mod_user)
							SELECT profile_id, recipients, copy_recipients, blind_copy_recipients, subject, body, body_format, importance, sensitivity, file_attachments, attachment_encoding, query, execute_query_database, attach_query_result_as_file, query_result_header, query_result_width, query_result_separator, exclude_query_output, append_query_error, send_request_date, send_request_user, sent_account_id, sent_status, sent_date, last_mod_date, last_mod_user
							FROM '+	@DBNameQuote +'.dbo.sysmail_mailitems 
							WHERE mailitem_id = ' + CONVERT(VARCHAR(20),@old_mailitem_id) + ''' 
							END

							EXEC(@sql)'

					
					EXEC(@sql)

					IF (@@error <> 0)	--Check for error
					BEGIN
						Set @Error = 1
					END
				
					SELECT @new_mailitem_id = @@identity

					/****************************************/
					/*	MOVE dbo.sysmail_log DATA	*/
					/****************************************/
					SET @sql = N'INSERT INTO msdb.dbo.sysmail_log
							(event_type, log_date, description, process_id, mailitem_id, account_id, last_mod_date, last_mod_user)
							SELECT event_type, log_date, description, process_id, ' + convert(varchar(5),@new_mailitem_id) + ', account_id, last_mod_date, last_mod_user
							FROM '+	@DBNameQuote +'.dbo.sysmail_log
							WHERE mailitem_id = ' + CONVERT(VARCHAR(20),@old_mailitem_id)
				
					EXEC(@sql)

					IF @@error <> 0 	--Check for error
					BEGIN
						Set @Error = 1
					END

					/****************************************/
					/*	MOVE dbo.sysmail_attachments DATA	*/ 
					/****************************************/
				SET @sql = N'USE ' + @DBNameQuote + ' 
								IF (object_id(''dbo.sysmail_attachments'') IS NOT NULL)
								begin
									INSERT INTO msdb.dbo.sysmail_attachments 
									(mailitem_id, filename, filesize, attachment, last_mod_date, last_mod_user)
									SELECT ' + convert(varchar(5),@new_mailitem_id) + ', filename, filesize, attachment, last_mod_date, last_mod_user
									FROM '+	@DBNameQuote +'.dbo.sysmail_attachments 
									WHERE mailitem_id = ' + CONVERT(VARCHAR(20),@old_mailitem_id)  + '
								end'
					EXEC(@sql)

					IF @@error <> 0 	--Check for error
					BEGIN
						Set @Error = 1
					END


					FETCH NEXT FROM mailitem_id_Cursor INTO @old_mailitem_id
				
				END
			
				IF @Error = 0
				BEGIN
					Print 'Completed data transfer to MSDB.'
					COMMIT TRANSACTION
				END
				ELSE
				BEGIN
					Print 'Not able to complete data transfer to MSDB.'
					ROLLBACK TRANSACTION

				END
			
				/* ENABLE Triggers as they were previously DISABLE	*/
				EXEC ('ENABLE TRIGGER [dbo].[trig_sysmail_attachments] ON [dbo].[sysmail_attachments]')
				EXEC ('ENABLE TRIGGER [dbo].[trig_sysmail_log] ON [dbo].[sysmail_log]')
				EXEC ('ENABLE TRIGGER [dbo].[trig_sysmail_mailitems]ON [dbo].[sysmail_mailitems]')

				CLOSE mailitem_id_Cursor
				DEALLOCATE mailitem_id_Cursor
				
				IF @Error = 0
				BEGIN
					/**********************************************************************/
					/*                                                                    */
					/* Uninstalls the tables, triggers and stored procedures necessary for*/
					/* sqlimail operations                                                */
					/*                                                                    */
					/*
					** Copyright (c) Microsoft Corporation
					** All Rights Reserved.
					*/
					/**********************************************************************/
					
					
					
					/**************************************************************/
					--		Drop ALL Database Mail objects (i.e Functions/Procedures )
					/**************************************************************/ 
					
					PRINT ''
					PRINT 'Dropping old Database Mail FUNCTIONS and PROCEDURES ...'
					PRINT ''
					
					-----
					PRINT 'Dropping function ConvertToInt'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.ConvertToInt'', ''FN'') IS NULL
								DROP FUNCTION ConvertToInt')
					
					-----
					PRINT 'Dropping procedure sysmail_start_sp'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_start_sp'', ''P'') IS NULL
						DROP PROCEDURE dbo.sysmail_start_sp')
					
					-----
					PRINT 'Dropping procedure sysmail_stop_sp'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_stop_sp'', ''P'') IS NULL
						DROP PROCEDURE dbo.sysmail_stop_sp')
					
					-----
					PRINT 'Dropping procedure sysmail_logmailevent_sp'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_logmailevent_sp'', ''P'') IS NULL
						DROP PROCEDURE dbo.sysmail_logmailevent_sp')
					
					-----
					PRINT 'Dropping procedure sp_has_changedbuser_permission'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_has_changedbuser_permission'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_has_changedbuser_permission')
					
					-----
					PRINT 'Dropping procedure sp_getprofilerequestxml'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_getprofilerequestxml'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_getprofilerequestxml')
					
					-----
					PRINT 'Dropping procedure sp_sendandreturn'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_sendandreturn'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_sendandreturn')
					
					-----
					PRINT 'Dropping procedure sp_sendandblock'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_sendandblock'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_sendandblock')
					
					-----
					PRINT 'Dropping procedure sp_isprohibited'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_isprohibited'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_isprohibited')
					
					-----
					PRINT 'Dropping procedure sp_sendimailqueues'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_sendimailqueues'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_sendimailqueues')
					
					-----
					PRINT 'Dropping procedure sp_gettestprofilexml'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_gettestprofilexml'', ''P'') IS NULL
					DROP PROCEDURE dbo.sp_gettestprofilexml')
					
					-----
					PRINT 'Dropping procedure sp_testprofileimailqueues'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_testprofileimailqueues'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_testprofileimailqueues')
					
					-----
					PRINT 'Dropping procedure sp_endconversation'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_endconversation'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_endconversation')
					
					-----
					PRINT 'Dropping procedure sp_endconversation'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_endconversation'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_endconversation')
					
					-----
					PRINT 'Dropping procedure sp_readrequest'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_readrequest'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_readrequest')
					
					-----
					PRINT 'Dropping procedure sp_sendresponse'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_sendresponse'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_sendresponse')
					
					-----
					PRINT 'Dropping procedure sp_sendtestprofileresponse'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_sendtestprofileresponse'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_sendtestprofileresponse')
					
					-----
					PRINT 'Dropping procedure sp_getsendmailxml'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_getsendmailxml'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_getsendmailxml')
					
					-----
					PRINT 'Dropping procedure sendimail_sp'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sendimail_sp'', ''P'') IS NULL
						DROP PROCEDURE dbo.sendimail_sp')
					
					-----
					PRINT 'Dropping procedure sp_testimailprofile'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_testimailprofile'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_testimailprofile')
						
					-----
					PRINT 'Dropping procedure dbo.sp_add_quota_information'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_add_quota_information'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_add_quota_information')
			
					-----
					PRINT 'Dropping procedure dbo.sp_current_principal_mails'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_current_principal_mails'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_current_principal_mails')
			
					-----
					PRINT 'Dropping procedure dbo.sp_delete_quota_information'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_delete_quota_information'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_delete_quota_information')

			
					-----
					PRINT 'Dropping procedure  dbo.sp_ExernalMailQueueListener'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_ExernalMailQueueListener'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_ExernalMailQueueListener')

			
					-----
					PRINT 'Dropping procedure dbo.sp_GetAttachmentData'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_GetAttachmentData'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_GetAttachmentData')

			
					-----
					PRINT 'Dropping procedure dbo.sp_RunMailQuery'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_RunMailQuery'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_RunMailQuery')

			
					-----
					PRINT 'Dropping procedure dbo.sp_SendMailMessage'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_SendMailMessage'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_SendMailMessage')

			
					-----
					PRINT 'Dropping procedure dbo.sp_SendMailQueues'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_SendMailQueues'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_SendMailQueues')
			
					-----
					PRINT 'Dropping procedure dbo.sp_verify_quota_mail_count'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_verify_quota_mail_count'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_verify_quota_mail_count')

					-----
					PRINT 'Dropping procedure dbo.sp_activate_sqlimail'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sp_activate_sqlimail'', ''P'') IS NULL
						DROP PROCEDURE dbo.sp_activate_sqlimail')



			
					
					/**************************************************************/
					--    Drop all Database Mail TABLES 
					/**************************************************************/ 
					
					PRINT ''
					PRINT 'Dropping TABLES...'
					PRINT ''
					
					-----
					PRINT 'Dropping table sysmail_log'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_log'', ''U'') IS NULL
						DROP TABLE sysmail_log')
					
					----- 
					PRINT 'Dropping table sysmail_send_retries'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_send_retries'', ''U'') IS NULL
						DROP TABLE dbo.sysmail_send_retries')
					
					-----
					PRINT 'Dropping table sqlimail_data_transfer'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sqlimail_data_transfer'', ''U'') IS NULL
						DROP TABLE sqlimail_data_transfer')
					
					-----
					PRINT 'Dropping table sysmail_attachments'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_attachments'', ''U'') IS NULL
						DROP TABLE sysmail_attachments')
			
					-----
					PRINT 'Dropping table sysmail_query_transfer'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_query_transfer'', ''U'') IS NULL
						DROP TABLE sysmail_query_transfer')
			
					-----
					PRINT 'Dropping table sysmail_attachments_transfer'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_attachments_transfer'', ''U'') IS NULL
						DROP TABLE sysmail_attachments_transfer')
			
					-----
					PRINT 'Dropping table sysmail_quota_information'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_quota_information'', ''U'') IS NULL
						DROP TABLE sysmail_quota_information')
			
					-----
					PRINT 'Dropping table sysmail_mailitems'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF NOT OBJECT_ID(''dbo.sysmail_mailitems'', ''U'') IS NULL
						DROP TABLE sysmail_mailitems')
			
					
					/**************************************************************/
					--		Drop MESSAGES, CONTRACTS, QUEUES AND SERVICES 
					/**************************************************************/
					
					PRINT ''
					PRINT 'Dropping MESSAGES, CONTRACTS, QUEUES AND SERVICES...'
					PRINT ''
					
					-----
					PRINT 'Dropping service iMailRequestorService'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.services WHERE name =''iMailRequestorService'')
						DROP SERVICE iMailRequestorService')
					
					-----
					PRINT 'Dropping service iMailResponderService'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.services WHERE name =''iMailResponderService'')
						DROP SERVICE iMailResponderService')
					
					-----
					PRINT 'Dropping queue iMailRequestor'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.objects WHERE name = ''iMailRequestor'' AND type = ''SQ'')
						DROP QUEUE iMailRequestor')
					
					-----
					PRINT 'Dropping queue iMailResponder'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.objects WHERE name = ''iMailResponder'' AND type = ''SQ'')
						DROP QUEUE iMailResponder')
					
					-----
					PRINT 'Dropping service [SQL/Notifications/IMailNotification/v1.0]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.services WHERE name =''SQL/Notifications/IMailNotification/v1.0'')
						DROP SERVICE [SQL/Notifications/IMailNotification/v1.0]')
					   
					-----
					PRINT 'Dropping service [ExternalMailService]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.services WHERE name =''ExternalMailService'')
						DROP SERVICE [ExternalMailService]')

					-----
					PRINT 'Dropping service [InternalMailService]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.services WHERE name =''InternalMailService'')
						DROP SERVICE [InternalMailService]')

					-----
					PRINT 'Dropping queue iMailNotificationQueue'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS (SELECT * FROM sys.objects WHERE name = ''iMailNotificationQueue'' AND type = ''SQ'')
						DROP QUEUE iMailNotificationQueue')
					   
					-----
					PRINT 'Dropping contract [//www.microsoft.com/imail/contracts/SendMail/v1.0]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS(SELECT * FROM sys.service_contracts 
							WHERE name = ''//www.microsoft.com/imail/contracts/SendMail/v1.0'')          
						DROP CONTRACT [//www.microsoft.com/imail/contracts/SendMail/v1.0]')
					
					-----
					PRINT 'Dropping message type [{//www.microsoft.com/imail/messages}SendMail]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS(SELECT * FROM sys.service_message_types 
							WHERE name = ''{//www.microsoft.com/imail/messages}SendMail'')        
					DROP MESSAGE TYPE [{//www.microsoft.com/imail/messages}SendMail]')
					  
					-----
					PRINT 'Dropping contract [//www.microsoft.com/imail/contracts/TestProfile/v1.0]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS(SELECT * FROM sys.service_contracts 
							WHERE name = ''//www.microsoft.com/imail/contracts/TestProfile/v1.0'')         
						DROP CONTRACT [//www.microsoft.com/imail/contracts/TestProfile/v1.0]')
					
					-----
					PRINT 'Dropping message type [{//www.microsoft.com/imail/messages}TestProfile]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS(SELECT * FROM sys.service_message_types 
							WHERE name = ''{//www.microsoft.com/imail/messages}TestProfile'')        
						DROP MESSAGE TYPE [{//www.microsoft.com/imail/messages}TestProfile]')
					
					-----
					PRINT 'Dropping message type [{//www.microsoft.com/imail/messages}TestProfileResponse]'
					-----
					EXEC('USE ' + @DBNameQuote + ' IF EXISTS(SELECT * FROM sys.service_message_types 
							WHERE name = ''{//www.microsoft.com/imail/messages}TestProfileResponse'')
						DROP MESSAGE TYPE [{//www.microsoft.com/imail/messages}TestProfileResponse]')
				
					-----
					PRINT 'Dropping certificates and related users'
					-----
					DECLARE @sqlcmd     nvarchar(max), 
						@CertName       sysname,
						@CertNameQuoted sysname,
						@MailLogin      sysname,
						@MailLoginQuoted sysname    
					
					SELECT @CertName       = N'SQLiMail-Certificate-' + @DBName,
						@CertNameQuoted = QUOTENAME( @CertName,''''),
						@MailLogin      = N'SQLiMail-' + @DBName + '-Certificate-Login',
						@MailLoginQuoted= QUOTENAME( @MailLogin,'''')
					
					-----
					PRINT 'Dropping user in msdb'
					-----
					SET @sqlcmd = 
					N'USE msdb
					IF(EXISTS (SELECT * FROM sys.database_principals WHERE name = N' + @MailLoginQuoted + '))
						DROP USER ' + QUOTENAME( @MailLogin)
					EXEC sp_executesql @sqlcmd
					
					-----
					PRINT 'Dropping user and certificate login in master'
					-----
					SET @sqlcmd = 
					N'USE master 
						IF(EXISTS (SELECT * FROM sys.database_principals WHERE name = N' + @MailLoginQuoted + '))
						DROP USER ' + QUOTENAME( @MailLogin) + '
						IF(EXISTS(select * from sys.server_principals where name = N' + @MailLoginQuoted + '))
						DROP LOGIN ' + QUOTENAME( @MailLogin)
					EXEC sp_executesql @sqlcmd
					
					-----
					PRINT 'Dropping user in this database'
					-----
					SET @sqlcmd = 
					N'USE ' + @DBNameQuote + '
					IF(EXISTS (SELECT * FROM sys.database_principals WHERE name = N' + @MailLoginQuoted + '))
						DROP USER ' + QUOTENAME( @MailLogin)

					EXEC sp_executesql @sqlcmd			
					
					-----
					PRINT 'Dropping the certificate in this db'
					-----
					SET @sqlcmd = 
					N'USE ' + @DBNameQuote + '	
					IF(EXISTS(SELECT * FROM sys.certificates WHERE name = N' + @CertNameQuoted + '))
					BEGIN
						DROP CERTIFICATE ' + QUOTENAME(@CertName) + ' 
					END'

					EXEC sp_executesql @sqlcmd			
					
					-----
					PRINT 'Dropping certificate from master'
					-----
					SET @sqlcmd = 
					N'USE master
					IF(EXISTS(SELECT * FROM sys.certificates WHERE name = N' + @CertNameQuoted + '))
						DROP CERTIFICATE ' + QUOTENAME(@CertName)

					EXEC sp_executesql @sqlcmd
				
				END
			END

			FETCH NEXT FROM DBName_Cursor INTO @DBName

		END	-- Loop through all databases.

		CLOSE DBName_Cursor
		DEALLOCATE DBName_Cursor

    	-- deleting all principal associations that are not in MSDB or public
	    exec sp_executesql N'
	    delete from msdb.dbo.sysmail_principalprofile
	    where database_id <> 4 and database_id <> 0'
	END TRY
	BEGIN CATCH
		print 'There was a problem upgrading Mail Host databases.'
		print 'Error State: ' + convert(varchar(10),ERROR_STATE() )
		print 'Error Message: ' + ERROR_MESSAGE()
		print 'Error Number: ' + convert(varchar(10),ERROR_NUMBER()) 
		print 'Error Severity: ' + convert(varchar(10),ERROR_SEVERITY())
	END CATCH
END

BEGIN TRY
	IF EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='principal_id' and id =
			(SELECT OBJECT_ID(N'dbo.sysmail_principalprofile', 'U')))
	BEGIN
		-- convert data from principal_id to principal_sid
		exec sp_executesql N'
		DECLARE @principal_sid varbinary(85)
		DECLARE @principal_id int 

		DECLARE principal_sid_cursor CURSOR LOCAL 
		FOR
		SELECT distinct principal_id
		FROM dbo.sysmail_principalprofile

		OPEN principal_sid_cursor 
		FETCH NEXT FROM principal_sid_cursor INTO @principal_id
		WHILE (@@fetch_status = 0)
		BEGIN
			IF @principal_id = 0
			   SET @principal_sid = 0x00
			ELSE
			   SELECT @principal_sid = dbo.get_principal_sid(@principal_id)

			IF @principal_sid IS NOT NULL -- principal_id is valid
			BEGIN
				UPDATE dbo.sysmail_principalprofile
				SET principal_sid = @principal_sid
				WHERE principal_id = @principal_id
			END
			ELSE
			BEGIN
				DELETE FROM dbo.sysmail_principalprofile
				WHERE principal_id = @principal_id
			END
			FETCH NEXT FROM principal_sid_cursor INTO @principal_id
		END
		CLOSE principal_sid_cursor 
		DEALLOCATE principal_sid_cursor'

		-- safety clean-up
		DELETE FROM dbo.sysmail_principalprofile WHERE principal_sid = 0xFFFF

		-- remove obsolete column
		ALTER TABLE dbo.sysmail_principalprofile DROP CONSTRAINT [SYSMAIL_PRINCIPALPROFILE_ProfilePrincipalMustBeUnique]
		ALTER TABLE dbo.sysmail_principalprofile DROP COLUMN principal_id
  		ALTER TABLE dbo.sysmail_principalprofile ADD CONSTRAINT [SYSMAIL_PRINCIPALPROFILE_ProfilePrincipalMustBeUnique] PRIMARY KEY CLUSTERED ([profile_id] ASC,[principal_sid] ASC)
	END
END TRY
BEGIN CATCH
	print 'There was a problem upgrading MSDB mail host database.'
	print 'Unable to map existing profile id values to profile_sid column'
	print 'Error State: ' + convert(varchar(10),ERROR_STATE() )
	print 'Error Message: ' + ERROR_MESSAGE()
	print 'Error Number: ' + convert(varchar(10),ERROR_NUMBER()) 
	print 'Error Severity: ' + convert(varchar(10),ERROR_SEVERITY())
END CATCH
GO

BEGIN TRY
    -- remove database_id column
    IF EXISTS (SELECT * FROM msdb.dbo.syscolumns WHERE name='database_id' and id =
       (SELECT OBJECT_ID(N'dbo.sysmail_principalprofile', 'U')))
    BEGIN
        ALTER TABLE dbo.sysmail_principalprofile DROP CONSTRAINT [SYSMAIL_PRINCIPALPROFILE_ProfilePrincipalMustBeUnique]
        ALTER TABLE dbo.sysmail_principalprofile DROP COLUMN database_id
        ALTER TABLE dbo.sysmail_principalprofile ADD CONSTRAINT [SYSMAIL_PRINCIPALPROFILE_ProfilePrincipalMustBeUnique] PRIMARY KEY CLUSTERED ([profile_id] ASC,[principal_sid] ASC)
    END
END TRY
BEGIN CATCH
	print 'There was a problem upgrading MSDB mail host database.'
	print 'Unable to create a primary key constraint on sysmail_principalprofile table'
	print 'Error State: ' + convert(varchar(10),ERROR_STATE() )
	print 'Error Message: ' + ERROR_MESSAGE()
	print 'Error Number: ' + convert(varchar(10),ERROR_NUMBER()) 
	print 'Error Severity: ' + convert(varchar(10),ERROR_SEVERITY())
END CATCH

Print 'Completed upgrade of Database Mail related objects...'
GO
/*------------------------------------------------------------------------------

upgrade_ucp_cmdw_discovery.sql

It uses sys.sp_dbscriptlevel to enable the other upgrade scripts to run.

** Copyright (c) Microsoft Corporation.  All rights reserved.

------------------------------------------------------------------------------*/


-- These must match the definitions used in sqlscriptupgrade.cpp
PRINT '------------------------------------------------------'
PRINT 'Starting execution of UPGRADE_UCP_CMDW_DISCOVERY.SQL'
PRINT '------------------------------------------------------'

GO


DECLARE @mdw_database_name SYSNAME
SELECT @mdw_database_name=CAST(current_value as SYSNAME) FROM [msdb].[dbo].[sysutility_ucp_configuration_internal] WHERE name = 'MdwDatabaseName'

DECLARE @run_script BIT
SET @run_script=1

-- If MdwDatabaseName does not exist then skip execution of instmdw.sql.
-- This can happen for example when the instance is not a Ucp.
IF (@mdw_database_name IS NULL OR @mdw_database_name = N'')
BEGIN
    SET @run_script=0
    RAISERROR ('The Utility MDW does not exist on this instance.', 0, 1) WITH NOWAIT;
END

-- If the @mdw_database_name does not exist skip the execution of instmdw.sql
IF (@run_script = 1) AND NOT EXISTS (SELECT * FROM master.dbo.sysdatabases WHERE name = @mdw_database_name)
BEGIN
    SET @run_script=0
    RAISERROR ('The Utility MDW does not exist on this instance.', 0, 1) WITH NOWAIT;
END

DECLARE @print_expr nvarchar(400)

-- If the @mdw_database_name is NOT online, log a message and skip the execution of instmdw.sql
DECLARE @state_online SYSNAME
SET @state_online = 'ONLINE'
SELECT @state_online = UPPER(@state_online collate SQL_Latin1_General_CP1_CS_AS)

IF (@run_script = 1) AND NOT EXISTS (SELECT state_desc FROM master.sys.databases where name = @mdw_database_name AND
                                     UPPER(state_desc collate SQL_Latin1_General_CP1_CS_AS) LIKE @state_online)
BEGIN    
    SET @print_expr = 'WARNING! The database ' + @mdw_database_name + ' is not ONLINE. So skipping execution of instmdw.sql on it. Please run the script manually after the upgrade.'
    RAISERROR (@print_expr, 0, 1) WITH NOWAIT;
    SET @run_script=0
END


DECLARE @EMPTY_SCRIPT_LEVEL INT
DECLARE @DENALI_SCRIPT_LEVEL INT
DECLARE @ID_UTILITY_CMDW_UPGRADE INT

SELECT @EMPTY_SCRIPT_LEVEL = 0
SELECT @DENALI_SCRIPT_LEVEL = 500
SELECT @ID_UTILITY_CMDW_UPGRADE = 15

-- Disable execution of upgrade_ucp_cmdw.sql by default
EXEC sys.sp_dbscriptlevel 'master', @ID_UTILITY_CMDW_UPGRADE, @DENALI_SCRIPT_LEVEL


-- Only if this is a UCP and sysutility_mdw exists and is ONLINE trigger execution of upgrade_ucp_cmdw.sql
IF (@run_script = 1)
BEGIN
    RAISERROR ('instmdw.sql will be executed on the Utility MDW database.', 0, 1) WITH NOWAIT;
    EXEC sys.sp_dbscriptlevel 'master', @ID_UTILITY_CMDW_UPGRADE, @EMPTY_SCRIPT_LEVEL
END
ELSE
BEGIN
    RAISERROR ('Skipping the execution of instmdw.sql.', 0, 1) WITH NOWAIT;
END


PRINT '------------------------------------------------------'
PRINT 'execution of UPGRADE_UCP_CMDW_DISCOVERY.SQL completed'
PRINT '------------------------------------------------------'

GO


/**********************************************************************/
/* PRE_UPGRADE_UCP_CMDW.SQL                                           */
/*                                                                    */
/*
** Copyright (c) Microsoft Corporation
** All Rights Reserved.
*/
/**********************************************************************/

PRINT '------------------------------------------------'
PRINT 'Starting execution of PRE_UPGRADE_UCP_CMDW.SQL'
PRINT '------------------------------------------------'
USE sysutility_mdw
Go


PRINT '------------------------------------------------'
PRINT 'execution of PRE_UPGRADE_UCP_CMDW.SQL completed'
PRINT '------------------------------------------------'

GO



/**********************************************************************/
/* INSTMDW.SQL                                                        */
/*                                                                    */
/* Installs the tables and stored procedures necessary for            */
/* supporting the Management Data Warehouse in Data Collector         */
/*                                                                    */
/* MDW database is created by Setup and this script is run in the     */
/* context of the created MDW database                                */
/*                                                                    */
/* To run this script manually on a MDW database:                     */
/* "sqlcmd -d MDW -i instmdw.sql"                                     */
/*                                                                    */
/*                                                                    */
/* Copyright (c) Microsoft Corporation                                */
/* All Rights Reserved.                                               */
/*                                                                    */
/**********************************************************************/


RAISERROR('', 0, 1)  WITH NOWAIT;
RAISERROR('----------------------------------', 0, 1)  WITH NOWAIT;
RAISERROR('Starting execution of INSTMDW.SQL ', 0, 1)  WITH NOWAIT;
RAISERROR('----------------------------------', 0, 1)  WITH NOWAIT;
GO

SET ANSI_NULLS ON
SET ANSI_NULL_DFLT_ON ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
SET ARITHABORT ON
SET CONCAT_NULL_YIELDS_NULL ON
SET NUMERIC_ROUNDABORT OFF
SET QUOTED_IDENTIFIER ON
GO

-- This version of instmdw should be executed only against 10.0 servers or higher
IF (@@microsoftversion / 0x01000000) < 10
BEGIN
    RAISERROR('Management Data Warehouse database can only be installed on an instance of SQL Server 2008 or higher.', 21, 127) WITH LOG 
END
GO

-- SQL Server Express Edition does not support a management data warehouse.
DECLARE @retval sql_variant
SELECT @retval = (SELECT SERVERPROPERTY('EngineEdition'))
IF (@retval = 4) -- 4: Express
BEGIN
    RAISERROR(14713, 20, -1) WITH LOG 
END
GO

-- Take the database to single mode during the installation
RAISERROR('', 0, 1)  WITH NOWAIT;
RAISERROR('Taking database to single user mode', 0, 1)  WITH NOWAIT;

DECLARE @dbname sysname
SELECT @dbname = QUOTENAME(DB_NAME())


-- Check if someone has turned async statistics autoupdate on
DECLARE @async_auto_stat bit
SELECT @async_auto_stat = is_auto_update_stats_async_on
FROM sys.databases
WHERE database_id = DB_ID()

CREATE TABLE #tmp_auto_mode (auto_mode bit)

IF (@async_auto_stat = 1)       -- if yes, turn it off while install
BEGIN
    RAISERROR('Disabling asynchronous auto statistics while database in single user mode ...', 0, 1)  WITH NOWAIT;
    DECLARE @sql_async_autostat_off nvarchar(256)
    SET @sql_async_autostat_off = 'ALTER DATABASE ' + @dbname + 
                                            ' SET AUTO_UPDATE_STATISTICS_ASYNC OFF'

    EXEC sp_executesql @sql_async_autostat_off

    -- check for all the currently running background statistics jobs
    -- and kill them
    DECLARE @stats_job_id nvarchar(10)
    DECLARE @sql_kill_stats_job nvarchar(256)

    DECLARE stats_jobs_id_cursor CURSOR READ_ONLY FOR
    SELECT CONVERT(nvarchar(10), job_id)
    FROM sys.dm_exec_background_job_queue
    WHERE database_id = DB_ID()

    OPEN stats_jobs_id_cursor
    FETCH NEXT FROM stats_jobs_id_cursor INTO @stats_job_id
    WHILE (@@FETCH_STATUS = 0)
    BEGIN
        SET @sql_kill_stats_job = 'KILL STATS JOB ' + @stats_job_id
        EXEC sp_executesql @sql_kill_stats_job
    END

    CLOSE stats_jobs_id_cursor
    DEALLOCATE stats_jobs_id_cursor

    -- save the fact that async_auto_stats was on
    INSERT INTO #tmp_auto_mode
    VALUES(1)
END

-- Now, put the database in single user mode
DECLARE @sql_query nvarchar(256)
SET @sql_query = 'ALTER DATABASE ' + @dbname +
                 '   SET SINGLE_USER WITH ROLLBACK IMMEDIATE'
EXEC sp_executesql @sql_query;

-- Allow the use of snapshot transaction isolation level 
SET @sql_query = 'ALTER DATABASE ' + @dbname + ' SET ALLOW_SNAPSHOT_ISOLATION ON';
EXEC sp_executesql @sql_query;

-- Turn on the read_committed_snapshot database option
SET @sql_query = 'ALTER DATABASE ' + @dbname + ' SET READ_COMMITTED_SNAPSHOT ON';
EXEC sp_executesql @sql_query;
GO


-- Set the right options
-- These are needed for the correct behavior of check constraint
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
SET ARITHABORT ON
SET CONCAT_NULL_YIELDS_NULL ON
SET NUMERIC_ROUNDABORT OFF
SET QUOTED_IDENTIFIER ON 
GO

/**********************************************************************/
/* Add an extended database property to identify database as a data   */
/* warehouse for the data collector.                                  */
/*                                                                    */
/* If a version already exists, check that we are not down grading    */
/* to a lower version.                                                */
/**********************************************************************/

--
-- This stored procedure compares two build numbers
-- It tokenizes the numbers by '.' and compares the portions from left to right
--
IF (OBJECT_ID(N'tempdb..#sp_compare_builds', 'P') IS NOT NULL)
BEGIN
    DROP PROCEDURE [tempdb]..[#sp_compare_builds]
END
GO 

CREATE PROCEDURE #sp_compare_builds
    @old            nvarchar(100),
    @new            nvarchar(100),
    @order          int OUTPUT      -- -1 if @old<@new, 0 if @old=@new, 1 if @old>@new
AS
BEGIN
    DECLARE @retVal int
    DECLARE @old_portion nvarchar(100)
    DECLARE @new_portion nvarchar(100)
    DECLARE @old_number int
    DECLARE @new_number int
    
    SET @old = NULLIF(@old+'.', '.')
    SET @new = NULLIF(@new+'.', '.')    
    SET @order = 0
    BEGIN TRY
        SET @retVal = 0
        WHILE ((@old IS NOT NULL) AND (@new IS NOT NULL))
        BEGIN
            --SELECT @old as old, @new as new
            DECLARE @old_token_mark int
            DECLARE @new_token_mark int
            SET @old_token_mark = CHARINDEX('.', @old)
            SET @new_token_mark = CHARINDEX('.', @new)
            
            -- get the first number in the version from the left
            SET @old_portion = LEFT(@old, @old_token_mark-1)
            SET @new_portion = LEFT(@new, @new_token_mark-1)
            
            -- trim the number from the left
            SET @old = NULLIF(SUBSTRING(@old, @old_token_mark+1, LEN(@old)-@old_token_mark), '')
            SET @new = NULLIF(SUBSTRING(@new, @new_token_mark+1, LEN(@new)-@new_token_mark), '')
            
            -- compare the portions you have
            SET @old_number = CONVERT(int, @old_portion)
            SET @new_number = CONVERT(int, @new_portion)
            
            IF (@old_number = @new_number)
            BEGIN
                CONTINUE
            END
            ELSE
            BEGIN -- We have a resolution, figure out who comes first and get out
                IF (@old_number < @new_number)
                BEGIN
                    SET @order = -1
                END
                ELSE
                BEGIN
                    SET @order = 1
                END
                
                BREAK
            END
        END
        
        RETURN (0)
    END TRY
    BEGIN CATCH
        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, @ErrorSeverity, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

        RETURN (1)
    END CATCH    
END
GO

DECLARE @prop_name sysname
DECLARE @new_value sql_variant
DECLARE @old_value sql_variant
SET @prop_name = 'Microsoft_DataCollector_MDW_Version'
SET @new_value = '15.0.2130.3'     -- This will be replaced at build time with the sql build number

SELECT @old_value = value
FROM fn_listextendedproperty(@prop_name, NULL, NULL, NULL, NULL, NULL, NULL)

IF (@old_value IS NOT NULL)
BEGIN
    DECLARE @order int
    DECLARE @old_value_char nvarchar(100)
    DECLARE @new_value_char nvarchar(100)
    SET @old_value_char = CONVERT(nvarchar(100), @old_value)
    SET @new_value_char = CONVERT(nvarchar(100), @new_value)

    -- check that we are not downgrading the database
    IF (@old_value_char <> '15.0.2130.3')   -- value only used during development
    BEGIN
        EXEC #sp_compare_builds @old_value_char, @new_value_char, @order OUTPUT
        
        IF (@order > 0) -- the old build version is older than the new, abort the script and kill the connection
        BEGIN
            -- Put the database back into multi user mode
            RAISERROR('', 0, 1)  WITH NOWAIT;
            RAISERROR('Restoring database to multi user mode before aborting the script', 0, 1)  WITH NOWAIT;
            DECLARE @dbname sysname
            SET @dbname = QUOTENAME(DB_NAME())

            DECLARE @sql_db_multi_mode nvarchar(256)
            SET @sql_db_multi_mode = 'ALTER DATABASE ' + @dbname +
                                                ' SET MULTI_USER WITH ROLLBACK IMMEDIATE'
            EXEC sp_executesql @sql_db_multi_mode

            RAISERROR(14714, 21, 1, @old_value_char, @new_value_char) WITH LOG
        END
    END
    EXEC sp_dropextendedproperty @name = @prop_name
END            

EXEC sp_addextendedproperty
        @name = @prop_name,
        @value = @new_value
GO

DROP PROCEDURE #sp_compare_builds


/*
Procedure [#create_or_alter_primary_key_or_index]

Helper proc to create/update primary key constraints and nonclustered/clustered indexes. 
Avoids the need to duplicate object definition in separate creation and upgrade sections. 
Prevents cluttering up the script with repetitive logic to repeat these tasks every time 
we need to define an index or a primary key: 
  - Check for an existing object and skip the create if appropriate
  - Compare the definition of the existing object (clustered vs. nonclustered, number and 
    order of index key columns, ignore_dup_key bit, included vs. key columns, asc/desc 
    sort direction) to the desired index or constraint definition
  - Drop the existing constraint/index if it is malformed or out-of-date 
  - Drop nonclustered indexes before dropping clustered indexes
  - Drop referencing foreign keys before dropping a primary key
 
Parameters: 
    @table_schema - e.g. "snapshots"
    @table_name - e.g. "query_stats"
    @object_type - either "PRIMARY KEY" or "INDEX"
    @constraint_or_index_name - the PK or index name
    @ignore_dup_key - 1 to enable the IGNORE_DUP_KEY index option (default 0)
    @clustered - 1 to create a clustered index/PK (default 1)

The columns in the table are passed to this proc via this temp table: 
    CREATE TABLE #index_key_columns (
        key_ordinal         int IDENTITY, 
        constraint_name     sysname COLLATE CATALOG_DEFAULT, 
        column_name         sysname COLLATE CATALOG_DEFAULT, 
        is_included_column  bit, 
        is_descending_key   bit
    );

Example usage: 
    TRUNCATE TABLE #index_key_columns;
    INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
    VALUES 
        ('IDX_performance_counter_instances1', 'object_name', 0, 0), 
        ('IDX_performance_counter_instances1', 'counter_name', 0, 0), 
        ('IDX_performance_counter_instances1', 'instance_name', 1, 0);
    GO

    EXEC #create_or_alter_primary_key_or_index
        @table_schema = 'snapshots', @table_name = 'performance_counter_instances', 
        @object_type = 'INDEX', @constraint_or_index_name = 'IDX_performance_counter_instances1', 
        @ignore_dup_key = 0, @clustered = 0;
    GO

The TRUNCATE is typically not necessary, but is a good practice just in case two 
indexes have the same name. 
*/
IF OBJECT_ID ('tempdb..#create_or_alter_primary_key_or_index') IS NOT NULL 
BEGIN
    DROP PROC #create_or_alter_primary_key_or_index 
END;
GO
CREATE PROC #create_or_alter_primary_key_or_index 
    @table_schema sysname, 
    @table_name sysname, 
    @object_type sysname, 
    @constraint_or_index_name sysname, 
    @ignore_dup_key bit = 0, 
    @clustered bit = 1
AS
BEGIN
    RAISERROR (N'Validating %s [%s] on table [%s].[%s]...', 0, 1, 
        @object_type, @constraint_or_index_name, @table_schema, @table_name) WITH NOWAIT;

    -- Get the list of key columns and included columns that are supposed to be in 
    -- this index/PK constraint
    DECLARE @column_name sysname;
    DECLARE @is_included_column bit;
    DECLARE @is_descending_key bit;
    DECLARE @key_column_list nvarchar(max);
    DECLARE @include_column_list nvarchar(max);
    DECLARE @sql nvarchar(max);
    DECLARE @expected_column_count int;
    SET @key_column_list = '';
    SET @include_column_list = '';
    SET @expected_column_count = 0;
    DECLARE cols INSENSITIVE CURSOR FOR 
    SELECT column_name, is_included_column, is_descending_key
    FROM #index_key_columns
    WHERE constraint_name = @constraint_or_index_name
    ORDER BY key_ordinal ASC;
    
    OPEN cols;
    FETCH NEXT FROM cols INTO @column_name, @is_included_column, @is_descending_key;

    WHILE (@@FETCH_STATUS = 0)
    BEGIN
        IF (@is_included_column = 1)
        BEGIN
            SET @include_column_list = @include_column_list 
                + CASE WHEN LEN (@include_column_list) > 0 THEN ', ' ELSE '' END 
                + QUOTENAME (@column_name)
            IF (@object_type = 'PRIMARY KEY')
            BEGIN
                RAISERROR ('Error in script -- INCLUDEd columns are not allowed in a primary key', 21, 1);
            END
        END
        ELSE BEGIN
            SET @key_column_list = @key_column_list 
                + CASE WHEN LEN (@key_column_list) > 0 THEN ', ' ELSE '' END 
                + QUOTENAME (@column_name)
                + CASE @is_descending_key WHEN 1 THEN ' DESC' ELSE ' ASC' END;
        END;
        SET @expected_column_count = @expected_column_count + 1;
        
        FETCH NEXT FROM cols INTO @column_name, @is_included_column, @is_descending_key;
    END;
    CLOSE cols;
    DEALLOCATE cols;

    -- Compare the object definition to the expected definition
    DECLARE @unexpected_column_count int;
    DECLARE @total_column_count int;
    DECLARE @total_included_count int;
    DECLARE @out_of_order_key_count int;
    DECLARE @wrong_sort_direction_count int;
    DECLARE @wrong_included_column_count int;
    DECLARE @key_ordinal_base int;
    DECLARE @existing_constraint_or_index_name  sysname;
    DECLARE @current_ignore_dup_key bit;
    DECLARE @current_clustered bit;
    DECLARE @table_object_id int;

    -- Get the target table's object id
    SET @table_object_id = OBJECT_ID (QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name));

    -- Get the indentity value of the first column in this constraint
    SELECT @key_ordinal_base = MIN (key_ordinal) - 1
    FROM #index_key_columns
    WHERE constraint_name = @constraint_or_index_name;

    -- Check the constraint schema against expected schema, note any differences
    SELECT 
        @total_column_count = COUNT(*), 
        @wrong_included_column_count = SUM (
            CASE 
                WHEN ic.is_included_column != ckc.is_included_column THEN 1
                ELSE 0
            END), 
        @wrong_sort_direction_count = SUM (
            CASE 
                WHEN ic.is_descending_key != ckc.is_descending_key AND ckc.is_included_column = 0 THEN 1
                ELSE 0
            END), 
        @unexpected_column_count = SUM (
            CASE
                -- [#index_key_columns].[column_name] will be NULL for any unexpected columns
                WHEN ckc.column_name IS NULL THEN 1
                ELSE 0
            END), 
        @out_of_order_key_count = SUM (
            CASE 
                WHEN ic.key_ordinal != (ckc.key_ordinal - @key_ordinal_base) AND ckc.is_included_column = 0 THEN 1
                ELSE 0
            END), 
        @existing_constraint_or_index_name = MIN (i.name), 
        @current_ignore_dup_key = MAX (CASE WHEN i.[ignore_dup_key] = 1 THEN 1 ELSE 0 END), 
        @current_clustered = MAX (CASE WHEN i.type_desc = 'CLUSTERED' THEN 1 ELSE 0 END)
    FROM sys.indexes AS i
    INNER JOIN sys.index_columns AS ic ON 
        i.[object_id] = ic.[object_id] AND i.index_id = ic.index_id
    INNER JOIN sys.columns AS c ON 
        ic.[object_id] = c.[object_id] AND c.column_id = ic.column_id
    LEFT OUTER JOIN #index_key_columns AS ckc ON 
        c.name = ckc.column_name 
    WHERE 
        i.[object_id] = @table_object_id AND 
        ckc.constraint_name = @constraint_or_index_name AND 
        -- Match index on expected name unless it's a PK, in which case we'll just look for [is_primary_key]=1
        (i.name = @constraint_or_index_name OR (@object_type = 'PRIMARY KEY' AND i.is_primary_key = 1)) AND 
        i.is_hypothetical = 0;

    -- If the constraint doesn't yet exist, or if it exists but is defined incorrectly, we 
    -- need to create it (and may need to drop some related objects before we create it). 
    IF (@total_column_count != @expected_column_count OR 
        @wrong_included_column_count > 0 OR 
        @wrong_sort_direction_count > 0 OR 
        @unexpected_column_count > 0 OR 
        @out_of_order_key_count > 0 OR 
        @current_ignore_dup_key != @ignore_dup_key OR
        @current_clustered != @clustered)
    BEGIN
        -- If this is a clustered index or clustered primary key, drop any NC indexes. 
        -- The calling script will rebuild them later, and if we don't drop them before we 
        -- drop the existing clustered PK/index, we'll end up wasting time by rebuilding 
        -- the nonclustered indexes twice (first when the clustered index is dropped, then 
        -- again when it gets recreated). 
        IF (@clustered = 1)
        BEGIN
            DECLARE @nc_index_name sysname;

            RAISERROR (N'    Dropping nonclustered indexes before dropping the clustered index...', 0, 1) WITH NOWAIT;

            DECLARE nc_indexes INSENSITIVE CURSOR FOR 
            SELECT i.name AS nc_index_name
            FROM sys.indexes AS i
            WHERE i.type_desc = 'NONCLUSTERED' AND i.is_unique_constraint = 0 AND i.is_hypothetical = 0 
                AND i.is_primary_key = 0 AND i.[object_id] = @table_object_id
            OPEN nc_indexes;
            FETCH NEXT FROM nc_indexes INTO @nc_index_name;

            WHILE (@@FETCH_STATUS = 0)
            BEGIN
                SET @sql = N'DROP INDEX ' + QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name) 
                    + '.' + QUOTENAME (@nc_index_name) + ';';
                RAISERROR (N'    Dropping nonclustered index: %s', 0, 1, @sql) WITH NOWAIT;
                EXEC (@sql);
                FETCH NEXT FROM nc_indexes INTO @nc_index_name;
            END;
            CLOSE nc_indexes;
            DEALLOCATE nc_indexes;
        END;

        -- If this is a primary key referenced by foreign keys, we have to drop the foreign keys before 
        -- we can drop the PK.  We also have to drop the foreign keys if we are about to drop a clustered 
        -- primary key to "make room" for a clustered non-PK index. 
        IF ((@object_type = 'PRIMARY KEY') OR 
            (@object_type = 'INDEX' AND @clustered = 1 AND 
                EXISTS (SELECT * FROM sys.indexes AS i WHERE i.[object_id] = @table_object_id AND i.is_primary_key = 1 AND i.type_desc = 'CLUSTERED')))
        BEGIN
            DECLARE @fk_constraint_name sysname;
            DECLARE @fk_table_schema sysname;
            DECLARE @fk_table_name sysname;

            DECLARE fk_constraints INSENSITIVE CURSOR FOR 
            SELECT 
                OBJECT_SCHEMA_NAME (fk.parent_object_id) AS fk_table_schema,  
                OBJECT_NAME (fk.parent_object_id) AS fk_table_name,
                fk.name AS fk_constraint_name
            FROM sys.foreign_keys AS fk
            WHERE referenced_object_id = @table_object_id
            OPEN fk_constraints;
            FETCH NEXT FROM fk_constraints INTO @fk_table_schema, @fk_table_name, @fk_constraint_name;

            WHILE (@@FETCH_STATUS = 0)
            BEGIN
                RAISERROR (N'    Dropping foreign key [%s].[%s].[%s] because it references table [%s]...', 0, 1, 
                    @fk_table_schema, @fk_table_name, @fk_constraint_name, @table_name) WITH NOWAIT;
                SET @sql = N'ALTER TABLE ' + QUOTENAME (@fk_table_schema) + '.' + QUOTENAME (@fk_table_name) 
                    + ' DROP CONSTRAINT ' + QUOTENAME (@fk_constraint_name) + ';';
                EXEC (@sql);
                FETCH NEXT FROM fk_constraints INTO @fk_table_schema, @fk_table_name, @fk_constraint_name;
            END;
            CLOSE fk_constraints;
            DEALLOCATE fk_constraints;
        END;

        -- Drop the incorrect constraint, if it exists. 
        IF (@total_column_count > 0)
        BEGIN
            RAISERROR (N'    Incorrect existing definition (current %d vs. expected %d key columns, %d wrong included columns, %d wrong sort direction, %d unexpected columns, %d out-of-order columns)', 0, 1, 
                @total_column_count, @expected_column_count, @wrong_included_column_count, @wrong_sort_direction_count, @unexpected_column_count, @out_of_order_key_count) WITH NOWAIT;
                                
            -- Drop the existing malformed constraint or index so that we can recreate it correctly
            IF @object_type = 'PRIMARY KEY'
            BEGIN
                SET @sql = N'ALTER TABLE ' + QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name) 
                    + ' DROP CONSTRAINT ' + QUOTENAME (@existing_constraint_or_index_name) + ';';
            END
            ELSE BEGIN
                SET @sql = N'DROP INDEX ' + QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name) 
                    + '.' + QUOTENAME (@existing_constraint_or_index_name) + ';';
            END;
            RAISERROR (N'    Dropping existing object: %s', 0, 1, @sql) WITH NOWAIT;
            EXEC (@sql);
        END;
        
        -- If we are about to create a clustered PK index but a clustered (non-PK) index exists, 
        -- we must drop the clustered index before we can create the clustered PK (or vice versa). 
        -- Example: Table has a clustered index, and we need to convert an existing non-clustered 
        -- PK into a clustered PK. 
        IF (@clustered = 1)
        BEGIN
            DECLARE @is_primary_key bit;
            SET @existing_constraint_or_index_name = NULL;
            SELECT 
                @existing_constraint_or_index_name = i.name, 
                @is_primary_key = is_primary_key
            FROM sys.indexes AS i
            WHERE i.type_desc = 'CLUSTERED' AND i.is_hypothetical = 0 AND i.[object_id] = @table_object_id
            
            IF (@existing_constraint_or_index_name IS NOT NULL)
            BEGIN
                -- Drop the existing clustered index to make way for our clustered PK 
                -- (or drop the existing clustered PK to make way for our clustered index)
                IF (@is_primary_key = 1)
                BEGIN
                    SET @sql = N'ALTER TABLE ' + QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name) 
                        + ' DROP CONSTRAINT ' + QUOTENAME (@existing_constraint_or_index_name) + ';';
                END
                ELSE BEGIN
                    SET @sql = N'DROP INDEX ' + QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name) 
                        + '.' + QUOTENAME (@existing_constraint_or_index_name) + ';';
                END;
                RAISERROR (N'    Dropping existing clustered index: %s', 0, 1, @sql) WITH NOWAIT;
                EXEC (@sql);
            END;
        END;

        -- Finally, create the PK or index
        IF @object_type = 'PRIMARY KEY'
        BEGIN
            SET @sql = N'ALTER TABLE ' + QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name) 
                + ' ADD CONSTRAINT ' + QUOTENAME (@constraint_or_index_name) + ' PRIMARY KEY ' 
                + CASE @clustered WHEN 1 THEN 'CLUSTERED' ELSE 'NONCLUSTERED' END 
                + ' (' + @key_column_list + ')'
                + CASE @ignore_dup_key WHEN 1 THEN ' WITH (IGNORE_DUP_KEY=ON)' ELSE '' END 
                + ';';
        END
        ELSE BEGIN
            SET @sql = N'CREATE ' + CASE @clustered WHEN 1 THEN 'CLUSTERED' ELSE 'NONCLUSTERED' END 
                + ' INDEX ' + QUOTENAME (@constraint_or_index_name) + ' ON ' 
                + QUOTENAME (@table_schema) + '.' + QUOTENAME (@table_name) 
                + ' (' + @key_column_list + ')'
                + CASE WHEN LEN (@include_column_list) > 0 THEN ' INCLUDE (' + @include_column_list + ') ' ELSE '' END 
                + CASE @ignore_dup_key WHEN 1 THEN ' WITH (IGNORE_DUP_KEY=ON)' ELSE '' END 
                + ';';
        END;
        RAISERROR (N'    Creating new object: %s', 0, 1, @sql) WITH NOWAIT;
        EXEC (@sql);
    END;
END;
GO

-- Create the temp table used to pass column lists to helper proc [#create_or_alter_primary_key_or_index]
IF OBJECT_ID ('tempdb..#index_key_columns') IS NOT NULL
BEGIN
    DROP TABLE #index_key_columns;
END;
GO
CREATE TABLE #index_key_columns (
    key_ordinal         int IDENTITY, 
    constraint_name     sysname COLLATE CATALOG_DEFAULT, 
    column_name         sysname COLLATE CATALOG_DEFAULT, 
    is_included_column  bit, 
    is_descending_key   bit
);
GO


-- Start a transaction
BEGIN TRANSACTION InstMdwSql
GO


/**********************************************************************/
/* UPGRADE SECTION - UPDATE EXISTING OBJECTS                          */
/**********************************************************************/

--
-- >>> CTP5 -> CTP6 Upgrade
--

-- Update notable_query_plan
IF (OBJECT_ID(N'snapshots.notable_query_plan', 'U') IS NOT NULL)
BEGIN
    RAISERROR ('Updating table [snapshots].[notable_query_plan]...', 0, 1) WITH NOWAIT;

    -- CTP6 added the [plan_generation_num] column to this table
    IF NOT EXISTS (SELECT column_id FROM sys.all_columns c WHERE c.name = N'plan_generation_num' AND c.object_id = OBJECT_ID(N'snapshots.notable_query_plan', 'U'))
    BEGIN
        ALTER TABLE [snapshots].[notable_query_plan]
            ADD [plan_generation_num] bigint DEFAULT 0 NOT NULL;
    END;
END;
GO

-- Update active_sessions_and_requests
-- Add a column
IF (OBJECT_ID(N'snapshots.active_sessions_and_requests', 'U') IS NOT NULL)
BEGIN
    RAISERROR ('Updating table [snapshots].[active_sessions_and_requests]...', 0, 1) WITH NOWAIT;

    IF NOT EXISTS (SELECT column_id FROM sys.all_columns c WHERE c.name = N'is_blocking' AND c.object_id = OBJECT_ID(N'snapshots.active_sessions_and_requests', 'U'))
    BEGIN
        ALTER TABLE [snapshots].[active_sessions_and_requests] 
            ADD [is_blocking] bit DEFAULT 0 NOT NULL
    END;

    -- Alter column length for [command] as 32; column : [command] sys.dm_exec_requests in SQL 11 is of type nvarchar(32)
    IF EXISTS (SELECT c.system_type_id FROM sys.columns c WHERE c.object_id = OBJECT_ID('snapshots.active_sessions_and_requests', 'U') AND c.name = N'command')
    BEGIN
        RAISERROR ('Updating [command] column length to 32...', 0, 1) WITH NOWAIT;
        
        ALTER TABLE  [snapshots].[active_sessions_and_requests] 
            ALTER COLUMN [command] nvarchar(32) NULL;
    END;
	
	-- Alter column length for [host_name] as 128; column : [host_name] sys.dm_exec_requests in SQL 11 is of type nvarchar(128)
    IF EXISTS (SELECT c.system_type_id FROM sys.columns c WHERE c.object_id = OBJECT_ID('snapshots.active_sessions_and_requests', 'U') 
				AND c.name = N'host_name' AND c.max_length < 128)
    BEGIN
        RAISERROR ('Updating [host_name] column length to 128...', 0, 1) WITH NOWAIT;
        
        ALTER TABLE  [snapshots].[active_sessions_and_requests] 
            ALTER COLUMN [host_name] nvarchar(128) NOT NULL;
    END;

	-- Alter column length for [program_name] as 128; column : [program_name] sys.dm_exec_requests in SQL 11 is of type nvarchar(128)
    IF EXISTS (SELECT c.system_type_id FROM sys.columns c WHERE c.object_id = OBJECT_ID('snapshots.active_sessions_and_requests', 'U') 
				AND c.name = N'program_name' AND c.max_length < 128)
    BEGIN
        RAISERROR ('Updating [program_name] column length to 128...', 0, 1) WITH NOWAIT;
        
        ALTER TABLE  [snapshots].[active_sessions_and_requests] 
            ALTER COLUMN [program_name] nvarchar(128) NOT NULL;
    END;
	
END;
GO


-- Update performance_counter_instances
-- Add Unique constraint for [path] column
-- This constraint creation generates a warning. We will deal with the warning when fix for Defect# 130259 is provided
IF (OBJECT_ID(N'snapshots.performance_counter_instances', 'U') IS NOT NULL)
BEGIN
    RAISERROR ('Updating table [snapshots].[performance_counter_instances]...', 0, 1) WITH NOWAIT;
    
    IF NOT EXISTS (SELECT index_id FROM sys.indexes WHERE object_id = OBJECT_ID('snapshots.performance_counter_instances', 'U') AND name = N'UN_performance_counter_path')
    BEGIN
        ALTER TABLE [snapshots].[performance_counter_instances] 
            ADD CONSTRAINT [UN_performance_counter_path] UNIQUE
            (
                [path]
            ) WITH (IGNORE_DUP_KEY = ON)
            ON [PRIMARY];
    END;
END;
GO

-- Update performance_counter_instances
IF (OBJECT_ID(N'snapshots.performance_counter_values', 'U') IS NOT NULL)
BEGIN
    RAISERROR ('Updating table [snapshots].[performance_counter_values]...', 0, 1) WITH NOWAIT;

    IF EXISTS (SELECT c.system_type_id FROM sys.columns c WHERE c.object_id = OBJECT_ID('snapshots.performance_counter_values', 'U') AND c.name = N'formatted_value' AND c.system_type_id = 127)
    BEGIN
        RAISERROR ('Changing [formatted_value] data type to float...', 0, 1) WITH NOWAIT;
        
        -- This index will be recreated later (after the table definition)
        IF EXISTS (SELECT * FROM sys.indexes AS i WHERE i.name = 'IDX_performance_counter_values1' AND [object_id] = OBJECT_ID ('snapshots.performance_counter_values'))
        BEGIN
            DROP INDEX [IDX_performance_counter_values1] ON [snapshots].[performance_counter_values];
        END;
       
        ALTER TABLE [snapshots].[performance_counter_values]
            ALTER COLUMN [formatted_value] float NOT NULL;
    END;
END;
GO

-- Change int log_id columns to bigint 
IF EXISTS (
    SELECT * 
    FROM sys.columns AS c
    INNER JOIN sys.types AS t ON c.system_type_id = t.system_type_id
    WHERE [object_id] = OBJECT_ID ('core.snapshots_internal')
        AND c.name = 'log_id' AND t.name = 'int'
    )
BEGIN
    RAISERROR ('Changing [core].[snapshots_internal].[log_id] column datatype from int to bigint...', 0, 1) WITH NOWAIT;
    ALTER TABLE [core].[snapshots_internal] ALTER COLUMN [log_id] bigint NOT NULL;
END;
GO
IF (OBJECT_ID ('core.snapshots') IS NOT NULL AND OBJECT_ID ('core.snapshots_internal') IS NOT NULL)
BEGIN
    -- Refresh the view to ensure that it reflects the datatype change
    EXEC sp_refreshview 'core.snapshots'
END
GO

--
-- >>> CTP6 -> CTP6 Refresh/RC0 Upgrade
--
-- Our query stats data collection query merges query stats from sys.dm_exec_query_stats (completed query stats) 
-- and sys.dm_exec_requests (in-progress query stats).  We may not have access to all query stats for any queries 
-- that were only visible as in-progress queries in dm_exec_requests.  For example, this DMV doesn't expose CLR 
-- stats. Some of the columns in our query_stats table must therefore tolerate NULLs. 
IF EXISTS (
    SELECT c.* 
    FROM INFORMATION_SCHEMA.TABLES AS t
    INNER JOIN INFORMATION_SCHEMA.COLUMNS AS c ON c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME 
    WHERE t.TABLE_SCHEMA = 'snapshots' AND t.TABLE_NAME = 'query_stats'
        AND c.COLUMN_NAME = 'snapshot_clr_time' AND c.IS_NULLABLE = 'NO'
)
BEGIN
    RAISERROR ('Making [snapshots].[query_stats] columns NULLable...', 0, 1) WITH NOWAIT;
    ALTER TABLE snapshots.query_stats ALTER COLUMN min_clr_time bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN max_clr_time bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN total_clr_time bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN snapshot_clr_time bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN min_worker_time bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN min_physical_reads bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN min_logical_writes bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN min_logical_reads bigint NULL;
    ALTER TABLE snapshots.query_stats ALTER COLUMN min_elapsed_time bigint NULL;
END;
GO

-- Add the [snapshot_execution_count] column (new to CTP6 Refresh) to [query_stats]
IF OBJECT_ID ('snapshots.query_stats', 'U') IS NOT NULL AND NOT EXISTS (
    SELECT c.* 
    FROM INFORMATION_SCHEMA.TABLES AS t
    INNER JOIN INFORMATION_SCHEMA.COLUMNS AS c ON c.TABLE_SCHEMA = t.TABLE_SCHEMA AND c.TABLE_NAME = t.TABLE_NAME 
    WHERE t.TABLE_SCHEMA = 'snapshots' AND t.TABLE_NAME = 'query_stats'
        AND c.COLUMN_NAME = 'snapshot_execution_count'
)
BEGIN
    RAISERROR ('Adding [snapshot_execution_count] column to [snapshots].[query_stats]...', 0, 1) WITH NOWAIT;
    ALTER TABLE snapshots.query_stats ADD [snapshot_execution_count] bigint NULL;
END;
GO

/**********************************************************************/
/* CORE SCHEMA                                                        */
/**********************************************************************/
RAISERROR('', 0, 1)  WITH NOWAIT;
RAISERROR('Create schema core...', 0, 1)  WITH NOWAIT;
GO
IF (SCHEMA_ID('core') IS NULL)
BEGIN
    DECLARE @sql nvarchar(128)
    SET @sql = 'CREATE SCHEMA core'
    EXEC sp_executesql @sql
END
GO

-- SUPPORTED_COLLECTOR_TYPES
--
RAISERROR('', 0, 1)  WITH NOWAIT;
GO
IF (OBJECT_ID(N'[core].[supported_collector_types_internal]', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [core].[supported_collector_types_internal]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [core].[supported_collector_types_internal] (
        collector_type_uid            uniqueidentifier NOT NULL,
    ) ON [PRIMARY]

END
GO

-- supported_collector_types_internal.PK_supported_collector_types_internal
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_supported_collector_types_internal', 'collector_type_uid', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'core', @table_name = 'supported_collector_types_internal', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_supported_collector_types_internal', 
    @ignore_dup_key = 0, @clustered = 1;
GO


IF (NOT OBJECT_ID(N'core.supported_collector_types', 'V') IS NULL)
BEGIN
    RAISERROR('Dropping view [core].[supported_collector_types]...', 0, 1)  WITH NOWAIT;
    DROP VIEW core.supported_collector_types
END
GO

RAISERROR('Creating view [core].[supported_collector_types]...', 0, 1)  WITH NOWAIT;
GO
CREATE VIEW core.supported_collector_types
AS 
    SELECT collector_type_uid
    FROM core.supported_collector_types_internal
GO

-- SOURCE_INFO
--
IF (OBJECT_ID(N'[core].[source_info_internal]', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [core].[source_info_internal]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [core].[source_info_internal] (
        source_id                   int IDENTITY NOT NULL,
        collection_set_uid          uniqueidentifier NOT NULL,  -- GUID of the collection set that loads the data
        instance_name               sysname COLLATE Latin1_General_CI_AI NOT NULL, -- the name of the machine the data is uploaded from
        days_until_expiration       smallint NOT NULL,          -- how many days data from this source should be kept in the warehouse. 0 indicates forever
        operator                    sysname NOT NULL,           -- login name of the principal who is uploading the data
        
        CONSTRAINT [UQ_collection_set_uid_instance_name] UNIQUE (collection_set_uid, instance_name, operator)
    ) ON [PRIMARY]
END
GO

-- source_info_internal.PK_source_info_internal
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_source_info_internal', 'source_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'core', @table_name = 'source_info_internal', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_source_info_internal', 
    @ignore_dup_key = 0, @clustered = 1;
GO


-- SNAPSHOT_TIMETABLE
--
IF (OBJECT_ID(N'[core].[snapshot_timetable_internal]', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [core].[snapshot_timetable_internal]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [core].[snapshot_timetable_internal] (
        snapshot_time_id            int IDENTITY NOT NULL,
        snapshot_time                datetimeoffset(7) NOT NULL,
            ) ON [PRIMARY]
END
GO

-- snapshot_timetable_internal.PK_snapshots_timetable_internal
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_snapshots_timetable_internal', 'snapshot_time_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'core', @table_name = 'snapshot_timetable_internal', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_snapshots_timetable_internal', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- snapshot_timetable_internal.IDX_snapshot_time
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_snapshot_time', 'snapshot_time', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'core', @table_name = 'snapshot_timetable_internal', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_snapshot_time', 
    @ignore_dup_key = 0, @clustered = 0;
GO


-- SNAPSHOTS
--
IF (OBJECT_ID(N'[core].[snapshots_internal]', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [core].[snapshots_internal]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [core].[snapshots_internal] (
        snapshot_id                    int IDENTITY NOT NULL,
        snapshot_time_id               int NOT NULL,
        source_id                      int NOT NULL,
        log_id                         bigint NOT NULL,             -- reference to the log table
    ) ON [PRIMARY]

    CREATE STATISTICS [STAT_snapshots_internal2] ON [core].[snapshots_internal](
        [snapshot_time_id], 
        [source_id]
    )

    CREATE STATISTICS [STAT_snapshots_internal3] ON [core].[snapshots_internal](
        [snapshot_time_id], [snapshot_id], [source_id])

END
GO

-- snapshots_internal.PK_snapshots_internal
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_snapshots_internal', 'snapshot_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'core', @table_name = 'snapshots_internal', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_snapshots_internal', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- snapshots_internal.IDX_snapshot_time_id
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_snapshot_time_id', 'snapshot_time_id', 0, 0), 
    ('IDX_snapshot_time_id', 'source_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'core', @table_name = 'snapshots_internal', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_snapshot_time_id', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- snapshots_internal.FK_snapshots_source_info
IF OBJECT_ID ('core.FK_snapshots_source_info', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_snapshots_source_info] on core.snapshots_internal ...', 0, 1) WITH NOWAIT;
    ALTER TABLE core.snapshots_internal
        ADD CONSTRAINT [FK_snapshots_source_info] FOREIGN KEY(source_id)
        REFERENCES core.source_info_internal (source_id)
END;
GO

-- snapshots_internal.FK_snapshots_snapshots_timetable
IF OBJECT_ID ('core.FK_snapshots_snapshots_timetable', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_snapshots_snapshots_timetable] on core.snapshots_internal ...', 0, 1) WITH NOWAIT;
    ALTER TABLE core.snapshots_internal
        ADD CONSTRAINT [FK_snapshots_snapshots_timetable] FOREIGN KEY(snapshot_time_id)
        REFERENCES core.snapshot_timetable_internal (snapshot_time_id)
END;
GO


-- SNAPSHOTS VIEW
--
IF (NOT OBJECT_ID(N'core.snapshots', 'V') IS NULL)
BEGIN
    RAISERROR('Dropping view [core].[snapshots]...', 0, 1)  WITH NOWAIT;
    DROP VIEW core.snapshots
END
GO

RAISERROR('Creating view [core].[snapshots]...', 0, 1)  WITH NOWAIT;
GO
CREATE VIEW core.snapshots
AS 
    SELECT 
        s.source_id,
        s.snapshot_id, 
        s.snapshot_time_id,
        t.snapshot_time, 
        CASE src.days_until_expiration
            WHEN 0 THEN NULL
            ELSE DATEADD(DAY, src.days_until_expiration, t.snapshot_time)
        END AS valid_through,
        src.instance_name, 
        src.collection_set_uid, 
        src.operator,
        s.log_id
    FROM core.source_info_internal src, core.snapshots_internal s, core.snapshot_timetable_internal t
    WHERE src.source_id = s.source_id AND s.snapshot_time_id = t.snapshot_time_id
GO

--
-- This stored proc creates a snapshot entry and return the id
--
IF (NOT OBJECT_ID(N'core.sp_create_snapshot', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [core].[sp_create_snapshot] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [core].[sp_create_snapshot]
END
GO 

RAISERROR('Creating procedure [core].[sp_create_snapshot] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE core.sp_create_snapshot
    @collection_set_uid     uniqueidentifier,
    @collector_type_uid     uniqueidentifier,
    @machine_name           sysname,
    @named_instance         sysname,
    @log_id                 bigint,
    @snapshot_id            int OUTPUT
AS 
BEGIN
    SET NOCOUNT ON
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_writer'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_writer')
        RETURN(1) -- Failure
    END
    
    DECLARE @operator sysname;
    SELECT @operator = SUSER_SNAME();

    DECLARE @instance_name sysname
    SET @named_instance = NULLIF(RTRIM(LTRIM(@named_instance)), N'')
    IF (@named_instance = 'MSSQLSERVER')
        SET @instance_name = @machine_name
    ELSE
        SET @instance_name = @machine_name + N'\' + @named_instance

    -- Parameters check
    -- Find the source_id that matches the requested collection set and operator
    DECLARE @source_id  int
    SET @source_id = (SELECT source_id 
                        FROM core.source_info_internal
                        WHERE collection_set_uid = @collection_set_uid 
                          AND operator = @operator
                          AND instance_name = @instance_name)

    IF(@source_id IS NULL)
    BEGIN
        DECLARE @collection_set_uid_as_char NVARCHAR(36)
        SELECT @collection_set_uid_as_char = CONVERT(NVARCHAR(36), @collection_set_uid)
        RAISERROR(14679, -1, -1, N'@collection_set_uid', @collection_set_uid_as_char)
    END

    -- Make sure the collector_type is registered in this warehouse
    IF NOT EXISTS (SELECT collector_type_uid FROM core.supported_collector_types WHERE collector_type_uid = @collector_type_uid)
    BEGIN
        DECLARE @collector_type_uid_as_char NVARCHAR(36)
        SELECT @collector_type_uid_as_char = CONVERT(NVARCHAR(36), @collector_type_uid)
        RAISERROR(14679, -1, -1, N'@collector_type_uid', @collector_type_uid_as_char)
    END

    -- Get the snapshot time
    BEGIN TRY
        BEGIN TRAN
        DECLARE @snapshot_time_id int

        IF NOT EXISTS (SELECT snapshot_time_id FROM core.snapshot_timetable_internal WITH(UPDLOCK) WHERE snapshot_time > DATEADD (minute, -1, SYSDATETIMEOFFSET()))
        BEGIN
            INSERT INTO core.snapshot_timetable_internal
            (
                snapshot_time
            )
            VALUES
            (
                SYSDATETIMEOFFSET()
            )
            SET @snapshot_time_id = SCOPE_IDENTITY()
        END
        ELSE
        BEGIN
            SET @snapshot_time_id = (SELECT MAX(snapshot_time_id) FROM core.snapshot_timetable_internal)
        END

        -- Finally insert an entry into snapshots table
        INSERT INTO core.snapshots_internal
        (
            snapshot_time_id,
            source_id,
            log_id
        )
        VALUES
        (
            @snapshot_time_id,
            @source_id,
            @log_id
        )
        SET @snapshot_id = SCOPE_IDENTITY()

        IF (@snapshot_id IS NULL)
        BEGIN
            RAISERROR(14262, -1, -1, '@snapshot_id', @snapshot_id)
            RETURN(1)
        END
        ELSE
        BEGIN
            COMMIT TRAN
        END
    END TRY
    BEGIN CATCH
        IF (@@TRANCOUNT > 0) 
            ROLLBACK TRANSACTION

        -- Rethrow the error
        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, -1, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
    END CATCH

END
GO

--
-- This stored proc updates data in source_info table 
--
IF (NOT OBJECT_ID(N'core.sp_update_data_source', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [core].[sp_update_data_source] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [core].[sp_update_data_source]
END
GO 

RAISERROR('Creating procedure [core].[sp_update_data_source] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [core].[sp_update_data_source]
    @collection_set_uid     uniqueidentifier,
    @machine_name           sysname,
    @named_instance         sysname,
    @days_until_expiration  smallint,
    @source_id              int OUTPUT
AS
BEGIN
    SET NOCOUNT ON
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_writer'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_writer')
        RETURN(1) -- Failure
    END

    -- Parameters check
    IF (@collection_set_uid IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@collection_set_uid')
        RETURN(1) -- Failure
    END

    SET @machine_name = NULLIF(RTRIM(LTRIM(@machine_name)), N'')
    IF (@machine_name IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@machine_name')
        RETURN(1) -- Failure
    END

    DECLARE @instance_name sysname
    SET @named_instance = NULLIF(RTRIM(LTRIM(@named_instance)), N'')
    IF (@named_instance = 'MSSQLSERVER')
        SET @instance_name = @machine_name
    ELSE
        SET @instance_name = @machine_name + N'\' + @named_instance

    IF (@days_until_expiration IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@days_until_expiration')
        RETURN(1) -- Failure
    END

    IF (@days_until_expiration < 0)
    BEGIN
        RAISERROR(14266, -1, -1, '@days_until_expiration', ' >= 0')
        RETURN (1) -- Failure
    END
    
    DECLARE @operator sysname
    SELECT @operator = SUSER_SNAME()

    BEGIN TRY
        BEGIN TRAN
        
        -- Insert data into the table
        -- We specify the lock hint, in order to keep the exlusive lock on the row from the moment we select it till we 
        -- update it
        SET @source_id = (SELECT source_id FROM core.source_info_internal WITH(UPDLOCK) WHERE collection_set_uid = @collection_set_uid AND instance_name = @instance_name AND operator = @operator)
        IF @source_id IS NULL
        BEGIN
            INSERT INTO core.source_info_internal
            (
                collection_set_uid,
                instance_name,
                days_until_expiration,
                operator
            )
            VALUES
            (
                @collection_set_uid,
                @instance_name,
                @days_until_expiration,
                @operator
            )
            SET @source_id = SCOPE_IDENTITY()
        END
        ELSE
        BEGIN
            UPDATE core.source_info_internal
            SET 
                days_until_expiration = @days_until_expiration
            WHERE source_id = @source_id;
        END
            
        COMMIT TRAN
        RETURN (0)
    END TRY
    BEGIN CATCH
        IF (@@TRANCOUNT > 0) 
            ROLLBACK TRANSACTION

        -- Rethrow the error
        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorSeverity  INT;
        DECLARE @ErrorState     INT;
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        DECLARE @ErrorProcedure NVARCHAR(200);
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorSeverity = ERROR_SEVERITY(),
               @ErrorState = ERROR_STATE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE(),
               @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

        RAISERROR (14684, -1, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);
        RETURN (1)
    END CATCH
END
GO


--
-- This stored proc adds new entry in supported_collector_types table
--
IF (NOT OBJECT_ID(N'core.sp_add_collector_type', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [core].[sp_add_collector_type] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [core].[sp_add_collector_type]
END
GO 

RAISERROR('Creating procedure [core].[sp_add_collector_type] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [core].[sp_add_collector_type]
    @collector_type_uid     uniqueidentifier
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_admin'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_admin')
        RETURN(1) -- Failure
    END

    -- Parameters check
    IF (@collector_type_uid IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@collector_type_uid')
        RETURN(1) -- Failure
    END

    -- Insert new collector type
    IF NOT EXISTS (SELECT collector_type_uid FROM core.supported_collector_types WHERE collector_type_uid = @collector_type_uid)
    BEGIN
        INSERT INTO core.supported_collector_types
        (
            collector_type_uid
        )
        VALUES
        (
            @collector_type_uid
        )
    END
    ELSE
    BEGIN
        -- Raise an info message, but do not fail
        DECLARE @collector_type_uid_as_char NVARCHAR(36)
        SELECT @collector_type_uid_as_char = CONVERT(NVARCHAR(36), @collector_type_uid)
        RAISERROR(14261, 10, -1, '@collector_type_uid', @collector_type_uid_as_char)
    END

    RETURN (0)
END
GO

--
-- This stored proc removes and existing entry from supported_collector_types table
--
IF (NOT OBJECT_ID(N'core.sp_remove_collector_type', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [core].[sp_remove_collector_type] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [core].[sp_remove_collector_type]
END
GO 

RAISERROR('Creating procedure [core].[sp_remove_collector_type] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [core].[sp_remove_collector_type]
    @collector_type_uid     uniqueidentifier
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_admin'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_admin')
        RETURN(1) -- Failure
    END

    -- Parameters check
    IF (@collector_type_uid IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@collector_type_uid')
        RETURN(1) -- Failure
    END

    -- Delete collector type
    IF EXISTS (SELECT collector_type_uid FROM core.supported_collector_types WHERE collector_type_uid = @collector_type_uid)
    BEGIN
        DELETE FROM core.supported_collector_types WHERE collector_type_uid = @collector_type_uid
    END
    ELSE
    BEGIN
        DECLARE @collector_type_uid_as_char NVARCHAR(36)
        SELECT @collector_type_uid_as_char = CONVERT(NVARCHAR(36), @collector_type_uid)
        RAISERROR(14262, -1, -1, '@collector_type_uid', @collector_type_uid_as_char)
        RETURN (1) -- Failure
    END

    RETURN (0)
END
GO

--
-- This table is used to determine status of current purge operation.
-- Contains records only if a sp_stop_purge operation was requested.
--
RAISERROR('', 0, 1)  WITH NOWAIT;
GO
IF (OBJECT_ID(N'[core].[purge_info_internal]', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [core].[purge_info_internal]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [core].[purge_info_internal] (
      stop_purge bit NOT NULL,
    ) ON [PRIMARY]

END
GO


--
-- This stored proc removes orphaned notable_query_plan data from the warehouse 
--
IF (NOT OBJECT_ID(N'core.sp_purge_orphaned_notable_query_plan', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [core].[sp_purge_orphaned_notable_query_plan] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [core].[sp_purge_orphaned_notable_query_plan]
END
GO 

RAISERROR('Creating procedure [core].[sp_purge_orphaned_notable_query_plan] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [core].[sp_purge_orphaned_notable_query_plan]
    @duration smallint = NULL,
    @end_time datetime = NULL,
    @delete_batch_size int = 500
AS
BEGIN
    PRINT 'Begin purging orphaned records in snapshots.notable_query_plan Current UTC Time:' + CONVERT(VARCHAR, GETUTCDATE())

    DECLARE @stop_purge int

    -- Delete orphaned rows from snapshots.notable_query_plan.  Query plans are not deleted by the generic purge 
    -- process that deletes other data (above) because query plan rows are not tied to a particular snapshot ID. 
    -- Purging query plans table  as a special case, by looking for plans that 
    -- are no longer referenced by any of the rows in the snapshots.query_stats table.  We need to delete these 
    -- rows in small chunks, since deleting many GB in a single delete statement would cause lock escalation and 
    -- an explosion in the size of the transaction log (individual query plans can be 10-50MB).  
    DECLARE @rows_affected int;
    -- set expected rows affected as delete batch size
    SET @rows_affected = @delete_batch_size;
    
    -- select set of orphaned query plans to be deleted into a temp table 
    SELECT qp.[sql_handle],
        qp.plan_handle,
        qp.plan_generation_num,
        qp.statement_start_offset,
        qp.statement_end_offset,
        qp.creation_time
    INTO #tmp_notable_query_plan
    FROM snapshots.notable_query_plan AS qp 
    WHERE NOT EXISTS (
        SELECT snapshot_id 
        FROM snapshots.query_stats AS qs
        WHERE qs.[sql_handle] = qp.[sql_handle] AND qs.plan_handle = qp.plan_handle 
            AND qs.plan_generation_num = qp.plan_generation_num 
            AND qs.statement_start_offset = qp.statement_start_offset 
            AND qs.statement_end_offset = qp.statement_end_offset 
            AND qs.creation_time = qp.creation_time)

    WHILE (@rows_affected = @delete_batch_size)
    BEGIN
        -- Deleting TOP N orphaned rows in query plan table by joining info from temp table variable
        -- This is done to speed up delete query. 
        DELETE TOP (@delete_batch_size) snapshots.notable_query_plan 
        FROM snapshots.notable_query_plan AS qp , #tmp_notable_query_plan AS tmp
        WHERE tmp.[sql_handle] = qp.[sql_handle] 
            AND tmp.plan_handle = qp.plan_handle 
            AND tmp.plan_generation_num = qp.plan_generation_num 
            AND tmp.statement_start_offset = qp.statement_start_offset 
            AND tmp.statement_end_offset = qp.statement_end_offset 
            AND tmp.creation_time = qp.creation_time
        
        SET @rows_affected = @@ROWCOUNT;
        IF(@rows_affected > 0)
        BEGIN
            RAISERROR ('Deleted %d orphaned rows from snapshots.notable_query_plan', 0, 1, @rows_affected) WITH NOWAIT;
        END

        -- Check if the execution of the stored proc exceeded the @duration specified
        IF (@duration IS NOT NULL)
        BEGIN
            IF (GETUTCDATE()>=@end_time)
            BEGIN
                PRINT 'Stopping purge. More than ' + CONVERT(VARCHAR, @duration) + ' minutes passed since the start of operation.';
                BREAK
            END
        END

        -- Check if somebody wanted to stop the purge operation
        SELECT @stop_purge = COUNT(stop_purge) FROM [core].[purge_info_internal]
        IF (@stop_purge > 0)
        BEGIN
            PRINT 'Stopping purge. Detected a user request to stop purge.';
            BREAK
        END
    END;
    
    PRINT 'End purging orphaned records in snapshots.notable_query_plan Current UTC Time:' + CONVERT(VARCHAR, GETUTCDATE())
END

GO

--
-- This stored proc removes orphaned notable_query_text data from the warehouse 
--
IF (NOT OBJECT_ID(N'core.sp_purge_orphaned_notable_query_text', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [core].[sp_purge_orphaned_notable_query_text] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [core].[sp_purge_orphaned_notable_query_text]
END
GO 

RAISERROR('Creating procedure [core].[sp_purge_orphaned_notable_query_text] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [core].[sp_purge_orphaned_notable_query_text]
    @duration smallint = NULL,
    @end_time datetime = NULL,
    @delete_batch_size int = 500
AS
BEGIN
    PRINT 'Begin purging orphaned records in snapshots.notable_query_text Current UTC Time:' + CONVERT(VARCHAR, GETUTCDATE())

    DECLARE @stop_purge int

    -- Delete orphaned rows from snapshots.notable_query_text.  Query texts are not deleted by the generic purge 
    -- process that deletes other data (above) because query text rows are not tied to a particular snapshot ID. 
    -- Purging  query text table as a special case, by looking for plans that 
    -- are no longer referenced by any of the rows in the snapshots.query_stats table.  We need to delete these 
    -- rows in small chunks, since deleting many GB in a single delete statement would cause lock escalation and 
    -- an explosion in the size of the transaction log (individual query plans can be 10-50MB).  
    DECLARE @rows_affected int;
    -- set expected rows affected as delete batch size
    SET @rows_affected = @delete_batch_size;

    SELECT qtext.[sql_handle] 
    INTO #tmp_notable_query_text
    FROM snapshots.notable_query_text AS qtext
    EXCEPT (
	SELECT qt.[sql_handle] FROM snapshots.notable_query_text AS qt 
	INNER HASH JOIN snapshots.query_stats AS qs 
	ON (qt.sql_handle = qs.sql_handle))

    WHILE (@rows_affected = @delete_batch_size)
    BEGIN
        -- Deleting TOP N orphaned rows in query text table by joining info from temp table
        -- This is done to speed up delete query. 
        DELETE TOP (@delete_batch_size) snapshots.notable_query_text 
         FROM snapshots.notable_query_text AS qt, #tmp_notable_query_text AS tmp
        WHERE tmp.[sql_handle] = qt.[sql_handle]
        
        SET @rows_affected = @@ROWCOUNT;
        IF(@rows_affected > 0)
        BEGIN
            RAISERROR ('Deleted %d orphaned rows from snapshots.notable_query_text', 0, 1, @rows_affected) WITH NOWAIT;
        END

        -- Check if the execution of the stored proc exceeded the @duration specified
        IF (@duration IS NOT NULL)
        BEGIN
            IF (GETUTCDATE()>=@end_time)
            BEGIN
                PRINT 'Stopping purge. More than ' + CONVERT(VARCHAR, @duration) + ' minutes passed since the start of operation.';
                BREAK
            END
        END

        -- Check if somebody wanted to stop the purge operation
        SELECT @stop_purge = COUNT(stop_purge) FROM [core].[purge_info_internal]
        IF (@stop_purge > 0)
        BEGIN
            PRINT 'Stopping purge. Detected a user request to stop purge.';
            BREAK
        END
    END;

    PRINT 'End purging orphaned records in snapshots.notable_query_text Current UTC Time:' + CONVERT(VARCHAR, GETUTCDATE())

END

GO

--
-- This stored proc removes data from the warehouse that reached its expiration date
--
IF (NOT OBJECT_ID(N'core.sp_purge_data', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [core].[sp_purge_data] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [core].[sp_purge_data]
END
GO 

RAISERROR('Creating procedure [core].[sp_purge_data] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [core].[sp_purge_data]
    @retention_days smallint = NULL,
    @instance_name sysname = NULL,
    @collection_set_uid uniqueidentifier = NULL,
    @duration smallint = NULL,
    @delete_batch_size int = 500
AS
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_admin'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_admin')
        RETURN(1) -- Failure
    END

    -- Validate parameters
    IF ((@retention_days IS NOT NULL) AND (@retention_days < 0))
    BEGIN
        RAISERROR(14200, -1, -1, '@retention_days')
        RETURN(1) -- Failure
    END

    IF ((@duration IS NOT NULL) AND (@duration < 0))
    BEGIN
        RAISERROR(14200, -1, -1, '@duration')
        RETURN(1) -- Failure
    END

    -- This table will contain a record if somebody requests purge to stop
    -- If user requested us to purge data - we reset the content of it - and proceed with purge
    -- If somebody in a different session wants purge operations to stop he adds a record
    -- that we will discover while purge in progress
    --
    -- We dont clear this flag when we exit since multiple purge operations with differnet
    -- filters may proceed, and we want all of them to stop.
    DELETE FROM [core].[purge_info_internal]

    SET @instance_name = NULLIF(LTRIM(RTRIM(@instance_name)), N'')

    -- Calculate the time when the operation should stop (NULL otherwise)
    DECLARE @end_time datetime
    IF (@duration IS NOT NULL)
    BEGIN
        SET @end_time = DATEADD(minute, @duration, GETUTCDATE())
    END

    -- Declare table that will be used to find what are the valid
    -- candidate snapshots that could be selected for purge
    DECLARE @purge_candidates table
    (
        snapshot_id int NOT NULL,
        snapshot_time datetime NOT NULL,
        instance_name sysname NOT NULL,
        collection_set_uid uniqueidentifier NOT NULL
    )

    -- Find candidates that match the retention_days criteria (if specified)
    IF (@retention_days IS NULL)
    BEGIN
        -- User did not specified a value for @retention_days, therfore we
        -- will use the default expiration day as marked in the source info
        INSERT INTO @purge_candidates
        SELECT s.snapshot_id, s.snapshot_time, s.instance_name, s.collection_set_uid
        FROM core.snapshots s
        WHERE (GETUTCDATE() >= s.valid_through)
    END
    ELSE
    BEGIN
        -- User specified a value for @retention_days, we will use this overriden value
        -- when deciding what means old enough to qualify for purge this overrides
        -- the days_until_expiration value specified in the source_info_internal table
        INSERT INTO @purge_candidates
        SELECT s.snapshot_id, s.snapshot_time, s.instance_name, s.collection_set_uid
        FROM core.snapshots s
        WHERE GETUTCDATE() >= DATEADD(DAY, @retention_days, s.snapshot_time)
    END

    -- Determine which is the oldest snapshot, from the list of candidates
    DECLARE oldest_snapshot_cursor CURSOR FORWARD_ONLY READ_ONLY FOR
    SELECT p.snapshot_id, p.instance_name, p.collection_set_uid
    FROM @purge_candidates p
    WHERE 
        ((@instance_name IS NULL) or (p.instance_name = @instance_name)) AND
        ((@collection_set_uid IS NULL) or (p.collection_set_uid = @collection_set_uid))
    ORDER BY p.snapshot_time ASC    

    OPEN oldest_snapshot_cursor

    DECLARE @stop_purge int
    DECLARE @oldest_snapshot_id int
    DECLARE @oldest_instance_name sysname
    DECLARE @oldest_collection_set_uid uniqueidentifier

    FETCH NEXT FROM oldest_snapshot_cursor
    INTO @oldest_snapshot_id, @oldest_instance_name, @oldest_collection_set_uid

    -- As long as there are snapshots that matched the time criteria
    WHILE @@FETCH_STATUS = 0
    BEGIN

        -- Filter out records that do not match the other filter crieria
        IF ((@instance_name IS NULL) or (@oldest_instance_name = @instance_name))
        BEGIN

            -- There was no filter specified for instance_name or the instance matches the filter
            IF ((@collection_set_uid IS NULL) or (@oldest_collection_set_uid = @collection_set_uid))
            BEGIN

                -- There was no filter specified for the collection_set_uid or the collection_set_uid matches the filter
                BEGIN TRANSACTION tran_sp_purge_data

                -- Purge data associated with this snapshot. Note: deleting this snapshot
                -- triggers cascade delete in all warehouse tables based on the foreign key 
                -- relationship to snapshots table

                -- Cascade cleanup of all data related referencing oldest snapshot
                DELETE core.snapshots_internal
                FROM core.snapshots_internal s
                WHERE s.snapshot_id = @oldest_snapshot_id

                COMMIT TRANSACTION tran_sp_purge_data

                PRINT 'Snapshot #' + CONVERT(VARCHAR, @oldest_snapshot_id) + ' purged.';
            END

        END

        -- Check if the execution of the stored proc exceeded the @duration specified
        IF (@duration IS NOT NULL)
        BEGIN
            IF (GETUTCDATE()>=@end_time)
            BEGIN
                PRINT 'Stopping purge. More than ' + CONVERT(VARCHAR, @duration) + ' minutes passed since the start of operation.';
                BREAK
            END
        END

        -- Check if somebody wanted to stop the purge operation
        SELECT @stop_purge = COUNT(stop_purge) FROM [core].[purge_info_internal]
        IF (@stop_purge > 0)
        BEGIN
                PRINT 'Stopping purge. Detected a user request to stop purge.';
            BREAK
        END

        -- Move to next oldest snapshot
        FETCH NEXT FROM oldest_snapshot_cursor
        INTO @oldest_snapshot_id, @oldest_instance_name, @oldest_collection_set_uid

    END

    CLOSE oldest_snapshot_cursor
    DEALLOCATE oldest_snapshot_cursor

    -- delete orphaned query plans
    EXEC [core].[sp_purge_orphaned_notable_query_plan] @duration = @duration, @end_time = @end_time, @delete_batch_size = @delete_batch_size

    -- delete orphaned query text
    EXEC [core].[sp_purge_orphaned_notable_query_text] @duration = @duration, @end_time = @end_time, @delete_batch_size = @delete_batch_size
       
END
GO


--
-- This stored procedure is used to stop all purge operations in progress
--
IF (NOT OBJECT_ID(N'core.sp_stop_purge', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [core].[sp_stop_purge] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [core].[sp_stop_purge]
END
GO 

RAISERROR('Creating procedure [core].[sp_stop_purge] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [core].[sp_stop_purge]
AS
BEGIN
    INSERT INTO [core].[purge_info_internal] (stop_purge) VALUES (1)
END
GO


--
-- This function verifies if the caller matches the operator set for the snapshot 
--
IF (OBJECT_ID(N'core.fn_check_operator', 'FN') IS NULL)
BEGIN
    RAISERROR('Creating function [core].[fn_check_operator] ...', 0, 1)  WITH NOWAIT;
    DECLARE @sql nvarchar(max)
    SET @sql = 
    'CREATE FUNCTION [core].[fn_check_operator](
        @snapshot_id int
    )
    RETURNS bit
    AS
    BEGIN
        DECLARE @retval bit;
        
        DECLARE @operator sysname;
        SELECT @operator=operator FROM core.snapshots WHERE snapshot_id = @snapshot_id;
        IF (@operator = SUSER_SNAME())
            SELECT @retval = 1;
        ELSE
            SELECT @retval = 0;
        RETURN @retval;
    END;'

    EXEC sp_executesql @sql;
END
GO

-- Table wait_categories
--
-- This table contains CPU and wait categories.
--
IF (NOT OBJECT_ID(N'core.wait_types', 'U') IS NULL)
BEGIN
    -- Must drop wait_types first since it references wait_categories via FK
    RAISERROR('Dropping table [core].[wait_types] ...', 0, 1)  WITH NOWAIT;
    DROP TABLE [core].[wait_types]
END

IF (NOT OBJECT_ID(N'core.wait_categories', 'U') IS NULL)
BEGIN
    RAISERROR('Dropping table [core].[wait_categories] ...', 0, 1)  WITH NOWAIT;
    DROP TABLE [core].[wait_categories]
END

RAISERROR('Creating table [core].[wait_categories] ...', 0, 1)  WITH NOWAIT;

CREATE TABLE [core].[wait_categories](
        [category_id] [smallint] NOT NULL,
        [category_name] [nvarchar](20) NOT NULL,
        [ignore] [bit] NOT NULL, 
     CONSTRAINT [PK_categories] PRIMARY KEY CLUSTERED ([category_id] ASC)WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]

) ON [PRIMARY]
GO

-- Table wait_types
--
-- This table contains relation between cpu/wait events and cpu/wait categories for each version of SQL Server.
--
--
RAISERROR('Creating table [core].[wait_types] ...', 0, 1)  WITH NOWAIT;

CREATE TABLE [core].[wait_types](
        [category_id] [smallint] NOT NULL,
        [wait_type] [nvarchar](45) NOT NULL,
     CONSTRAINT [PK_events] PRIMARY KEY CLUSTERED ([wait_type] ASC) WITH (IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

ALTER TABLE [core].[wait_types] ADD CONSTRAINT [FK_events_categories] FOREIGN KEY([category_id])
REFERENCES [core].[wait_categories] ([category_id])
GO


IF (NOT OBJECT_ID(N'core.wait_types_categorized', 'V') IS NULL)
BEGIN
    RAISERROR('Dropping view [core].[wait_types_categorized] ...', 0, 1)  WITH NOWAIT;
    DROP VIEW [core].[wait_types_categorized];
END;
GO

RAISERROR('Creating view [core].[wait_types_categorized] ...', 0, 1)  WITH NOWAIT;
GO
CREATE VIEW [core].[wait_types_categorized]
AS
    SELECT 
        ct.category_name,
        ev.wait_type, 
        ct.category_id, 
        ct.ignore
    FROM core.wait_categories ct
    INNER JOIN core.wait_types ev ON (ev.category_id = ct.category_id)
GO

-- Insert Categories
--
SET NOCOUNT ON;
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (0, N'CPU', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (1, N'Backup', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (2, N'SQLCLR', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (3, N'Parallelism', 1);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (4, N'Latch', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (5, N'Lock', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (6, N'Network I/O', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (7, N'Buffer I/O', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (8, N'Buffer Latch', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (9, N'Memory', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (10, N'Logging', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (11, N'Compilation', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (12, N'Transaction', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (13, N'Idle', 1);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (14, N'User Waits', 1);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (15, N'Other', 0);
INSERT INTO [core].[wait_categories] ([category_id], [category_name], [ignore]) VALUES (16, N'Full Text Search', 0);

-- Insert Events
--
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (0, N'CPU');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'MISCELLANEOUS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_SCH_S');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_SCH_M');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_S');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_U');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_X');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_IS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_IU');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_IX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_SIU');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_SIX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_UIX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_BU');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RS_S');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RS_U');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RIn_NL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RIn_S');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RIn_U');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RIn_X');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RX_S');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RX_U');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (5, N'LCK_M_RX_X');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'LATCH_NL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'LATCH_KP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'LATCH_SH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'LATCH_UP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'LATCH_EX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'LATCH_DT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (8, N'PAGELATCH_NL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (8, N'PAGELATCH_KP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (8, N'PAGELATCH_SH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (8, N'PAGELATCH_UP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (8, N'PAGELATCH_EX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (8, N'PAGELATCH_DT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'PAGEIOLATCH_NL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'PAGEIOLATCH_KP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'PAGEIOLATCH_SH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'PAGEIOLATCH_UP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'PAGEIOLATCH_EX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'PAGEIOLATCH_DT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'TRAN_MARKLATCH_NL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'TRAN_MARKLATCH_KP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'TRAN_MARKLATCH_SH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'TRAN_MARKLATCH_UP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'TRAN_MARKLATCH_EX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'TRAN_MARKLATCH_DT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'LAZYWRITER_SLEEP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'IO_COMPLETION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'ASYNC_IO_COMPLETION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (6, N'ASYNC_NETWORK_IO');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SLEEP_BPOOL_FLUSH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'CHKPT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SLEEP_TASK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SLEEP_SYSTEMTASK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (9, N'RESOURCE_SEMAPHORE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'DTC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (6, N'OLEDB');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'FAILPOINT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'RESOURCE_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'ASYNC_DISKPOOL_LOCK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'THREADPOOL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DEBUG');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'REPLICA_WRITES');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'BROKER_RECEIVE_WAITFOR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DBMIRRORING_CMD');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'WAIT_FOR_RESULTS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (0, N'SOS_SCHEDULER_YIELD');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (9, N'SOS_VIRTUALMEMORY_LOW');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (9, N'SOS_RESERVEDMEMBLOCKLIST');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOS_LOCALALLOCATORLIST');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOS_CALLBACK_REMOVAL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (9, N'LOWFAIL_MEMMGR_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (1, N'BACKUP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (1, N'BACKUPBUFFER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (1, N'BACKUPIO');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (1, N'BACKUPTHREAD');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DBMIRROR_DBM_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DBMIRROR_DBM_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (6, N'DBMIRROR_SEND');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'CURSOR_ASYNC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'HTTP_ENUMERATION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (16, N'SOAP_READ');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (16, N'SOAP_WRITE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DUMP_LOG_COORDINATOR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (1, N'DISKIO_SUSPEND');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'IMPPROV_IOWAIT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QNMANAGER_ACQUIRE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DEADLOCK_TASK_SEARCH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'REPL_SCHEMA_ACCESS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'REPL_CACHE_ACCESS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SQLSORT_SORTMUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SQLSORT_NORMMUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SQLTRACE_WAIT_ENTRIES');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SQLTRACE_LOCK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SQLTRACE_BUFFER_FLUSH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SQLTRACE_SHUTDOWN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'MSQL_SYNC_PIPE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_TRACEOUT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (6, N'DTC_STATE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'FCB_REPLICA_WRITE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (7, N'FCB_REPLICA_READ');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (10, N'WRITELOG');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'HTTP_ENDPOINT_COLLCREATE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (3, N'EXCHANGE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DBTABLE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'EC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'TEMPOBJ');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'XACTLOCKINFO');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (10, N'LOGMGR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (9, N'CMEMTHREAD');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (9, N'CMEMPARTITIONED');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (3, N'CXPACKET');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (14, N'WAITFOR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'CURSOR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (3, N'EXECSYNC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_INTERNAL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_SLEEP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_WAITFORDONE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_SEMAPHORE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_RWLOCK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOSHOST_TRACELOCK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'MSQL_XP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (6, N'MSQL_DQ');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (10, N'LOGBUFFER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'TRANSACTION_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (16, N'MSSEARCH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'XACTWORKSPACE_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_JOIN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_CRST');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_SEMAPHORE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'CLR_MANUAL_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'CLR_AUTO_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_MONITOR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_RWLOCK_READER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_RWLOCK_WRITER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'SQLCLR_QUANTUM_PUNISHMENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'SQLCLR_APPDOMAIN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'SQLCLR_ASSEMBLY');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'KTM_ENLISTMENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'KTM_RECOVERY_RESOLUTION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'KTM_RECOVERY_MANAGER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'SQLCLR_DEADLOCK_DETECTION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QPJOB_WAITFOR_ABORT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QPJOB_KILL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BAD_PAGE_PROCESS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (1, N'BACKUP_OPERATOR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'PRINT_ROLLBACK_PROGRESS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'ENABLE_VERSIONING');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DISABLE_VERSIONING');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'REQUEST_DISPENSER_PAUSE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DROPTEMP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'FT_RESTART_CRAWL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'FT_RESUME_CRAWL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (10, N'LOGMGR_RESERVE_APPEND');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (10, N'LOGMGR_FLUSH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'XACT_OWN_TRANSACTION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'XACT_RECLAIM_SESSION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'DTC_WAITFOR_OUTCOME');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'DTC_RESOLVE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SEC_DROP_TEMP_KEY');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SRVPROC_SHUTDOWN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (6, N'NET_WAITFOR_PACKET');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'DTC_ABORT_REQUEST');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'DTC_TMDOWN_REQUEST');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'RECOVER_CHANGEDB');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'WORKTBL_DROP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'MIRROR_SEND_MESSAGE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SNI_HTTP_ACCEPT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SNI_HTTP_WAITFOR_0_DISCON');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (9, N'UTIL_PAGE_ALLOC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SERVER_IDLE_CHECK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (1, N'BACKUP_CLIENTLOCK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'DEADLOCK_ENUM_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'INDEX_USAGE_STATS_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (4, N'VIEW_DEFINITION_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_NOTIFICATION_MGR_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_NOTIFICATION_TABLE_MGR_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_NOTIFICATION_SUBSCRIPTION_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_NOTIFICATION_UNITTEST_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'IMP_IMPORT_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (11, N'RESOURCE_SEMAPHORE_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'IO_AUDIT_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BUILTIN_HASHKEY_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOS_PROCESS_AFFINITY_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'MSQL_XACT_MGR_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (12, N'MSQL_XACT_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QRY_MEM_GRANT_INFO_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOS_STACKSTORE_INIT_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOS_SYNC_TASK_ENQUEUE_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOS_OBJECT_STORE_DESTROY_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'EE_PMOLOCK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (11, N'RESOURCE_SEMAPHORE_QUERY_COMPILE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (11, N'RESOURCE_SEMAPHORE_SMALL_QUERY');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'ABR');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'ASSEMBLY_LOAD');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BROKER_CONNECTION_RECEIVE_TASK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BROKER_ENDPOINT_STATE_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'BROKER_EVENTHANDLER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BROKER_INIT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BROKER_MASTERSTART');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BROKER_REGISTERALLENDPOINTS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BROKER_SHUTDOWN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'BROKER_TASK_STOP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'BROKER_TRANSMITTER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'CHECK_PRINT_RECORD');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'CHECKPOINT_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_MEMORY_SPY');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLR_TASK_START');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'CLRHOST_STATE_ACCESS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DAC_INIT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DBCC_COLUMN_TRANSLATION_CACHE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DBMIRROR_EVENTS_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DBMIRROR_WORKER_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DLL_LOADING_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DUMP_LOG_COORDINATOR_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'DUMPTRIGGER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'EE_SPECPROC_MAP_INIT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'ERROR_REPORTING_MANAGER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'EXECUTION_PIPE_EVENT_INTERNAL');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (2, N'FS_GARBAGE_COLLECTOR_SHUTDOWN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'FULLTEXT GATHERER');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'GUARDIAN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'HTTP_START');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'INTERNAL_TESTING');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'KSOURCE_WAKEUP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'LOGMGR_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'ONDEMAND_TASK_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'PARALLEL_BACKUP_QUEUE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_ERRHDL_SERVICE_DONE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_EXECUTION_INDEX_SORT_EVENT_OPEN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_OPTIMIZER_PRINT_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'QUERY_REMOTE_BRICKS_DONE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'REQUEST_FOR_DEADLOCK_SEARCH');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SEQUENTIAL_GUID');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SHUTDOWN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SLEEP_DBSTARTUP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SLEEP_DCOMSTARTUP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SLEEP_MSDBSTARTUP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'SLEEP_TEMPDBSTARTUP');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SNI_CRITICAL_SECTION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SNI_LISTENER_ACCESS');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SNI_TASK_COMPLETION');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'SOS_DISPATCHER_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'TIMEPRIV_TIMEPERIOD');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'TRACEWRITE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'VIA_ACCEPT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'WAITFOR_TASKSHUTDOWN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'WAITSTAT_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'WCC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'XE_TIMER_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'XE_DISPATCHER_WAIT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (13, N'FSAGENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_TIMER_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_TIMER_TASK_DONE');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_BUFFERMGR_ALLPROCECESSED_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_BUFFERMGR_FREEBUF_EVENT');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_DISPATCHER_JOIN');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_MODULEMGR_SYNC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_OLS_LOCK');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_SERVICES_MUTEX');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_SESSION_CREATE_SYNC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_SESSION_SYNC');
INSERT INTO [core].[wait_types] ([category_id],[wait_type]) VALUES (15, N'XE_STM_CREATE')
GO

IF (NOT OBJECT_ID(N'core.fn_query_text_from_handle', 'TF') IS NULL)
BEGIN
    RAISERROR('Dropping function [core].[fn_query_text_from_handle] ...', 0, 1)  WITH NOWAIT;
    DROP FUNCTION [core].[fn_query_text_from_handle]
END
GO 

RAISERROR('Creating function [core].[fn_query_text_from_handle] ...', 0, 1)  WITH NOWAIT;
GO
CREATE FUNCTION [core].[fn_query_text_from_handle](
    @handle varbinary(64), 
    @statement_start_offset int, 
    @statement_end_offset int)
RETURNS @query_text TABLE (database_id smallint, object_id int, encrypted bit, query_text nvarchar(max))
BEGIN
      IF @handle IS NOT NULL
      BEGIN
            DECLARE @start int, @end int
            DECLARE @dbid smallint, @objectid int, @encrypted bit
            DECLARE @batch nvarchar(max), @query nvarchar(max)

            -- statement_end_offset is zero prior to beginning query execution (e.g., compilation)
            SELECT 
                  @start = ISNULL(@statement_start_offset, 0), 
                  @end = CASE WHEN @statement_end_offset is null or @statement_end_offset = 0 THEN -1
                                    ELSE @statement_end_offset 
                              END
 
            SELECT @dbid = t.dbid, 
                  @objectid = t.objectid, 
                  @encrypted = t.encrypted, 
                  @batch = t.text 
            FROM sys.dm_exec_sql_text(@handle) AS t

            SELECT @query = CASE 
                        WHEN @encrypted = CAST(1 as bit) THEN N'encrypted text' 
                        ELSE LTRIM(SUBSTRING(@batch, @start / 2 + 1, ((CASE WHEN @end = -1 THEN DATALENGTH(@batch) 
                                          ELSE @end END) - @start) / 2))
                  end

            -- Found internal queries (e.g., CREATE INDEX) with end offset of original batch that is 
            -- greater than the length of the internal query and thus returns nothing if we don''t do this
            IF DATALENGTH(@query) = 0
            BEGIN
                  SELECT @query = @batch
            END

            INSERT INTO @query_text (database_id, object_id, encrypted, query_text) 
            VALUES (@dbid, @objectid, @encrypted, @query)
      END
      RETURN
END
GO


-- Table performance_counter_report_groups
--
-- This table lists the counters that are used by each report.  Referenced in [snapshots].[rpt_generic_perfmon]. 
--
IF (NOT OBJECT_ID(N'[core].[performance_counter_report_group_items]', 'U') IS NULL)
BEGIN
    -- Must drop wait_types first since it references wait_categories via FK
    RAISERROR('Dropping table [core].[performance_counter_report_group_items] ...', 0, 1)  WITH NOWAIT;
    DROP TABLE [core].[performance_counter_report_group_items]
END
GO

RAISERROR('Creating table [core].[performance_counter_report_group_items] ...', 0, 1)  WITH NOWAIT;
CREATE TABLE [core].[performance_counter_report_group_items](
        [counter_group_item_id] int IDENTITY NOT NULL PRIMARY KEY CLUSTERED, 
        [counter_group_id] nvarchar(128) NOT NULL,          -- e.g. report name
        [counter_subgroup_id] nvarchar(128) NOT NULL,       -- e.g. chart name, used by the chart to filter out its rows from the larger resultset
        [series_name] nvarchar(512) NOT NULL,               -- data series label used for output
        [object_name] nvarchar(2048) NOT NULL,              -- perfmon object name
        [object_name_wildcards] bit NOT NULL,               -- 1 if wildcard expansion is needed for the perfmon object name (e.g. '%SQL%:General Statistics'), 0 otherwise
        [counter_name] nvarchar(2048) NOT NULL,             -- perfmon counter name
        [instance_name] nvarchar(2048) NULL,                -- perfmon instance name(s) to include (evaluated with LIKE)
        [not_instance_name] nvarchar(2048) NULL,            -- perfmon instance name(s) to omit (evaluated with LIKE, use NULL to skip this criteria)
        [multiply_by] numeric(28,10) NOT NULL,              -- value to multiple the counter  by on output (for unit conversion) 
        [divide_by_cpu_count] bit NOT NULL                  -- 1 if counter should be divided by the machine's CPU count (e.g. "Process(abc)\% Processor Time", 0 otherwise
) 
GO

-- Insert perfmon counter list for each report group 
--
DECLARE @CONVERT_BYTES_TO_MB numeric(28,10)
DECLARE @CONVERT_KB_TO_MB numeric(28,10)
SET @CONVERT_BYTES_TO_MB = 1.0 / (1024*1024)
SET @CONVERT_KB_TO_MB = 1.0 / (1024);

INSERT INTO [core].[performance_counter_report_group_items] ([counter_group_id],[counter_subgroup_id],[series_name],
    [object_name],[object_name_wildcards],[counter_name],[instance_name],[not_instance_name],[multiply_by],[divide_by_cpu_count]) 
VALUES 
    ('ServerActivity', 'memoryUsage', 'System', 'Process', 0, 'Working Set', '_Total', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('ServerActivity', 'memoryUsage', 'SQL Server', 'Process', 0, 'Working Set', '$(TARGETPROCESS)', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('ServerActivity', 'IOUsage', 'System', 'Process', 0, 'IO Read Bytes/sec', '_Total', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('ServerActivity', 'IOUsage', 'System', 'Process', 0, 'IO Write Bytes/sec', '_Total', NULL, @CONVERT_BYTES_TO_MB, 0 ),               -- changed to fix Bug # 234905 
    ('ServerActivity', 'IOUsage', 'SQL Server', 'Process', 0, 'IO Read Bytes/sec', '$(TARGETPROCESS)', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('ServerActivity', 'IOUsage', 'SQL Server', 'Process', 0, 'IO Write Bytes/sec', '$(TARGETPROCESS)', NULL, @CONVERT_BYTES_TO_MB, 0 ), -- changed to fix Bug # 234905 
    ('ServerActivity', 'cpuUsage', 'System', 'Processor', 0, '% Processor Time', '_Total', NULL, 1.0, 0 ), 
    ('ServerActivity', 'cpuUsage', 'SQL Server', 'Process', 0, '% Processor Time', '$(TARGETPROCESS)', NULL, 1.0, 1 ), 
    ('ServerActivity', 'networkUsage', 'System', 'Network Interface', 0, 'Bytes Total/sec', '%', NULL, 1.0, 0 ), 
    ('ServerActivity', 'sqlActivity', 'Logins/sec', '%SQL%:General Statistics', 1, 'Logins/sec', '', NULL, 1.0, 0 ), 
    ('ServerActivity', 'sqlActivity', 'Logouts/sec', '%SQL%:General Statistics', 1, 'Logouts/sec', '', NULL, 1.0, 0 ), 
    ('ServerActivity', 'sqlActivity', 'Transactions', '%SQL%:General Statistics', 1, 'Transactions', '', NULL, 1.0, 0 ), 
    ('ServerActivity', 'sqlActivity', 'User Connections', '%SQL%:General Statistics', 1, 'User Connections', '', NULL, 1.0, 0 ), 
    ('ServerActivity', 'sqlActivity', 'Batch Requests/sec', '%SQL%:SQL Statistics', 1, 'Batch Requests/sec', '', NULL, 1.0, 0 ), 
    ('ServerActivity', 'sqlActivity', 'SQL Compilations/sec', '%SQL%:SQL Statistics', 1, 'SQL Compilations/sec', '', NULL, 1.0, 0 ), 
    ('ServerActivity', 'sqlActivity', 'SQL Re-Compilations/sec', '%SQL%:SQL Statistics', 1, 'SQL Re-Compilations/sec', '', NULL, 1.0, 0 ), 

    ('SystemDiskUsage', 'diskSpeed', '[COUNTER_INSTANCE]', 'LogicalDisk', 0, 'Avg. Disk sec/Transfer', '%', NULL, 1.0, 0 ), 
    ('SystemDiskUsage', 'diskQueues', '[COUNTER_INSTANCE]', 'LogicalDisk', 0, 'Avg. Disk Queue Length', '%', NULL, 1.0, 0 ), 
    ('SystemDiskUsage', 'diskRates', '[COUNTER_INSTANCE]', 'LogicalDisk', 0, 'Disk Bytes/sec', '%', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('SystemDiskUsage', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'IO Read Bytes/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 
    ('SystemDiskUsage', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'IO Write Bytes/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 
    ('SystemDiskUsage', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'Disk Reads/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 
    ('SystemDiskUsage', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'Disk Writes/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 
    ('SystemDiskUsage', 'diskSpeed', '[COUNTER_INSTANCE]', 'LogicalDisk', 0, 'Avg. Disk sec/Transfer', '%', NULL, 1.0, 0 ), 
    ('SystemDiskUsage', 'diskQueues', '[COUNTER_INSTANCE]', 'LogicalDisk', 0, 'Avg. Disk Queue Length', '%', NULL, 1.0, 0 ), 
    ('SystemDiskUsage', 'diskRates', '[COUNTER_INSTANCE]', 'LogicalDisk', 0, 'Disk Bytes/sec', '%', NULL, @CONVERT_BYTES_TO_MB, 0 ), 

    ('SystemDiskUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'IO Read Bytes/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 
    ('SystemDiskUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'IO Write Bytes/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 
    ('SystemDiskUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'Disk Reads/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 
    ('SystemDiskUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'Disk Writes/sec', '%', '$(TARGETPROCESS)', 1.0, 0 ), 

    ('SqlMemoryUsage', 'memoryRates', 'Page life expectancy', '%SQL%:Buffer Manager', 1, 'Page life expectancy', '', NULL, 1.0, 0 ), 

    ('SystemMemoryUsage', 'memoryUsage', 'Total Working Set', 'Process', 0, 'Working Set', '_Total', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('SystemMemoryUsage', 'memoryUsage', 'Cache Bytes', 'Memory', 0, 'Cache Bytes', '', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('SystemMemoryUsage', 'memoryUsage', 'Pool Nonpaged Bytes', 'Memory', 0, 'Pool Nonpaged Bytes', '', NULL, @CONVERT_BYTES_TO_MB, 0 ), 
    ('SystemMemoryUsage', 'memoryRates', 'Page Reads/sec', 'Memory', 0, 'Page Reads/sec', '', NULL, 1.0, 0 ), 
    ('SystemMemoryUsage', 'memoryRates', 'Page Writes/sec', 'Memory', 0, 'Page Writes/sec', '', NULL, 1.0, 0 ), 

    ('SystemMemoryUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'Working Set', '%', '$(TARGETPROCESS)', @CONVERT_BYTES_TO_MB, 0 ), 
    ('SystemMemoryUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'Private Bytes', '%', '$(TARGETPROCESS)', @CONVERT_BYTES_TO_MB, 0 ), 

    ('SystemCpuUsage', 'cpuUsage', 'CPU [COUNTER_INSTANCE]', 'Processor', 0, '% Processor Time', '%', NULL, 1.0, 0 ), 

    ('SystemCpuUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, '% Processor Time', '%', '$(TARGETPROCESS)', 1.0, 1 ), 
    ('SystemCpuUsagePivot', 'processStats', '[COUNTER_INSTANCE]', 'Process', 0, 'Thread Count', '%', '$(TARGETPROCESS)', 1.0, 0 ), 

    ('SqlActivity', 'sessionsAndConnections', 'User Connections', '%SQL%:General Statistics', 1, 'User Connections', '', NULL, 1.0, 0 ), 
    ('SqlActivity', 'sessionsAndConnections', 'Active Transactions', '%SQL%:Databases', 1, 'Active Transactions', '_Total', NULL, 1.0, 0 ), 
    ('SqlActivity', 'sessionsAndConnections', 'Active requests', '%SQL%:Workload Group Stats', 1, 'Active requests', '%', NULL, 1.0, 0 ), 
    ('SqlActivity', 'requestsAndCompilations', 'Batch Requests/sec', '%SQL%:SQL Statistics', 1, 'Batch Requests/sec', '', NULL, 1.0, 0 ), 
    ('SqlActivity', 'requestsAndCompilations', 'SQL Compilations/sec', '%SQL%:SQL Statistics', 1, 'SQL Compilations/sec', '', NULL, 1.0, 0 ), 
    ('SqlActivity', 'requestsAndCompilations', 'SQL Re-Compilations/sec', '%SQL%:SQL Statistics', 1, 'SQL Re-Compilations/sec', '', NULL, 1.0, 0 ), 
    ('SqlActivity', 'planCache', '[COUNTER_INSTANCE]', '%SQL%:Plan Cache', 1, 'Cache Hit Ratio', '%', NULL, 1.0, 0 ), 
    ('SqlActivity', 'tempDb', 'Free Space in tempdb (MB)', '%SQL%:Transactions', 1, 'Free Space in tempdb (KB)', '', NULL, @CONVERT_KB_TO_MB, 0 ), 
    ('SqlActivity', 'tempDb', 'Active Temp Tables', '%SQL%:General Statistics', 1, 'Active Temp Tables', '', NULL, 1.0, 0 ), 
    ('SqlActivity', 'tempDb', 'Transactions/sec', '%SQL%:Databases', 1, 'Transactions/sec', 'tempdb', NULL, 1.0, 0 )
GO


/**********************************************************************/
/* SNAPSHOTS SCHEMA                                                   */
/**********************************************************************/
RAISERROR('', 0, 1)  WITH NOWAIT;
RAISERROR('Create schema snapshots...', 0, 1)  WITH NOWAIT;
RAISERROR('', 0, 1)  WITH NOWAIT;
GO
IF (SCHEMA_ID('snapshots') IS NULL)
BEGIN
    -- Dynamic SQL here because we must skip the creation attempt if the schema already 
    -- exists, but we can't use a simple IF check because CREATE SCHEMA must be the first 
    -- statement in its batch. 
    DECLARE @sql nvarchar(128)
    SET @sql = 'CREATE SCHEMA snapshots'
    EXEC sp_executesql @sql
END
GO

--
-- PERFORMANCE COUNTERS COLLECTOR TYPE SUPPORT
--

-- This table holds information about instances of perf counters collected (their path and type)
IF (OBJECT_ID(N'[snapshots].[performance_counter_instances]', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[performance_counter_instances]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[performance_counter_instances](
        [performance_counter_id]            int IDENTITY NOT NULL,
        [path]                                nvarchar(2048) NOT NULL,
        [object_name]                        nvarchar(2048) NOT NULL,
        [counter_name]                        nvarchar(2048) NOT NULL,
        [instance_name]                        nvarchar(2048) NULL,
        [counter_type]                        int NOT NULL,

        -- This constraint creation generates a warning. We will deal with the warning when fix for #130259 is provided
        CONSTRAINT [UN_performance_counter_path] UNIQUE
        (
            [path]
        ) WITH (IGNORE_DUP_KEY = ON)
    )
END;
GO

-- performance_counter_instances.PK_performance_counter_instances
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_performance_counter_instances', 'performance_counter_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'performance_counter_instances', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_performance_counter_instances', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- performance_counter_instances.IDX_performance_counter_instances1
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_performance_counter_instances1', 'object_name', 0, 0), 
    ('IDX_performance_counter_instances1', 'counter_name', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'performance_counter_instances', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_performance_counter_instances1', 
    @ignore_dup_key = 0, @clustered = 0;
GO


-- This table holds information about actual values collected for perf counters (formatted, raw values, and time when counter was collected)
IF (OBJECT_ID(N'[snapshots].[performance_counter_values]', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[performance_counter_values]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[performance_counter_values](
        [performance_counter_instance_id]    int NOT NULL,
        [snapshot_id]                        int NOT NULL,                    
        [collection_time]                    datetimeoffset(7) NOT NULL,
        [formatted_value]                    float NOT NULL,
        [raw_value_first]                    bigint NOT NULL,
        [raw_value_second]                    bigint NULL,
    ) ON [PRIMARY];
    
    ALTER TABLE [snapshots].[performance_counter_values]
    ADD CONSTRAINT [CHK_performance_counter_values_check_operator] CHECK (core.fn_check_operator(snapshot_id) = 1)
END;
GO

-- performance_counter_values.PK_performance_counter_values
-- 
-- Ignore duplicate keys on primary key and not fail uploads because of that.
-- This is needed because in some cases we may have collection item that
-- collects the same counter twice, for valid reasons. To not force users
-- to provide some exclusion lists, we will ignore duplicates. 
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_performance_counter_values', 'performance_counter_instance_id', 0, 0), 
    ('PK_performance_counter_values', 'snapshot_id', 0, 0), 
    ('PK_performance_counter_values', 'collection_time', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'performance_counter_values', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_performance_counter_values', 
    @ignore_dup_key = 1, @clustered = 1;
GO

-- performance_counter_values.IDX_performance_counter_values1
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_performance_counter_values1', 'snapshot_id', 0, 0), 
    ('IDX_performance_counter_values1', 'performance_counter_instance_id', 0, 0), 
    ('IDX_performance_counter_values1', 'collection_time', 0, 0), 
    ('IDX_performance_counter_values1', 'formatted_value', 1, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'performance_counter_values', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_performance_counter_values1', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- performance_counter_values.FK_performance_counter_values_snapshot_id
IF OBJECT_ID ('snapshots.FK_performance_counter_values_snapshot_id', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_performance_counter_values_snapshot_id] on snapshots.performance_counter_values ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[performance_counter_values]
        ADD CONSTRAINT [FK_performance_counter_values_snapshot_id] FOREIGN KEY(snapshot_id)
        REFERENCES [core].[snapshots_internal] (snapshot_id) ON DELETE CASCADE
END;
GO

-- performance_counter_values.FK_performance_counter_values_instance_id
IF OBJECT_ID ('snapshots.FK_performance_counter_values_snapshot_id', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_performance_counter_values_instance_id] on snapshots.performance_counter_values ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[performance_counter_values]
        ADD CONSTRAINT [FK_performance_counter_values_instance_id] FOREIGN KEY(performance_counter_instance_id)
        REFERENCES [snapshots].[performance_counter_instances] (performance_counter_id) ON DELETE CASCADE
END;
GO


-- This view shows user friendly information about performance counters
IF (OBJECT_ID(N'[snapshots].[performance_counters]', 'V') IS NOT NULL)
BEGIN
    RAISERROR('Dropping view [snapshots].[performance_counters]...', 0, 1)  WITH NOWAIT;
    DROP VIEW [snapshots].[performance_counters]
END

RAISERROR('Creating view [snapshots].[performance_counters]...', 0, 1)  WITH NOWAIT;
GO
CREATE VIEW [snapshots].[performance_counters]
AS
SELECT     
    pci.performance_counter_id, 
    pcv.snapshot_id AS snapshot_id, 
    pcv.collection_time AS collection_time,
    pci.path AS path, 
    pci.object_name AS performance_object_name,
    pci.counter_name AS performance_counter_name,
    pci.instance_name AS performance_instance_name,
    pcv.formatted_value AS formatted_value, 
    pcv.raw_value_first, 
    pcv.raw_value_second
FROM
    snapshots.performance_counter_instances pci
    INNER JOIN snapshots.performance_counter_values pcv ON pci.performance_counter_id = pcv.performance_counter_instance_id
GO

-- Function to obtain user friendly formatted perf counters values for a given instance and time window
IF (OBJECT_ID(N'snapshots.fn_get_performance_counters', 'IF') IS NOT NULL)
BEGIN
    RAISERROR('Dropping function [snapshots].[fn_get_performance_counters] ...', 0, 1)  WITH NOWAIT;
    DROP FUNCTION [snapshots].[fn_get_performance_counters]
END
GO 

RAISERROR('Creating function [snapshots].[fn_get_performance_counters] ...', 0, 1)  WITH NOWAIT;
GO
CREATE FUNCTION [snapshots].[fn_get_performance_counters]  
(
    @instance_name       sysname,
    @start_time          datetimeoffset(7) = NULL,
    @end_time            datetimeoffset(7) = NULL
) 
RETURNS TABLE
AS
RETURN
(
    SELECT 
        pc.performance_counter_id AS performance_counter_id,
        pc.collection_time AS collection_time,
        pc.path AS path,
        pc.performance_object_name AS performance_object_name,
        pc.performance_counter_name AS performance_counter_name,
        pc.performance_instance_name AS performance_instance_name,
        pc.formatted_value AS formatted_value
    FROM [snapshots].[performance_counters] as pc
    JOIN [core].[snapshots] s on s.snapshot_id = pc.snapshot_id
    WHERE
        @instance_name = s.instance_name AND
        ISNULL(@start_time,CAST (0 AS DATETIME)) <= pc.collection_time AND
        ISNULL(@end_time,GETDATE()) >= pc.collection_time
)
GO

-- Function to obtain user friendly statistics about a perf counter for a given instance, counter path and time window
IF (OBJECT_ID(N'snapshots.fn_get_performance_counter_statistics', 'IF') IS NOT NULL)
BEGIN
    RAISERROR('Dropping function [snapshots].[fn_get_performance_counter_statistics] ...', 0, 1)  WITH NOWAIT;
    DROP FUNCTION [snapshots].[fn_get_performance_counter_statistics]
END
GO 

RAISERROR('Creating function [snapshots].[fn_get_performance_counter_statistics] ...', 0, 1)  WITH NOWAIT;
GO
CREATE FUNCTION [snapshots].[fn_get_performance_counter_statistics]  
(
    @instance_name       sysname,
    @path_pattern         nvarchar(2048),
    @start_time          datetimeoffset(7) = NULL,
    @end_time            datetimeoffset(7) = NULL
) 
RETURNS TABLE
AS
RETURN
(
    SELECT 
        pc.path                        as path,
        MIN(pc.formatted_value)        as minimum_value,
        MAX(pc.formatted_value)        as maximum_value,
        AVG(pc.formatted_value)        as average_value,
        STDEV(pc.formatted_value)    as standard_deviation,
        VAR(pc.formatted_value)        as statistical_variance
    FROM [snapshots].[performance_counters] as pc
    JOIN [core].[snapshots] s on s.snapshot_id = pc.snapshot_id
    WHERE
        s.instance_name = @instance_name AND
        pc.path LIKE @path_pattern AND
        pc.collection_time >= @start_time AND
        pc.collection_time <= @end_time
    GROUP BY pc.path
)
GO



--
-- SQLTRACE COLLECTOR TYPE SUPPORT
--

-- This table holds information about captured traces
IF (OBJECT_ID(N'[snapshots].[trace_info]', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[trace_info]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[trace_info] (
        trace_info_id                 int IDENTITY NOT NULL,
        source_id                     int NOT NULL,      -- id of source_info record corresponding to source of this data
        collection_item_id            int NOT NULL,      -- id of the trace on the target instance
        last_snapshot_id              int NULL,          -- references core.snapshots table. Identifies the most recent snapshot id that was generated for this trace
        start_time                    datetime NULL,     -- time when this trace was started on the target machine
        last_event_sequence           bigint NULL,       -- event sequence of the most recent event for this trace was captured. 
        is_running                    bit NULL,          -- 0 - trace is stopped, 1 - trace is running
        event_count                   bigint NULL,       -- total number of events captured by this trace
        dropped_event_count           int NULL,          -- total number of events dropped by this trace.
    );
END;
GO

-- trace_info.PK_trace_info
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_trace_info', 'trace_info_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'trace_info', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_trace_info', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- trace_info.FK_trace_info_last_snapshot_id
IF OBJECT_ID ('snapshots.FK_trace_info_last_snapshot_id', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_trace_info_last_snapshot_id] on snapshots.trace_info ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[trace_info]
        ADD CONSTRAINT [FK_trace_info_last_snapshot_id] FOREIGN KEY(last_snapshot_id)
        REFERENCES [core].[snapshots_internal] (snapshot_id) ON DELETE CASCADE
END;
GO

-- trace_info.FK_trace_info_last_snapshot_id
IF OBJECT_ID ('snapshots.FK_trace_info_source_id', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_trace_info_source_id] on snapshots.trace_info ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[trace_info]
        ADD CONSTRAINT [FK_trace_info_source_id] FOREIGN KEY(source_id)
        REFERENCES [core].[source_info_internal] (source_id)
END;
GO


-- This table holds all trace data 
IF (OBJECT_ID(N'[snapshots].[trace_data]', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[trace_data]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[trace_data] (
        trace_info_id                   int NOT NULL,    -- references id of the trace from trace_info table
        snapshot_id                     int NOT NULL,    -- references snapshot_id from core.snapshots
        -- Following all all trace columns that match the list from sys.trace_columns system view
        TextData                        nvarchar(max) NULL,
        BinaryData                      varbinary(max) NULL,
        DatabaseID                      int NULL,
        TransactionID                   bigint NULL,
        LineNumber                      int NULL,
        NTUserName                      nvarchar(256) NULL,
        NTDomainName                    nvarchar(256) NULL,
        HostName                        nvarchar(256) NULL,
        ClientProcessID                 int NULL,
        ApplicationName                 nvarchar(256) NULL,
        LoginName                       nvarchar(256) NULL,
        SPID                            int NULL,
        Duration                        bigint NULL,
        StartTime                       datetimeoffset(7) NULL,
        EndTime                         datetimeoffset(7) NULL,
        Reads                           bigint NULL,
        Writes                          bigint NULL,
        CPU                             int NULL,
        Permissions                     bigint NULL,
        Severity                        int NULL,
        EventSubClass                   int NULL,
        ObjectID                        int NULL,
        Success                         int NULL,
        IndexID                         int NULL,
        IntegerData                     int NULL,
        ServerName                      nvarchar(256) NULL,
        EventClass                      int NULL,
        ObjectType                      int NULL,
        NestLevel                       int NULL,
        State                           int NULL,
        Error                           int NULL,
        Mode                            int NULL,
        Handle                          int NULL,
        ObjectName                      nvarchar(256) NULL,
        DatabaseName                    nvarchar(256) NULL,
        FileName                        nvarchar(256) NULL,
        OwnerName                       nvarchar(256) NULL,
        RoleName                        nvarchar(256) NULL,
        TargetUserName                  nvarchar(256) NULL,
        DBUserName                      nvarchar(256) NULL,
        LoginSid                        varbinary(max) NULL,
        TargetLoginName                 nvarchar(256) NULL,
        TargetLoginSid                  varbinary(max) NULL,
        ColumnPermissions               int NULL,
        LinkedServerName                nvarchar(256) NULL,
        ProviderName                    nvarchar(256) NULL,
        MethodName                      nvarchar(256) NULL,
        RowCounts                       bigint NULL,
        RequestID                       int NULL,
        XactSequence                    bigint NULL,
        EventSequence                   bigint NOT NULL,
        BigintData1                     bigint NULL,
        BigintData2                     bigint NULL,
        GUID                            uniqueidentifier NULL,
        IntegerData2                    int NULL,
        ObjectID2                       bigint NULL,
        Type                            int NULL,
        OwnerID                         int NULL,
        ParentName                      nvarchar(256) NULL,
        IsSystem                        int NULL,
        Offset                          int NULL,
        SourceDatabaseID                int NULL,
        SqlHandle                       varbinary(64) NULL,
        SessionLoginName                nvarchar(256) NULL,
        PlanHandle                      varbinary(64) NULL, 
        GroupID                         int NULL

    )  ON [PRIMARY];

    ALTER TABLE [snapshots].[trace_data]
        ADD CONSTRAINT [CHK_trace_data_check_operator] CHECK (core.fn_check_operator(snapshot_id) = 1);
END;
GO

-- trace_data.IDX_trace_data_EventSequence
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_trace_data_EventSequence', 'snapshot_id', 0, 0), 
    ('IDX_trace_data_EventSequence', 'EventSequence', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'trace_data', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_trace_data_EventSequence', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- trace_data.IDX_trace_data_trace_info_id
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_trace_data_trace_info_id', 'trace_info_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'trace_data', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_trace_data_trace_info_id', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- trace_data.IDX_trace_data_EventSequence
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_trace_data_StartTime_EventClass', 'StartTime', 0, 0), 
    ('IDX_trace_data_StartTime_EventClass', 'EventClass', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'trace_data', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_trace_data_StartTime_EventClass', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- trace_data.FK_trace_data_trace_info_id
IF OBJECT_ID ('snapshots.FK_trace_data_trace_info_id', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_trace_data_trace_info_id] on snapshots.trace_data ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[trace_data]
        ADD CONSTRAINT [FK_trace_data_trace_info_id] FOREIGN KEY(trace_info_id)
        REFERENCES [snapshots].[trace_info] (trace_info_id);
END;
GO

-- trace_data.FK_trace_data_snapshot_id
IF OBJECT_ID ('snapshots.FK_trace_data_snapshot_id', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_trace_data_snapshot_id] on snapshots.trace_data ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[trace_data]
        ADD CONSTRAINT [FK_trace_data_snapshot_id] FOREIGN KEY(snapshot_id)
        REFERENCES [core].[snapshots_internal] (snapshot_id) ON DELETE CASCADE;
END;
GO


-- This stored proc inserts a new row int the snapshots.trace_info table
IF (NOT OBJECT_ID(N'snapshots.sp_trace_get_info', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[sp_trace_get_info] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[sp_trace_get_info]
END
GO 

RAISERROR('Creating procedure [snapshots].[sp_trace_get_info] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[sp_trace_get_info]
    @source_id           int,               -- references source of data from core.source_info_internal
    @collection_item_id  int,               -- idenitfies the collection item id within the source info
    @start_time          datetime,          -- time when the trace has started
    @last_event_sequence bigint OUTPUT,     -- returns the event sequence number for last trace event uploaded, or 0
    @trace_info_id       int OUTPUT         -- returns id of trace_info record
AS
BEGIN
    SET NOCOUNT ON;

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_writer'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_writer');
        RETURN(1); -- Failure
    END;

    -- Parameters check - mandatory parameters
    IF (@source_id IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@source_id')
        RETURN(1) -- Failure
    END;

    IF (@collection_item_id IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@collection_item_id')
        RETURN(1) -- Failure
    END;

    IF (@start_time IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@start_time')
        RETURN(1) -- Failure
    END;

    SELECT 
        @trace_info_id = trace_info_id,
        @last_event_sequence = last_event_sequence 
    FROM snapshots.trace_info ti
    WHERE 
        ti.source_id = @source_id
        AND ti.collection_item_id = @collection_item_id
        AND ti.is_running = 1
        AND ti.start_time = @start_time;

    IF (@trace_info_id IS NULL)
    BEGIN
        SELECT @last_event_sequence = 0;

        -- Insert new record
        INSERT INTO [snapshots].[trace_info]
        (
            source_id,
            collection_item_id,
            start_time,
            is_running,
            last_event_sequence
        )
        VALUES
        (
            @source_id,
            @collection_item_id,
            @start_time,
            1,
            @last_event_sequence
        );
        SELECT @trace_info_id = SCOPE_IDENTITY();
    END;

    RETURN (0);
END
GO


-- This stored proc updates a row in the snapshots.trace_info table
IF (NOT OBJECT_ID(N'snapshots.sp_trace_update_info', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[sp_trace_update_info] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[sp_trace_update_info]
END
GO 

RAISERROR('Creating procedure [snapshots].[sp_trace_update_info] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[sp_trace_update_info]
    @trace_info_id       int,
    @snapshot_id         int,
    @last_event_sequence bigint,
    @is_running          bit,
    @event_count         bigint,
    @dropped_event_count int
AS
BEGIN
    SET NOCOUNT ON;

    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_writer'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_writer');
        RETURN(1); -- Failure
    END;

    -- Parameters check - mandatory parameters
    IF (@trace_info_id IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@trace_info_id')
        RETURN(1) -- Failure
    END;

    IF NOT EXISTS (SELECT trace_info_id from snapshots.trace_info where trace_info_id = @trace_info_id)
    BEGIN
        DECLARE @trace_info_id_as_char NVARCHAR(10)
        SELECT @trace_info_id_as_char = CONVERT(NVARCHAR(36), @trace_info_id)
        RAISERROR(14679, -1, -1, N'@trace_info_id', @trace_info_id_as_char)
        RETURN(1) -- Failure
    END;

    IF (@snapshot_id IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@snapshot_id')
        RETURN(1) -- Failure
    END;

    IF NOT EXISTS (SELECT snapshot_id from core.snapshots where snapshot_id = @snapshot_id)
    BEGIN
        DECLARE @snapshot_id_as_char NVARCHAR(36)
        SELECT @snapshot_id_as_char = CONVERT(NVARCHAR(36), @snapshot_id)
        RAISERROR(14679, -1, -1, N'@snapshot_id', @snapshot_id_as_char)
        RETURN(1) -- Failure
    END;

    IF (@last_event_sequence IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@last_event_sequence')
        RETURN(1) -- Failure
    END;

    IF (@is_running IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@is_running')
        RETURN(1) -- Failure
    END;

    IF (@event_count IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@event_count')
        RETURN(1) -- Failure
    END;

    IF (@dropped_event_count IS NULL)
    BEGIN
        RAISERROR(14200, -1, -1, '@dropped_event_count')
        RETURN(1) -- Failure
    END;

    -- Update existing record
    UPDATE [snapshots].[trace_info]
    SET
        last_snapshot_id = @snapshot_id,
        last_event_sequence = @last_event_sequence,
        is_running = @is_running,
        event_count = ISNULL(event_count,0) + @event_count,
        dropped_event_count = @dropped_event_count
    WHERE
        trace_info_id = @trace_info_id;

    RETURN(0);
END
GO


-- This function returns all data captured for the specified trace
IF (NOT OBJECT_ID(N'snapshots.fn_trace_gettable', 'IF') IS NULL)
BEGIN
    RAISERROR('Dropping function [snapshots].[fn_trace_gettable] ...', 0, 1)  WITH NOWAIT;
    DROP FUNCTION [snapshots].[fn_trace_gettable]
END
GO 

RAISERROR('Creating function [snapshots].[fn_trace_gettable] ...', 0, 1)  WITH NOWAIT;
GO
CREATE FUNCTION [snapshots].[fn_trace_gettable]  
(
    @trace_info_id       int,
    @start_time          datetimeoffset(7) = NULL,
    @end_time            datetimeoffset(7) = NULL
) 
RETURNS TABLE
AS
RETURN
(
    SELECT 
        TextData,
        BinaryData,
        DatabaseID,
        TransactionID,
        LineNumber,
        NTUserName,
        NTDomainName,
        HostName,
        ClientProcessID,
        ApplicationName,
        LoginName,
        SPID,
        Duration,
        StartTime,
        EndTime,
        Reads,
        Writes,
        CPU,
        Permissions,
        Severity,
        EventSubClass,
        ObjectID,
        Success,
        IndexID,
        IntegerData,
        ServerName,
        EventClass,
        ObjectType,
        NestLevel,
        State,
        Error,
        Mode,
        Handle,
        ObjectName,
        DatabaseName,
        FileName,
        OwnerName,
        RoleName,
        TargetUserName,
        DBUserName,
        LoginSid,
        TargetLoginName,
        TargetLoginSid,
        ColumnPermissions,
        LinkedServerName,
        ProviderName,
        MethodName,
        RowCounts,
        RequestID,
        XactSequence,
        EventSequence,
        BigintData1,
        BigintData2,
        GUID,
        IntegerData2,
        ObjectID2,
        Type,
        OwnerID,
        ParentName,
        IsSystem,
        Offset,
        SourceDatabaseID,
        SqlHandle,
        SessionLoginName,
        PlanHandle
    FROM snapshots.trace_data
    WHERE
        trace_info_id = @trace_info_id
        AND StartTime >= ISNULL(@start_time, '1753-01-01')
        AND StartTime <= ISNULL(@end_time, '9999-12-31')
)
GO

--
-- GENERAL TABLES FOR SYSTEM COLLECTION SETS
--

-- Association of batch text with sql_handle and object_name
--
IF (OBJECT_ID(N'snapshots.notable_query_text', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[notable_query_text]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[notable_query_text] (
        [sql_handle]        varbinary(64) NOT NULL,
        [database_id]       smallint      NULL,
        [object_id]         int           NULL,
        [object_name]       nvarchar(128) NULL,
        [sql_text]          nvarchar(max) NULL,
        [source_id]         int           NOT NULL
    ) ON [PRIMARY]
END
GO

-- notable_query_text.PK_notable_query_text
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_notable_query_text', 'source_id', 0, 0), 
    ('PK_notable_query_text', 'sql_handle', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'notable_query_text', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_notable_query_text', 
    @ignore_dup_key = 1, @clustered = 1; -- IGNORE_DUP_KEY
GO

-- notable_query_text.FK_distinct_query_to_handle_notable_query_text
IF OBJECT_ID ('snapshots.FK_notable_query_text_source_info_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_distinct_query_to_handle_notable_query_text] on snapshots.notable_query_text ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[notable_query_text] ADD CONSTRAINT [FK_notable_query_text_source_info_internal] FOREIGN KEY([source_id])
        REFERENCES [core].[source_info_internal] (source_id)
        ON DELETE CASCADE
END;


-- Association of batch plan with sql_handle and plan_handle and object_name
--
IF (OBJECT_ID(N'snapshots.notable_query_plan', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[notable_query_plan]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[notable_query_plan] (
        [sql_handle]                varbinary(64)   NOT NULL,
        [plan_handle]               varbinary(64)   NOT NULL,
        [statement_start_offset]    int             NOT NULL,
        [statement_end_offset]      int             NOT NULL,
        [plan_generation_num]       bigint          NOT NULL,
        [creation_time]             datetimeoffset(7)  NOT NULL,
        [database_id]               smallint        NULL,
        [object_id]                 int             NULL,
        [object_name]               nvarchar(128)   NULL,
        [query_plan]                nvarchar(max)   NULL,
        [source_id]                 int             NOT NULL,
    ) ON [PRIMARY]
END;

-- notable_query_plan.PK_notable_query_plan
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_notable_query_plan', 'source_id', 0, 0), 
    ('PK_notable_query_plan', 'sql_handle', 0, 0), 
    ('PK_notable_query_plan', 'plan_handle', 0, 0), 
    ('PK_notable_query_plan', 'statement_start_offset', 0, 0), 
    ('PK_notable_query_plan', 'statement_end_offset', 0, 0), 
    ('PK_notable_query_plan', 'creation_time', 0, 0), 
    ('PK_notable_query_plan', 'plan_generation_num', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'notable_query_plan', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_notable_query_plan', 
    @ignore_dup_key = 1, @clustered = 1;
GO

-- notable_query_plan.IDX_notable_query_plan_plan_handle
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_notable_query_plan_plan_handle', 'source_id', 0, 0), 
    ('IDX_notable_query_plan_plan_handle', 'plan_handle', 0, 0), 
    ('IDX_notable_query_plan_plan_handle', 'statement_start_offset', 0, 0), 
    ('IDX_notable_query_plan_plan_handle', 'statement_end_offset', 0, 0), 
    ('IDX_notable_query_plan_plan_handle', 'creation_time', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'notable_query_plan', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_notable_query_plan_plan_handle', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- notable_query_plan.FK_notable_query_plan_source_info_internal
IF OBJECT_ID ('snapshots.FK_notable_query_plan_source_info_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_notable_query_plan_source_info_internal] on snapshots.notable_query_plan ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[notable_query_plan] ADD CONSTRAINT [FK_notable_query_plan_source_info_internal] FOREIGN KEY([source_id])
        REFERENCES [core].[source_info_internal] (source_id)
        ON DELETE CASCADE;
END;
GO


IF (OBJECT_ID(N'snapshots.distinct_queries', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[distinct_queries]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[distinct_queries] (
        [distinct_query_hash]   bigint          NOT NULL,
        [distinct_sql_text]     nvarchar(512)   NOT NULL,
        [source_id]             int             NOT NULL,
    );
END;
GO

-- distinct_queries.PK_distinct_queries
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_distinct_queries', 'source_id', 0, 0), 
    ('PK_distinct_queries', 'distinct_query_hash', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'distinct_queries', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_distinct_queries', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- distinct_queries.FK_distinct_queries_source_info_internal
IF OBJECT_ID ('snapshots.FK_distinct_queries_source_info_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_distinct_queries_source_info_internal] on snapshots.distinct_queries ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[distinct_queries] ADD CONSTRAINT [FK_distinct_queries_source_info_internal] FOREIGN KEY([source_id])
        REFERENCES [core].[source_info_internal] (source_id)
        ON DELETE CASCADE
END;
GO


IF (OBJECT_ID(N'snapshots.distinct_query_to_handle', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[distinct_query_to_handle]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[distinct_query_to_handle] (
        [distinct_query_hash]   bigint          NOT NULL,
        [sql_handle]            varbinary(64)   NOT NULL,
        [source_id]             int             NOT NULL
    ) ON [PRIMARY]
END
GO

-- distinct_query_to_handle.PK_distinct_query_to_handle
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_distinct_query_to_handle', 'source_id', 0, 0), 
    ('PK_distinct_query_to_handle', 'distinct_query_hash', 0, 0), 
    ('PK_distinct_query_to_handle', 'sql_handle', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'distinct_query_to_handle', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_distinct_query_to_handle', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- distinct_query_to_handle.FK_distinct_query_to_handle_notable_query_text
IF OBJECT_ID ('snapshots.FK_distinct_query_to_handle_notable_query_text', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key snapshots.distinct_query_to_handle.FK_distinct_query_to_handle_notable_query_text...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[distinct_query_to_handle] ADD CONSTRAINT [FK_distinct_query_to_handle_notable_query_text] FOREIGN KEY([source_id], [sql_handle])
        REFERENCES [snapshots].[notable_query_text] ([source_id], [sql_handle])
END;
GO

-- distinct_query_to_handle.FK_distinct_query_to_handle_distinct_queries
IF OBJECT_ID ('snapshots.FK_distinct_query_to_handle_distinct_queries', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key snapshots.distinct_query_to_handle.FK_distinct_query_to_handle_distinct_queries...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[distinct_query_to_handle] ADD CONSTRAINT [FK_distinct_query_to_handle_distinct_queries] FOREIGN KEY([source_id], [distinct_query_hash])
        REFERENCES [snapshots].[distinct_queries] ([source_id], [distinct_query_hash])
END;
GO

-- distinct_query_to_handle.FK_distinct_query_to_handle_source_info_internal
IF OBJECT_ID ('snapshots.FK_distinct_query_to_handle_source_info_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key snapshots.distinct_query_to_handle.FK_distinct_query_to_handle_source_info_internal...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[distinct_query_to_handle] ADD CONSTRAINT [FK_distinct_query_to_handle_source_info_internal] FOREIGN KEY([source_id])
        REFERENCES [core].[source_info_internal] (source_id)
        ON DELETE CASCADE
END;
GO


-- This function returns fragment within sql query text in the specified start_offset and end_offset range (offsets are specified in bytes)
IF (NOT OBJECT_ID(N'snapshots.fn_get_query_fragment', 'FN') IS NULL)
BEGIN
    RAISERROR('Dropping function [snapshots].[fn_get_query_fragment] ...', 0, 1)  WITH NOWAIT;
    DROP FUNCTION [snapshots].[fn_get_query_fragment]
END
GO 
RAISERROR('Creating function [snapshots].[fn_get_query_fragment] ...', 0, 1)  WITH NOWAIT;
GO
CREATE FUNCTION [snapshots].[fn_get_query_fragment](
    @sqltext nvarchar(max),
    @start_offset int, 
    @end_offset int
)
RETURNS NVARCHAR(MAX)
BEGIN
    DECLARE @query_text NVARCHAR(MAX)
    
    DECLARE @query_text_length int
    SET @query_text_length = DATALENGTH(@sqltext) 

    -- If start_offset was set as null, default to starting byte 0
    IF (@start_offset IS NULL)
    BEGIN
        SET @start_offset = 0
    END 

    -- Validate start_offset, return this function if  offset is less than 0
    -- Validate sqltext, if input is NULL, we dont need to continue
    IF (@start_offset < 0 OR @sqltext IS NULL)
    BEGIN
        -- exceptions are not thrown here because caller calls this function on a report query where
        -- throwing exceptions would abort report rendering
        RETURN @query_text
    END

    -- ending position of the query that the row describes within the text of its batch or persisted object. 
    -- value of -1 indicates the end of the batch.
    IF (@end_offset IS NULL OR @end_offset = -1 )
    BEGIN
        SET @end_offset = @query_text_length
    END 

    -- Set the offset to closest even number. Ex: start_offset = 5, set as start_offset = 4th byte
    SET @start_offset = CEILING(@start_offset/2) *2
    SET @end_offset = CEILING(@end_offset/2) *2

    -- Validate start and end offsets
    IF (@start_offset <= @query_text_length    -- start offset should be  less than length of query string
        AND @end_offset <= @query_text_length      -- end offset should be  less than length of query string
        AND @start_offset <= @end_offset)          -- start offset should be less than end offset
    BEGIN
        -- start and end offsets are the byte's position as reported  in DMV sys.dm_exec_query_stats.
        -- sqltext has a NVARCHAR string where every character takes 2 bytes. SUBSTRING() deals with starting character's position 
        -- and length of characters from starting position. so, we are dividing by 2 to convert byte position to character position 
        SELECT @query_text = SUBSTRING(@sqltext, @start_offset/2, (@end_offset - @start_offset)/2 + 1)
    END

    RETURN @query_text
END
GO

-- This function returns text and database and object context for a query identified by a sql_handle
IF (NOT OBJECT_ID(N'snapshots.fn_get_query_text', 'TF') IS NULL)
BEGIN
    RAISERROR('Dropping function [snapshots].[fn_get_query_text] ...', 0, 1)  WITH NOWAIT;
    DROP FUNCTION [snapshots].[fn_get_query_text]
END
GO 

RAISERROR('Creating function [snapshots].[fn_get_query_text] ...', 0, 1)  WITH NOWAIT;
GO
CREATE FUNCTION [snapshots].[fn_get_query_text](
    @source_id int,
    @sql_handle varbinary(64), 
    @statement_start_offset int, 
    @statement_end_offset int
)
RETURNS @query_text TABLE (database_id smallint NULL, object_id int NULL, object_name sysname NULL, query_text nvarchar(max) NULL)

BEGIN
    IF @sql_handle IS NOT NULL AND 
       EXISTS (SELECT sql_handle FROM snapshots.notable_query_text WHERE sql_handle = @sql_handle AND source_id = @source_id)
    BEGIN
        INSERT INTO @query_text 
        (
            database_id, 
            object_id, 
            object_name, 
            query_text
        ) 
        SELECT 
            t.database_id,
            t.object_id,
            t.object_name,
            [snapshots].[fn_get_query_fragment](t.sql_text, @statement_start_offset, @statement_end_offset)
        FROM
            snapshots.notable_query_text t
        WHERE
            t.sql_handle = @sql_handle
            AND t.source_id = @source_id
    END

    RETURN
END
GO

-- This stored procedure returns all rows from notable_query_text table 
-- where sql_text value is NULL, meaning we did not caputure the text for that query yet.
IF (NOT OBJECT_ID(N'snapshots.sp_get_unknown_query_text', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[sp_get_unknown_query_text] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[sp_get_unknown_query_text]
END
GO 

RAISERROR('Creating procedure [snapshots].[sp_get_unknown_query_text] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[sp_get_unknown_query_text]
    @source_id  int
AS
BEGIN
    SET NOCOUNT ON;

    SELECT
        [source_id],
        [sql_handle]
    FROM
        [snapshots].[notable_query_text]
    WHERE
        [source_id] = @source_id
        AND [sql_text] IS NULL
END;
GO

-- This stored procedure returns all rows from notable_query_plan table 
-- where sql_text value is NULL, meaning we did not caputure the plan for that query yet.
IF (NOT OBJECT_ID(N'snapshots.sp_get_unknown_query_plan', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[sp_get_unknown_query_plan] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[sp_get_unknown_query_plan]
END
GO 

RAISERROR('Creating procedure [snapshots].[sp_get_unknown_query_plan] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[sp_get_unknown_query_plan]
    @source_id       int
AS
BEGIN
    SET NOCOUNT ON;

    SELECT
        [source_id],
        [sql_handle],
        [plan_handle],
        [statement_start_offset],
        [statement_end_offset],
        [plan_generation_num]
    FROM
        [snapshots].[notable_query_plan]
    WHERE
        [source_id] = @source_id
        AND [query_plan] IS NULL
END;
GO

-- Updates a single row with new text for the sql_handle
IF (NOT OBJECT_ID(N'snapshots.sp_update_query_text', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[sp_update_query_text] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[sp_update_query_text]
END
GO 

RAISERROR('Creating procedure [snapshots].[sp_update_query_text] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[sp_update_query_text]
    @source_id       int,
    @sql_handle      varbinary(64),
    @database_id     smallint     ,
    @object_id       int          ,
    @object_name     nvarchar(128),
    @sql_text        nvarchar(max)
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE [snapshots].[notable_query_text]
    SET
        database_id = @database_id,
        object_id   = @object_id,
        object_name = @object_name,
        sql_text    = @sql_text
    WHERE
        source_id = @source_id 
        AND sql_handle = @sql_handle
        
END;
GO

-- Updates a single row with new plan for the plan_handle
IF (NOT OBJECT_ID(N'snapshots.sp_update_query_plan', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[sp_update_query_plan] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[sp_update_query_plan]
END
GO 

RAISERROR('Creating procedure [snapshots].[sp_update_query_plan] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[sp_update_query_plan]
    @source_id                 int,
    @sql_handle                varbinary(64),
    @plan_handle               varbinary(64),
    @statement_start_offset    int          ,
    @statement_end_offset      int          ,
    @plan_generation_num       bigint       ,
    @database_id               smallint     ,
    @object_id                 int          ,
    @object_name               nvarchar(128),
    @query_plan                nvarchar(max)
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE [snapshots].[notable_query_plan]
    SET
        database_id     = @database_id,
        object_id       = @object_id,
        object_name     = @object_name,
        query_plan      = @query_plan
    WHERE
        source_id = @source_id
        AND sql_handle = @sql_handle
        AND plan_handle = @plan_handle
        AND statement_start_offset = @statement_start_offset
        AND statement_end_offset = @statement_end_offset
        AND plan_generation_num = @plan_generation_num

END;
GO




--
-- SERVER ACTIVITY COLLECTION SET SUPPORT
--

-- os_wait_stats table
--
IF (OBJECT_ID(N'snapshots.os_wait_stats', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[os_wait_stats]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[os_wait_stats](
        [wait_type]             nvarchar(45) NOT NULL,
        [waiting_tasks_count]   bigint NOT NULL,
        [wait_time_ms]          bigint NOT NULL,
        [signal_wait_time_ms]   bigint NOT NULL,
        [collection_time]       datetimeoffset(7) NOT NULL,
        [snapshot_id]           int NOT NULL,
    );
    
    ALTER TABLE [snapshots].[os_wait_stats]  WITH CHECK ADD  CONSTRAINT [CHK_os_wait_stats_check_operator] CHECK  (([core].[fn_check_operator]([snapshot_id])=(1)))
    
    CREATE STATISTICS [STAT_os_wait_stats1] ON [snapshots].[os_wait_stats](
        [collection_time], 
        [snapshot_id], 
        [wait_type]
    )

    CREATE STATISTICS [STAT_os_wait_stats2] ON [snapshots].[os_wait_stats](
        [collection_time], 
        [wait_type]
    )

END;
GO

-- os_wait_stats.PK_os_wait_stats
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_os_wait_stats', 'snapshot_id', 0, 0), 
    ('PK_os_wait_stats', 'collection_time', 0, 0), 
    ('PK_os_wait_stats', 'wait_type', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_wait_stats', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_os_wait_stats', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- os_wait_stats.IDX_os_wait_stats1
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_os_wait_stats1', 'collection_time', 0, 1), 
    ('IDX_os_wait_stats1', 'snapshot_id', 0, 0), 
    ('IDX_os_wait_stats1', 'wait_type', 1, 0), 
    ('IDX_os_wait_stats1', 'signal_wait_time_ms', 1, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_wait_stats', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_os_wait_stats1', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- os_wait_stats.IDX_os_wait_stats1
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_os_wait_stats2', 'snapshot_id', 0, 0), 
    ('IDX_os_wait_stats2', 'collection_time', 0, 0); 
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_wait_stats', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_os_wait_stats2', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- os_wait_stats.FK_os_wait_stats_snapshots_internal
IF OBJECT_ID ('snapshots.FK_os_wait_stats_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_os_wait_stats_snapshots_internal] on snapshots.os_wait_stats ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[os_wait_stats] ADD CONSTRAINT [FK_os_wait_stats_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


-- os_latch_stats table
--
IF (OBJECT_ID(N'snapshots.os_latch_stats', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[os_latch_stats]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[os_latch_stats](
        [latch_class]               nvarchar(45) NOT NULL,
        [waiting_requests_count]    bigint NOT NULL,
        [wait_time_ms]              bigint NOT NULL,
        [collection_time]           datetimeoffset(7) NOT NULL,
        [snapshot_id]               int NOT NULL,
    );
    
    ALTER TABLE [snapshots].[os_latch_stats] WITH CHECK ADD CONSTRAINT [CHK_os_latch_stats_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END;
GO

-- os_latch_stats.PK_os_latch_stats
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_os_latch_stats', 'snapshot_id', 0, 0), 
    ('PK_os_latch_stats', 'collection_time', 0, 0), 
    ('PK_os_latch_stats', 'latch_class', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_latch_stats', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_os_latch_stats', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- os_latch_stats.FK_os_latch_stats_snapshots_internal
IF OBJECT_ID ('snapshots.FK_os_latch_stats_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_os_latch_stats_snapshots_internal] on snapshots.os_latch_stats ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[os_latch_stats] ADD CONSTRAINT [FK_os_latch_stats_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


IF (OBJECT_ID(N'snapshots.active_sessions_and_requests', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[active_sessions_and_requests]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[active_sessions_and_requests](
        [row_id]                    int NOT NULL,
        [session_id]                smallint NOT NULL,
        [request_id]                int NOT NULL,
        [exec_context_id]           int NOT NULL,
        [blocking_session_id]       smallint NULL,
        [blocking_exec_context_id]  int NULL,
        [scheduler_id]              int NULL,
        [database_name]             nvarchar(128) NULL,
        [user_id]                   int NULL,
        [task_state]                nvarchar(10) NULL,
        [request_status]            nvarchar(15) NULL,
        [session_status]            nvarchar(15) NOT NULL,
        [executing_managed_code]    bit NULL,
        [login_time]                datetimeoffset(7) NOT NULL,
        [is_user_process]           bit NOT NULL,
        [host_name]                 nvarchar(128) NOT NULL,
        [program_name]              nvarchar(128) NOT NULL,
        [login_name]                nvarchar(30) NOT NULL,
        [wait_type]                 nvarchar(45) NOT NULL,
        [last_wait_type]            nvarchar(45) NOT NULL,
        [wait_duration_ms]          bigint NOT NULL,
        [wait_resource]             nvarchar(50) NOT NULL,
        [resource_description]      nvarchar(140) NOT NULL,
        [transaction_id]            bigint NULL,
        [open_transaction_count]    int NOT NULL,
        [transaction_isolation_level] smallint NULL,
        [request_cpu_time]          int NULL,
        [request_logical_reads]     bigint NULL,
        [request_reads]             bigint NULL,
        [request_writes]            bigint NULL,
        [request_total_elapsed_time] int NULL,
        [request_start_time]        datetimeoffset(7) NULL,
        [memory_usage]              int NOT NULL,
        [session_cpu_time]          int NOT NULL,
        [session_reads]             bigint NOT NULL,
        [session_writes]            bigint NOT NULL,
        [session_logical_reads]     bigint NOT NULL,
        [session_total_scheduled_time] int NOT NULL,
        [session_total_elapsed_time] int NOT NULL,
        [session_last_request_start_time] datetimeoffset(7) NOT NULL,
        [session_last_request_end_time] datetimeoffset(7) NULL,
        [open_resultsets]           int NULL,
        [session_row_count]         bigint NOT NULL,
        [prev_error]                int NOT NULL,
        [pending_io_count]          int NULL,
        [command]                   nvarchar(32) NULL,
        [plan_handle]               varbinary(64) NULL,
        [sql_handle]                varbinary(64) NULL,
        [statement_start_offset]    int NULL,
        [statement_end_offset]      int NULL,
        [collection_time]           datetimeoffset(7) NOT NULL,
        [snapshot_id]               int NOT NULL,
        [is_blocking]               bit NOT NULL,
    );

    ALTER TABLE [snapshots].[active_sessions_and_requests] WITH CHECK ADD CONSTRAINT [CHK_active_sessions_and_requests_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END;
GO

-- active_sessions_and_requests.PK_active_sessions_and_requests
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_active_sessions_and_requests', 'snapshot_id', 0, 0), 
    ('PK_active_sessions_and_requests', 'collection_time', 0, 0), 
    ('PK_active_sessions_and_requests', 'row_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'active_sessions_and_requests', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_active_sessions_and_requests', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- active_sessions_and_requests.IDX_blocking_session_id
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_blocking_session_id', 'blocking_session_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'active_sessions_and_requests', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_blocking_session_id', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- active_sessions_and_requests.IDX_collection_time
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('IDX_collection_time', 'collection_time', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'active_sessions_and_requests', 
    @object_type = 'INDEX', @constraint_or_index_name = 'IDX_collection_time', 
    @ignore_dup_key = 0, @clustered = 0;
GO

-- active_sessions_and_requests.FK_active_sessions_and_requests_snapshots_internal
IF OBJECT_ID ('snapshots.FK_active_sessions_and_requests_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_active_sessions_and_requests_snapshots_internal] on snapshots.active_sessions_and_requests ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[active_sessions_and_requests] ADD CONSTRAINT [FK_active_sessions_and_requests_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


IF (OBJECT_ID(N'snapshots.os_schedulers', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[os_schedulers]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[os_schedulers] (
        [parent_node_id]            int    NOT NULL,
        [scheduler_id]                int    NOT NULL,
        [cpu_id]                    int NOT NULL,
        [status]                    nvarchar(60) NOT NULL,
        [is_idle]                    bit NOT NULL,
        [preemptive_switches_count]    int NOT NULL,
        [context_switches_count]    int NOT NULL,
        [yield_count]                int NOT NULL,
        [current_tasks_count]        int NOT NULL,
        [runnable_tasks_count]        int NOT NULL,
        [work_queue_count]            bigint NOT NULL,
        [pending_disk_io_count]        int NOT NULL,
        [collection_time]           datetimeoffset(7) NOT NULL,
        [snapshot_id]               int NOT NULL,
    );

    ALTER TABLE [snapshots].[os_schedulers] WITH CHECK ADD CONSTRAINT [CHK_os_schedulers_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END;
GO

-- os_schedulers.PK_os_schedulers
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_os_schedulers', 'snapshot_id', 0, 0), 
    ('PK_os_schedulers', 'collection_time', 0, 0), 
    ('PK_os_schedulers', 'scheduler_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_schedulers', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_os_schedulers', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- os_schedulers.FK_os_schedulers_snapshots_internal
IF OBJECT_ID ('snapshots.FK_os_schedulers_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_os_schedulers_snapshots_internal] on snapshots.os_schedulers ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[os_schedulers] ADD CONSTRAINT [FK_os_schedulers_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


IF (OBJECT_ID(N'snapshots.os_memory_nodes', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[os_memory_nodes]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[os_memory_nodes] (
        [memory_node_id]                        smallint NOT NULL, 
        [virtual_address_space_reserved_kb]     bigint NOT NULL, 
        [virtual_address_space_committed_kb]    bigint NOT NULL, 
        [locked_page_allocations_kb]            bigint NOT NULL, 
        [single_pages_kb]                       bigint NOT NULL, 
        [multi_pages_kb]                        bigint NOT NULL, 
        [shared_memory_reserved_kb]             bigint NOT NULL, 
        [shared_memory_committed_kb]            bigint NOT NULL,
        [collection_time]                       datetimeoffset(7) NOT NULL,
        [snapshot_id]                           int NOT NULL,
    );

    ALTER TABLE [snapshots].[os_memory_nodes] WITH CHECK ADD CONSTRAINT [CHK_os_memory_nodes_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END;
GO

-- os_memory_nodes.PK_os_memory_nodes
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_os_memory_nodes', 'snapshot_id', 0, 0), 
    ('PK_os_memory_nodes', 'collection_time', 0, 0), 
    ('PK_os_memory_nodes', 'memory_node_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_memory_nodes', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_os_memory_nodes', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- os_memory_nodes.FK_os_memory_nodes_snapshots_internal
IF OBJECT_ID ('snapshots.FK_os_memory_nodes_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_os_memory_nodes_snapshots_internal] on snapshots.os_memory_nodes ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[os_memory_nodes] ADD CONSTRAINT [FK_os_memory_nodes_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


-- NOTE: Used by System Activity collection set up to CTP6 Refresh/RC0. Not used in RTM and later. 
IF (OBJECT_ID(N'snapshots.os_process_memory', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[os_process_memory]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[os_process_memory] (
        [physical_memory_in_use_kb]              bigint NOT NULL,
        [large_page_allocations_kb]              bigint NOT NULL, 
        [locked_page_allocations_kb]             bigint NOT NULL, 
        [total_virtual_address_space_kb]         bigint NOT NULL,
        [virtual_address_space_reserved_kb]      bigint NOT NULL,
        [virtual_address_space_committed_kb]     bigint NOT NULL,
        [virtual_address_space_available_kb]     bigint NOT NULL,
        [page_fault_count]                       bigint NOT NULL,
        [memory_utilization_percentage]          int NOT NULL,
        [available_commit_limit_kb]              bigint NOT NULL,
        [process_physical_memory_low]            bit NOT NULL,
        [process_virtual_memory_low]             bit NOT NULL,
        [collection_time]                        datetimeoffset(7) NOT NULL,
        [snapshot_id]                            int NOT NULL,
    );

    ALTER TABLE [snapshots].[os_process_memory] WITH CHECK ADD CONSTRAINT [CHK_os_process_memory_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END;
GO

-- os_process_memory.PK_os_process_memory
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_os_process_memory', 'snapshot_id', 0, 0), 
    ('PK_os_process_memory', 'collection_time', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_process_memory', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_os_process_memory', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- os_process_memory.FK_os_process_memory_snapshots_internal
IF OBJECT_ID ('snapshots.FK_os_process_memory_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_os_process_memory_snapshots_internal] on snapshots.os_process_memory ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[os_process_memory] ADD CONSTRAINT [FK_os_process_memory_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


IF (OBJECT_ID(N'snapshots.sql_process_and_system_memory', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[sql_process_and_system_memory]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[sql_process_and_system_memory](
        [sql_physical_memory_in_use_kb]             bigint NOT NULL,
        [sql_large_page_allocations_kb]             bigint NOT NULL,
        [sql_locked_page_allocations_kb]            bigint NOT NULL,
        [sql_total_virtual_address_space_kb]        bigint NOT NULL,
        [sql_virtual_address_space_reserved_kb]     bigint NOT NULL,
        [sql_virtual_address_space_committed_kb]    bigint NOT NULL,
        [sql_virtual_address_space_available_kb]    bigint NOT NULL,
        [sql_page_fault_count]                      bigint NOT NULL,
        [sql_memory_utilization_percentage]         int NOT NULL,
        [sql_available_commit_limit_kb]             bigint NOT NULL,
        [sql_process_physical_memory_low]           bit NOT NULL,
        [sql_process_virtual_memory_low]            bit NOT NULL,
        [system_total_physical_memory_kb]           bigint NOT NULL,
        [system_available_physical_memory_kb]       bigint NOT NULL,
        [system_total_page_file_kb]                 bigint NOT NULL,
        [system_available_page_file_kb]             bigint NOT NULL,
        [system_cache_kb]                           bigint NOT NULL,
        [system_kernel_paged_pool_kb]               bigint NOT NULL,
        [system_kernel_nonpaged_pool_kb]            bigint NOT NULL,
        [system_high_memory_signal_state]           bit NOT NULL,
        [system_low_memory_signal_state]            bit NOT NULL,
        [bpool_commit_target]                       bigint NOT NULL,
        [bpool_committed]                           bigint NOT NULL,
        [bpool_visible]                             bigint NOT NULL, 
        [collection_time]                           datetimeoffset(7) NOT NULL,
        [snapshot_id]                               int NOT NULL,
    );

    ALTER TABLE [snapshots].[sql_process_and_system_memory] WITH CHECK ADD CONSTRAINT [CHK_sql_process_and_system_memory_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)));
END;
GO

-- sql_process_and_system_memory.PK_sql_process_and_system_memory
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_sql_process_and_system_memory', 'snapshot_id', 0, 0), 
    ('PK_sql_process_and_system_memory', 'collection_time', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'sql_process_and_system_memory', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_sql_process_and_system_memory', 
    @ignore_dup_key = 0, @clustered = 1;
GO



-- sql_process_and_system_memory.FK_sql_process_and_system_memory_internal
IF OBJECT_ID ('snapshots.FK_sql_process_and_system_memory_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_sql_process_and_system_memory_internal] on snapshots.sql_process_and_system_memory ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[sql_process_and_system_memory] ADD CONSTRAINT [FK_sql_process_and_system_memory_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE;
END;
GO


IF (OBJECT_ID(N'snapshots.os_memory_clerks', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[os_memory_clerks]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[os_memory_clerks] (
        [type]                                  nvarchar(60),
        [memory_node_id]                        smallint,
        [single_pages_kb]                       bigint,
        [multi_pages_kb]                        bigint,
        [virtual_memory_reserved_kb]            bigint,
        [virtual_memory_committed_kb]           bigint,
        [awe_allocated_kb]                      bigint,
        [shared_memory_reserved_kb]             bigint,
        [shared_memory_committed_kb]            bigint,
        [collection_time]                       datetimeoffset(7) NOT NULL,
        [snapshot_id]                           int NOT NULL
    ) ON [PRIMARY]

    ALTER TABLE [snapshots].[os_memory_clerks] WITH CHECK ADD CONSTRAINT [CHK_os_memory_clerks_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END
GO

-- os_memory_clerks.CIDX_os_memory_clerks
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('CIDX_os_memory_clerks', 'snapshot_id', 0, 0), 
    ('CIDX_os_memory_clerks', 'collection_time', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'os_memory_clerks', 
    @object_type = 'INDEX', @constraint_or_index_name = 'CIDX_os_memory_clerks', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- os_memory_clerks.FK_os_memory_clerks_snapshots_internal
IF OBJECT_ID ('snapshots.FK_os_memory_clerks_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_os_memory_clerks_snapshots_internal] on snapshots.os_memory_clerks ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[os_memory_clerks] ADD CONSTRAINT [FK_os_memory_clerks_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


--
-- QUERY ACTIVITY COLLECTION SET SUPPORT
--

IF (OBJECT_ID(N'snapshots.query_stats', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[query_stats]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[query_stats] (
           [sql_handle]                varbinary(64) NOT NULL,
         [statement_start_offset]   int NOT NULL,
         [statement_end_offset]        int NOT NULL,
         [plan_generation_num]        bigint NOT NULL,
         [plan_handle]                varbinary(64) NOT NULL,
         [creation_time]            datetimeoffset(7) NOT NULL,
         [last_execution_time]        datetimeoffset(7) NOT NULL,
         [execution_count]            bigint NOT NULL,
         [snapshot_execution_count]    bigint NULL,
         [total_worker_time]        bigint NOT NULL,
         [snapshot_worker_time]        bigint NOT NULL,
         [min_worker_time]            bigint NULL,        -- NULLable b/c we can't calc min for queries that are still in-progress 
         [max_worker_time]            bigint NOT NULL,
         [total_physical_reads]        bigint NOT NULL,
         [snapshot_physical_reads]    bigint NOT NULL,
         [min_physical_reads]        bigint NULL,        -- NULLable b/c we can't calc min for queries that are still in-progress 
         [max_physical_reads]        bigint NOT NULL,
         [total_logical_writes]        bigint NOT NULL,
         [snapshot_logical_writes]    bigint NOT NULL,
         [min_logical_writes]        bigint NULL,        -- NULLable b/c we can't calc min for queries that are still in-progress 
         [max_logical_writes]        bigint NOT NULL,
         [total_logical_reads]        bigint NOT NULL,
         [snapshot_logical_reads]    bigint NOT NULL,
         [min_logical_reads]        bigint NULL,        -- NULLable b/c we can't calc min for queries that are still in-progress 
         [max_logical_reads]        bigint NOT NULL,
         [total_clr_time]            bigint NULL,        -- NULLable b/c dm_exec_requests doesn't expose this stat
         [snapshot_clr_time]        bigint NULL,        -- NULLable b/c dm_exec_requests doesn't expose this stat
         [min_clr_time]                bigint NULL,        -- NULLable b/c we can't calc min for queries that are still in-progress 
         [max_clr_time]                bigint NULL,        -- NULLable b/c dm_exec_requests doesn't expose this stat 
         [total_elapsed_time]        bigint NOT NULL,
         [snapshot_elapsed_time]    bigint NOT NULL,
         [min_elapsed_time]            bigint NULL,        -- NULLable b/c we can't calc min for queries that are still in-progress 
         [max_elapsed_time]            bigint NOT NULL,
         [collection_time]          datetimeoffset(7) NOT NULL,
         [snapshot_id]              int NOT NULL

    ) ON [PRIMARY]

    ALTER TABLE [snapshots].[query_stats] WITH CHECK ADD CONSTRAINT [CHK_query_stats_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END
GO

-- query_stats.PK_query_stats
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('CIDX_query_stats', 'snapshot_id', 0, 0), 
    ('CIDX_query_stats', 'collection_time', 0, 0), 
    ('CIDX_query_stats', 'sql_handle', 0, 0), 
    ('CIDX_query_stats', 'statement_start_offset', 0, 0), 
    ('CIDX_query_stats', 'statement_end_offset', 0, 0), 
    ('CIDX_query_stats', 'plan_handle', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'query_stats', 
    @object_type = 'INDEX', @constraint_or_index_name = 'CIDX_query_stats', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- query_stats.FK_query_stats_snapshots_internal
IF OBJECT_ID ('snapshots.FK_query_stats_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_query_stats_snapshots_internal] on snapshots.query_stats ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[query_stats] ADD CONSTRAINT [FK_query_stats_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


IF (NOT OBJECT_ID(N'snapshots.distinct_query_stats', 'V') IS NULL)
BEGIN
    RAISERROR('Dropping view [snapshots].[distinct_query_stats]...', 0, 1)  WITH NOWAIT;
    DROP VIEW [snapshots].[distinct_query_stats]
END
GO

RAISERROR('Creating view [snapshots].[distinct_query_stats]...', 0, 1)  WITH NOWAIT;
GO
CREATE VIEW [snapshots].[distinct_query_stats]
AS 
    SELECT 
        dqth.distinct_query_hash,
        SUM(qs.execution_count) AS execution_count,
        SUM(qs.total_worker_time) AS total_worker_time,
        SUM(qs.total_physical_reads) AS total_physical_reads,
        SUM(qs.total_logical_reads) AS total_logical_reads,
        SUM(qs.total_logical_writes) AS total_logical_writes,
        SUM(qs.total_clr_time) AS total_clr_time,
        SUM(qs.total_elapsed_time) AS total_elapsed_time
    FROM
        [snapshots].[query_stats] qs
        JOIN [core].[snapshots_internal] s ON (s.snapshot_id = qs.snapshot_id)
        JOIN [snapshots].[distinct_query_to_handle] dqth ON (s.source_id = dqth.source_id AND qs.sql_handle = dqth.sql_handle)
    GROUP BY
        dqth.distinct_query_hash
GO



--
-- DISK USAGE COLLECTION SET SUPPORT
--

-- disk_usage table
--
IF (OBJECT_ID(N'snapshots.disk_usage', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[disk_usage]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[disk_usage](
        [dbsize] [bigint] NULL,
        [logsize] [bigint] NULL,
        [ftsize] [bigint] NULL,
        [reservedpages] [bigint] NULL,
        [usedpages] [bigint] NULL,
        [pages] [bigint] NULL,
        [database_name] [nvarchar](128) NOT NULL,
        [collection_time] datetimeoffset(7) NOT NULL,
        [snapshot_id] [int] NOT NULL, 
    ) ON [PRIMARY]

    ALTER TABLE [snapshots].[disk_usage]  WITH CHECK ADD  CONSTRAINT [CHK_disk_usage_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END
GO

-- disk_usage.PK_disk_usage
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_disk_usage', 'snapshot_id', 0, 0), 
    ('PK_disk_usage', 'collection_time', 0, 0), 
    ('PK_disk_usage', 'database_name', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'disk_usage', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_disk_usage', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- disk_usage.FK_disk_usage_snapshots_internal
IF OBJECT_ID ('snapshots.FK_disk_usage_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_disk_usage_snapshots_internal] on snapshots.disk_usage ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[disk_usage]  WITH CHECK ADD  CONSTRAINT [FK_disk_usage_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


-- log_usage table
--
IF (OBJECT_ID(N'snapshots.log_usage', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[log_usage]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[log_usage](
        [database_name] [nvarchar](128) NOT NULL,
        [log_size_mb] [float] NULL,
        [log_space_used] [float] NULL,
        [status] [int] NULL,
        [collection_time] datetimeoffset(7) NOT NULL,
        [snapshot_id] [int] NOT NULL, 
    ) ON [PRIMARY]

    ALTER TABLE [snapshots].[log_usage] WITH CHECK ADD CONSTRAINT [CHK_log_usage_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END
GO

-- log_usage.PK_log_usage
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_log_usage', 'snapshot_id', 0, 0), 
    ('PK_log_usage', 'collection_time', 0, 0), 
    ('PK_log_usage', 'database_name', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'log_usage', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_log_usage', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- log_usage.FK_log_usage_snapshots_internal
IF OBJECT_ID ('snapshots.FK_log_usage_snapshots_internal', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_log_usage_snapshots_internal] on snapshots.log_usage ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[log_usage] WITH CHECK ADD CONSTRAINT [FK_log_usage_snapshots_internal] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO


-- io_virtual_file_stats table
--
IF (OBJECT_ID(N'snapshots.io_virtual_file_stats', 'U') IS NULL)
BEGIN
    RAISERROR('Creating table [snapshots].[io_virtual_file_stats]...', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[io_virtual_file_stats] (
        [database_name] [nvarchar](128) NOT NULL,
        [database_id] [int] NOT NULL,
        [logical_file_name] [nvarchar](128) NOT NULL,
        [file_id] [int] NOT NULL,
        [type_desc] [nvarchar](60) NULL,
        [logical_disk] [nvarchar](255) NOT NULL,
        [num_of_reads] [bigint] NULL,
        [num_of_bytes_read] [bigint] NULL,
        [io_stall_read_ms] [bigint] NULL,
        [num_of_writes] [bigint] NULL,
        [num_of_bytes_written] [bigint] NULL,
        [io_stall_write_ms] [bigint] NULL,
        [size_on_disk_bytes] [bigint] NULL,
        [collection_time] [datetimeoffset](7) NOT NULL,
        [snapshot_id] [int] NOT NULL, 
        
    ) ON [PRIMARY]

    ALTER TABLE [snapshots].[io_virtual_file_stats] WITH CHECK ADD CONSTRAINT [CHK_io_virtual_file_stats_check_operator] CHECK (([core].[fn_check_operator]([snapshot_id])=(1)))
END
GO

-- io_virtual_file_stats.PK_io_virtual_file_stats
TRUNCATE TABLE #index_key_columns;
INSERT INTO #index_key_columns (constraint_name, column_name, is_included_column, is_descending_key) 
VALUES 
    ('PK_io_virtual_file_stats', 'snapshot_id', 0, 0), 
    ('PK_io_virtual_file_stats', 'collection_time', 0, 0), 
    ('PK_io_virtual_file_stats', 'logical_disk', 0, 0), 
    ('PK_io_virtual_file_stats', 'database_name', 0, 0), 
    ('PK_io_virtual_file_stats', 'file_id', 0, 0);
EXEC #create_or_alter_primary_key_or_index
    @table_schema = 'snapshots', @table_name = 'io_virtual_file_stats', 
    @object_type = 'PRIMARY KEY', @constraint_or_index_name = 'PK_io_virtual_file_stats', 
    @ignore_dup_key = 0, @clustered = 1;
GO

-- io_virtual_file_stats.FK_io_virtual_file_stats
IF OBJECT_ID ('snapshots.FK_io_virtual_file_stats', 'F') IS NULL
BEGIN
    RAISERROR ('Creating foreign key [FK_io_virtual_file_stats] on snapshots.io_virtual_file_stats ...', 0, 1) WITH NOWAIT;
    ALTER TABLE [snapshots].[io_virtual_file_stats] WITH CHECK ADD CONSTRAINT [FK_io_virtual_file_stats] FOREIGN KEY([snapshot_id])
        REFERENCES [core].[snapshots_internal] ([snapshot_id])
        ON DELETE CASCADE
END;
GO



/**********************************************************************/
/* REPORTING STORED PROCEDURES                                       */
/**********************************************************************/

--
-- snapshots.rpt_snapshot_times
--  Returns all snapshot times for a given collection set.  
--  Used by (nearly) all reports to return data for the timeline chart that sits at the top of each report. 
--
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of the user-selected time window (UTC)
--    @WindowSize - Number of minutes in the time window 
--    @CollectionSetUid - GUID of the collection set with the snapshot_times that the report cares about
--
IF (NOT OBJECT_ID(N'snapshots.rpt_snapshot_times', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_snapshot_times] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_snapshot_times]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_snapshot_times] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROC [snapshots].[rpt_snapshot_times]
    @ServerName sysname, 
    @EndTime datetime, 
    @WindowSize int, 
    @CollectionSetUid uniqueidentifier
AS
BEGIN
    DECLARE @end_time_internal datetimeoffset(7)
    SET @end_time_internal = TODATETIMEOFFSET (@EndTime, '+00:00')

    -- Get the time of the earliest and latest snapshots for this collection set
    DECLARE @min_snapshot_time datetimeoffset(7)
    DECLARE @max_snapshot_time datetimeoffset(7)
    DECLARE @total_data_collection_window int
    SELECT @min_snapshot_time = MIN (snapshot_time), @max_snapshot_time = MAX (snapshot_time)
    FROM core.snapshots 
    WHERE instance_name = @ServerName
        AND collection_set_uid = @CollectionSetUid
    IF @min_snapshot_time IS NULL SET @min_snapshot_time = SYSDATETIMEOFFSET()
    IF @max_snapshot_time IS NULL SET @max_snapshot_time = SYSDATETIMEOFFSET()
    SET @total_data_collection_window = DATEDIFF (minute, @min_snapshot_time, @max_snapshot_time)

    -- First return all snapshot_time values for this collection set
    SELECT DISTINCT 
        CONVERT (datetime, SWITCHOFFSET (CAST (snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time,
        1 AS value, 
        'AllSnapshotTimes' AS series_name
    FROM core.snapshots
    WHERE instance_name = @ServerName
        AND collection_set_uid = @CollectionSetUid
    UNION ALL
     
    -- Then return the snapshot_time values that are in the selected time window, with a different series label. 
    SELECT DISTINCT 
        CONVERT (datetime, SWITCHOFFSET (CAST (snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time,
        0.8 AS value, 
        'SelectedSnapshotTimes' AS series_name
    FROM core.snapshots
    WHERE instance_name = @ServerName
        AND collection_set_uid = @CollectionSetUid
        AND snapshot_time BETWEEN DATEADD (minute, -1 * @WindowSize, @end_time_internal) AND @end_time_internal 
    UNION ALL 
    SELECT DISTINCT 
        DATEADD (millisecond, 10, CONVERT (datetime, SWITCHOFFSET (CAST (snapshot_time AS datetimeoffset(7)), '+00:00'))) AS snapshot_time,
        1.2 AS value, 
        'SelectedSnapshotTimes' AS series_name
    FROM core.snapshots
    WHERE instance_name = @ServerName
        AND collection_set_uid = @CollectionSetUid
        AND snapshot_time BETWEEN DATEADD (minute, -1 * @WindowSize, @end_time_internal) AND @end_time_internal 

    -- Return a "fake" data point (will not be plotted) so that the timeline always extends to the current time
    UNION ALL 
        SELECT GETUTCDATE() AS snapshot_time,
        -1 AS value, 
        'Formatting' AS in_selected_time_window

    -- Order is important here since points are plotted in the order in which they are returned from the query. 
    -- The "SelectedSnapshotTimes" series must be drawn on top of the "AllSnapshotTimes" series, so we return it 
    -- last. 
    ORDER BY series_name ASC, snapshot_time
END
GO


--
-- snapshots.rpt_interval_collection_times
--  Helper proc used by other procs that need to return data from N evenly-spaced intervals within a larger user-specified time window
--  Sample usage: 
--  
--      CREATE TABLE #intervals (
--          interval_time_id        int, 
--          interval_start_time     datetimeoffset(7),
--          interval_end_time       datetimeoffset(7),
--          interval_id             int, 
--          first_collection_time   datetimeoffset(7), 
--          last_collection_time    datetimeoffset(7), 
--          first_snapshot_id       int,
--          last_snapshot_id        int, 
--          -- the following columns may be ignored if @include_snapshot_detail = 0
--          source_id               int,                
--          snapshot_id             int,
--          collection_time         datetimeoffset(7),
--          collection_time_id      int
--      )
--      -- GUID 49268954-... is the Server Activity CS
--      INSERT INTO #intervals
--      EXEC [snapshots].[rpt_interval_collection_times] 
--          @ServerName, @EndTime, @WindowSize, 'snapshots.os_memory_clerks', '49268954-4FD4-4EB6-AA04-CD59D9BB5714', 40
--      
--      SELECT ... 
--      FROM snapshots.dm_os_memory_clerks AS mc 
--      JOIN #intervals AS col ON mc.collection_time = col.last_collection_time AND mc.snapshot_id = col.last_snapshot_id
--      WHERE ...
--
--
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of the user-selected time window (UTC)
--    @WindowSize - Number of minutes in the time window 
--    @TargetCollectionTable - Name of the table from which to harvest the collection_time values. Must have a datetimeoffset [collection_time] 
--        column and be in the [snapshots] or [custom_snapshots] schema. 
--    @CollectionSetUid - GUID of the collection set that populates @TargetCollectionTable
--    @interval_count int - Number of time intervals to divide the time window up into (default 40)
--    @include_snapshot_detail - 0 if the caller only wants 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_interval_collection_times', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_interval_collection_times] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_interval_collection_times]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_interval_collection_times] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_interval_collection_times]
    @ServerName sysname,
    @EndTime datetime = NULL,
    @WindowSize int = NULL, 
    @TargetCollectionTable sysname, 
    @CollectionSetUid varchar(64), 
    @interval_count int = 40, 
    @include_snapshot_detail bit = 0
AS
BEGIN
    SET NOCOUNT ON;
    
    DECLARE @start_time_internal datetimeoffset(7);
    DECLARE @end_time_internal datetimeoffset(7);
    
    -- Start time should be passed in as a UTC datetime
    IF (@EndTime IS NOT NULL)
    BEGIN
        -- Assumed time zone offset for this conversion is +00:00 (datetime must be passed in as UTC)
        SET @end_time_internal = CAST (@EndTime AS datetimeoffset(7));
    END
    ELSE BEGIN
        SELECT @end_time_internal = MAX(snapshot_time)
        FROM core.snapshots 
        WHERE instance_name = @ServerName AND collection_set_uid = @CollectionSetUid
    END
    SET @start_time_internal = DATEADD (minute, -1 * @WindowSize, @end_time_internal);
    
    -- Get the earliest and latest snapshot_id values that could contain data for the selected time interval. 
    -- This will allow a more efficient query plan. 
    DECLARE @start_snapshot_id int;
    DECLARE @end_snapshot_id int;
    
    SELECT @start_snapshot_id = MIN (t.snapshot_id)
    FROM
    (
        SELECT TOP 2 s.snapshot_id
        FROM core.snapshots AS s 
        WHERE s.instance_name = @ServerName AND s.collection_set_uid = @CollectionSetUid
            AND s.snapshot_time < @start_time_internal
        ORDER BY s.snapshot_id DESC
    ) AS t
    
    SELECT @end_snapshot_id = MAX (t.snapshot_id) 
    FROM 
    (
        SELECT TOP 2 snapshot_id
        FROM core.snapshots AS s 
        WHERE s.instance_name = @ServerName AND s.collection_set_uid = @CollectionSetUid
            AND s.snapshot_time >= @end_time_internal
        ORDER BY s.snapshot_id ASC
    ) AS t
    
    IF @start_snapshot_id IS NULL SELECT @start_snapshot_id = MIN (snapshot_id) FROM core.snapshots
    IF @end_snapshot_id IS NULL SELECT @end_snapshot_id = MAX (snapshot_id) FROM core.snapshots
    
    -- Divide the time window up into N equal intervals.  
    -- First, calculate the duration of one interval, in minutes. 
    DECLARE @group_interval_min int
    SET @group_interval_min = ROUND (DATEDIFF (second, @start_time_internal, @end_time_internal) / 60.0 / @interval_count, 0)
    IF @group_interval_min = 0 SET @group_interval_min = 1
        
    IF (ISNULL (PARSENAME (@TargetCollectionTable, 2), 'snapshots') IN ('snapshots', 'custom_snapshots'))
    BEGIN
        /*
        Some explanation of the expressions used in the query below: 
        
        DATEDIFF (minute, ''20000101'', dataTable.collection_time) / @group_interval_min AS interval_time_id
            - @group_interval_min is the length of one time interval (one Nth of the selected time window), in minutes
            - "DATEDIFF (minute, ''20000101'', dataTable.collection_time)" converts each collection time into an integer (the # of minutes since a fixed reference date)
            - This value is divided by @group_interval_min to get an integer "interval ID".  The query uses this in the GROUP BY clause to group together all collection 
              times that fall within the same time interval.

        DATEADD (minute, (<<interval_id_expression>> * @group_interval_min, ''20000101'') AS interval_start_time
            - This uses (interval number) * (minutes/interval) + (reference date) to generate the time interval's start time 
            - The next column ([interval_end_time]) is the same as the above, except the time is calculated for the following interval ID (an interval's end time is 
              also the start time for the following time interval)
        */

        -- Get the collection times that fall within the specified time window, and compute the time interval ID for each collection time
        CREATE TABLE #snapshots (
            interval_time_id int, 
            interval_start_time datetimeoffset(7), 
            interval_end_time datetimeoffset(7), 
            interval_id int, 
            collection_time datetimeoffset(7), 
            source_id int, 
            snapshot_id int
        )

        -- Dynamic SQL will re-evaluate object permissions -- the current user must have SELECT permission on the target table. 
        DECLARE @sql nvarchar(4000)
        SET @sql = '
        INSERT INTO #snapshots 
        SELECT DISTINCT 
            DATEDIFF (minute, ''20000101'', dataTable.collection_time) / @group_interval_min AS interval_time_id, 
            DATEADD (minute, (DATEDIFF (minute, ''20000101'', dataTable.collection_time) / @group_interval_min) * @group_interval_min, ''20000101'') AS interval_start_time, 
            DATEADD (minute, (DATEDIFF (minute, ''20000101'', dataTable.collection_time) / @group_interval_min + 1) * @group_interval_min, ''20000101'') AS interval_end_time, 
            DENSE_RANK() OVER (ORDER BY DATEDIFF (minute, ''20000101'', dataTable.collection_time) / @group_interval_min) AS interval_id,  
            dataTable.collection_time, s.source_id, s.snapshot_id 
        FROM ' + ISNULL (QUOTENAME (PARSENAME (@TargetCollectionTable, 2)), '[snapshots]') + '.' + QUOTENAME (PARSENAME (@TargetCollectionTable, 1)) + ' AS dataTable
        INNER JOIN core.snapshots AS s ON dataTable.snapshot_id = s.snapshot_id
        WHERE dataTable.collection_time BETWEEN @start_time_internal AND @end_time_internal
            AND s.snapshot_id BETWEEN @start_snapshot_id AND @end_snapshot_id
            AND s.instance_name = @ServerName AND s.collection_set_uid = @CollectionSetUid'
        
        EXEC sp_executesql 
            @sql, 
            N'@ServerName sysname, @CollectionSetUid nvarchar(64), @start_time_internal datetimeoffset(7), @end_time_internal datetimeoffset(7), @group_interval_min int, 
                @start_snapshot_id int, @end_snapshot_id int', 
            @ServerName = @ServerName, @CollectionSetUid = @CollectionSetUid, @start_time_internal = @start_time_internal, @end_time_internal = @end_time_internal, 
            @group_interval_min = @group_interval_min, @start_snapshot_id = @start_snapshot_id, @end_snapshot_id = @end_snapshot_id
        
        
        -- If the caller doesn't care about anything but the interval boundaries, don't bother returning the collection_time/snapshot_id values
        -- that fall in the middle of an interval. 
        IF (@include_snapshot_detail = 0)
        BEGIN
            SELECT interval_time_id, interval_start_time, interval_end_time, interval_id, 
                MIN (collection_time) AS first_collection_time, MAX (collection_time) AS last_collection_time, 
                MIN (snapshot_id) AS first_snapshot_id, MAX (snapshot_id) AS last_snapshot_id, 
                NULL AS source_id, NULL AS snapshot_id, NULL AS collection_time, NULL AS collection_time_id
            FROM #snapshots
            GROUP BY interval_time_id, interval_start_time, interval_end_time, interval_id
            ORDER BY interval_time_id
        END
        ELSE
        BEGIN
            SELECT interval_info.*, #snapshots.source_id, #snapshots.snapshot_id, #snapshots.collection_time, 
                DENSE_RANK() OVER (ORDER BY #snapshots.collection_time) AS collection_time_id
            FROM 
            (
                SELECT interval_time_id, interval_start_time, interval_end_time, interval_id, 
                    MIN (collection_time) AS first_collection_time, MAX (collection_time) AS last_collection_time, 
                    MIN (snapshot_id) AS first_snapshot_id, MAX (snapshot_id) AS last_snapshot_id
                FROM #snapshots
                GROUP BY interval_time_id, interval_start_time, interval_end_time, interval_id
            ) AS interval_info
            INNER JOIN #snapshots ON interval_info.interval_time_id = #snapshots.interval_time_id
            ORDER BY interval_info.interval_time_id, #snapshots.collection_time
        END
    END
    ELSE BEGIN
        /* Invalid parameter %s specified for %s. */
        RAISERROR (21055, 16, -1, @TargetCollectionTable, '@TargetCollectionTable')
    END
    
END;
GO


--
-- snapshots.rpt_next_and_previous_collection_times
--  Helper proc used by other procs that need to return data from N evenly-spaced intervals within a larger user-specified time window
--
--
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @CollectionTime - End of the user-selected time window (UTC)
--    @TargetCollectionTable - Name of the table from which to harvest the collection_time values. Must have a datetimeoffset [collection_time] 
--        column and be in the [snapshots] or [custom_snapshots] schema. 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_next_and_previous_collection_times', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_next_and_previous_collection_times] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_next_and_previous_collection_times]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_next_and_previous_collection_times] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROC [snapshots].[rpt_next_and_previous_collection_times]
    @ServerName sysname, 
    @CollectionTime datetime, 
    @DataGroupID nvarchar(128)
AS
BEGIN
    DECLARE @current_collection_time datetimeoffset(7)  -- current collection time
    DECLARE @current_snapshot_id int                    -- current collection time''s snapshot ID
    DECLARE @previous_collection_time datetimeoffset(7) -- next collection time
    DECLARE @next_collection_time datetimeoffset(7)     -- prior collection time
    DECLARE @snapshot_table sysname                     -- name of the snapshot table we'll be querying
    
    -- The assumed time zone offset for this conversion is +00:00 (datetime must be passed in as UTC)
    SET @current_collection_time = CAST (@CollectionTime AS datetimeoffset(7));
    -- Compensate for RS truncation of fractional seconds 
    SET @current_collection_time = DATEADD(second, 1, @CollectionTime);
    
    -- Currently, we only call this stored procedure from one place, and that code only needs next and prev collection times for 
    -- the snapshots.active_sessions_and_requests table. If, in the future, we need to call this for other tables, the three 
    -- SELECT TOP 1 queries below will need to be converted to dynamic SQL, executed via sp_executesql with OUTPUT parameters.  
    -- The correct target table name (@snapshot_table) should be determined based on the @DataGroupID parameter.  
    IF (@DataGroupID = 'SqlActiveRequests')
    BEGIN
        SET @snapshot_table = 'snapshots.active_sessions_and_requests';
    END
    ELSE BEGIN
        /* Invalid parameter %s specified for %s. */
        RAISERROR (21055, 16, -1, @DataGroupID, '@DataGroupID');
        RETURN;
    END
    
    DECLARE @sql nvarchar(max);

    -- Find our exact collection time using the approx time passed in   
    SELECT TOP 1 @current_collection_time = r.collection_time, @current_snapshot_id = r.snapshot_id
    FROM core.snapshots AS s
    INNER JOIN snapshots.active_sessions_and_requests AS r ON s.snapshot_id = r.snapshot_id
    WHERE s.instance_name = @ServerName
        AND r.collection_time <= @current_collection_time
    ORDER BY collection_time DESC;
    
    -- Find the previous collection time
    SELECT TOP 1 @previous_collection_time = r.collection_time
    FROM core.snapshots AS s
    INNER JOIN snapshots.active_sessions_and_requests AS r ON s.snapshot_id = r.snapshot_id
    WHERE s.instance_name = @ServerName
      AND r.collection_time < @current_collection_time 
    ORDER BY collection_time DESC;
    
    -- Find the next collection time
    SELECT TOP 1 @next_collection_time = r.collection_time
    FROM core.snapshots AS s
    INNER JOIN snapshots.active_sessions_and_requests AS r ON s.snapshot_id = r.snapshot_id
    WHERE s.instance_name = @ServerName
      AND r.collection_time > @current_collection_time 
    ORDER BY collection_time ASC;

    IF @previous_collection_time IS NULL SET @previous_collection_time = @current_collection_time;
    IF @next_collection_time IS NULL SET @next_collection_time = @current_collection_time;

    SELECT 
        CONVERT (datetime, SWITCHOFFSET (@current_collection_time, '+00:00')) AS current_collection_time, 
        @current_snapshot_id AS current_snapshot_id, 
        CONVERT (datetime, SWITCHOFFSET (@previous_collection_time, '+00:00')) AS previous_collection_time, 
        CONVERT (datetime, SWITCHOFFSET (@next_collection_time, '+00:00')) AS next_collection_time;
END
GO


--
-- snapshots.rpt_generic_perfmon
--  Returns perf counter data for the counters associated with a "data group id" (typically, a report name). 
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of selected time window end (UTC)
--    @WindowSize - Number of minutes in the time window
--    @DataGroupID - Name of the report, used to return only the necessary counters
--    @CollectionSetUid - GUID identifier for the target collection set
--    @interval_count - Number of time intervals to divide the time window into (default 40)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_generic_perfmon', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_generic_perfmon] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_generic_perfmon]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_generic_perfmon] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_generic_perfmon]
    @ServerName sysname, 
    @EndTime datetime, 
    @WindowSize int, 
    @DataGroupID nvarchar(128), 
    @CollectionSetUid nvarchar(64), 
    @interval_count int = 40
AS
BEGIN
    SET NOCOUNT ON;
    
    DECLARE @start_time_internal datetimeoffset(7);
    DECLARE @end_time_internal datetimeoffset(7);
    
    -- Start time should be passed in as a UTC datetime
    IF (@EndTime IS NOT NULL)
    BEGIN
        -- Assumed time zone offset for this conversion is +00:00 (datetime must be passed in as UTC)
        SET @end_time_internal = CAST (@EndTime AS datetimeoffset(7));
    END
    ELSE BEGIN
        SELECT @end_time_internal = MAX(snapshot_time)
        FROM core.snapshots 
        WHERE instance_name = @ServerName AND collection_set_uid = @CollectionSetUid
    END
    SET @start_time_internal = DATEADD (minute, -1 * @WindowSize, @end_time_internal);
    
    -- Divide the time window up into N equal intervals.  Each interval will correspond to one 
    -- point on a line chart.  Calc the duration of one interval, in seconds. 
    DECLARE @group_interval_sec int
    IF @interval_count < 1 SET @interval_count = 1
    SET @group_interval_sec = ROUND (DATEDIFF (second, @start_time_internal, @end_time_internal) / @interval_count, 0)
    IF @group_interval_sec < 10 SET @group_interval_sec = 10

    -- For counter groups that include the "Process(abc)\% Processor Time" counter (e.g. 'ServerActivity' and 'SystemCpuUsagePivot'), 
    -- we must determine the logical CPU count on the target system by querying the number of "Processor" counter instances we 
    -- captured in a perfmon sample that was captured around the same time. 
    DECLARE @cpu_count smallint
    SET @cpu_count = 1
    IF EXISTS (
        SELECT * FROM [core].[performance_counter_report_group_items] 
        WHERE counter_group_id = @DataGroupID AND [divide_by_cpu_count] = 1
    )
    BEGIN
        SELECT @cpu_count = COUNT (DISTINCT pc.performance_instance_name)
        FROM snapshots.performance_counters AS pc
        INNER JOIN core.snapshots s ON s.snapshot_id = pc.snapshot_id 
        WHERE pc.performance_object_name = 'Processor' AND pc.performance_counter_name = '% Processor Time'
            AND pc.performance_instance_name != '_Total' AND ISNUMERIC (pc.performance_instance_name) = 1
            AND s.instance_name = @ServerName AND s.collection_set_uid = @CollectionSetUid
            AND s.snapshot_id = 
                (SELECT TOP 1 s2.snapshot_id FROM core.snapshots AS s2 
                INNER JOIN snapshots.performance_counters AS pc2 ON s2.snapshot_id = pc2.snapshot_id 
                WHERE s2.snapshot_time > @start_time_internal
                    AND s2.instance_name = @ServerName AND s2.collection_set_uid = @CollectionSetUid
                    AND pc2.performance_object_name = 'Processor' AND pc2.performance_counter_name = '% Processor Time')
            AND pc.collection_time = 
                (SELECT TOP 1 collection_time FROM snapshots.performance_counter_values pcv2 WHERE pcv2.snapshot_id = s.snapshot_id)
        -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
        OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
        
        IF ISNULL (@cpu_count, 0) = 0
        BEGIN
            -- This message will never be shown on a report. It is included here only as a troubleshooting aid. 
            RAISERROR ('Unable to determine CPU count. Assuming 1 CPU for process CPU calculations', 9, 1)
            SET @cpu_count = 1
        END
    END

    -- Get the matching performance counter instances for this data group
    SELECT 
        pci.*, 
        cl.counter_group_item_id, cl.counter_group_id, cl.counter_subgroup_id, cl.series_name, 
        cl.multiply_by, cl.divide_by_cpu_count
    INTO #pci
    FROM snapshots.performance_counter_instances AS pci
    INNER JOIN [core].[performance_counter_report_group_items] AS cl 
        ON cl.counter_group_id = @DataGroupID
        AND pci.counter_name = cl.counter_name 
        AND ISNULL(pci.instance_name, N'') LIKE cl.instance_name 
        AND 
        (
            (cl.object_name_wildcards = 0 AND pci.[object_name] = cl.[object_name])
            OR (cl.object_name_wildcards = 1 AND pci.[object_name] LIKE cl.[object_name])
        )
        AND (cl.not_instance_name IS NULL OR pci.instance_name NOT LIKE cl.not_instance_name);
    
    -- Get the perfmon counter values for these counters in each time interval. 
    -- NOTE: If you change the schema of this resultset, you must also update the CREATE TABLE in [rpt_generic_perfmon_pivot]. 
    SELECT 
        #pci.counter_subgroup_id, 
        REPLACE (#pci.series_name, '[COUNTER_INSTANCE]', ISNULL(#pci.instance_name, N'')) AS series_name, 
        -- Using our time window end time (@end_time_internal) as a reference point, divide 
        -- the time window into [@interval_count] intervals of [@group_interval_sec] duration 
        -- per interval. 
        DATEDIFF (second, @end_time_internal, SWITCHOFFSET (CONVERT (datetimeoffset(7), pc.collection_time), '+00:00')) / @group_interval_sec AS interval_id, 
        -- Find the end time for the current time interval, and return as a UTC datetime
        -- Do this by converting [collection_time] into a second count, dividing and multiplying 
        -- the count by [@group_interval_sec] to discard any fraction of an interval, then 
        -- converting the second count back into a datetime.  That datetime is the end point for 
        -- the time interval that this [collection_time] value falls within. 
        CONVERT (datetime, 
            DATEADD (
                second, 
                (DATEDIFF (second, @end_time_internal, SWITCHOFFSET (CONVERT (datetimeoffset(7), pc.collection_time), '+00:00')) / @group_interval_sec) * @group_interval_sec, 
                @end_time_internal
            ) 
        ) AS interval_end_time, 
        #pci.counter_name, 
        AVG( pc.formatted_value * #pci.multiply_by / CASE WHEN #pci.divide_by_cpu_count = 1 THEN @cpu_count ELSE 1 END ) AS avg_formatted_value, 
        MAX( pc.formatted_value * #pci.multiply_by / CASE WHEN #pci.divide_by_cpu_count = 1 THEN @cpu_count ELSE 1 END ) AS max_formatted_value, 
        MIN( pc.formatted_value * #pci.multiply_by / CASE WHEN #pci.divide_by_cpu_count = 1 THEN @cpu_count ELSE 1 END ) AS min_formatted_value, 
        -- This column can be used to simulate a "_Total" instance for multi-instance counters that lack _Total -- use a "%" for #counterlist.instance_name
        -- Expression "1.0 * pc.formatted_value * cl.multiply_by / CASE WHEN cl.divide_by_cpu_count = 1 THEN @cpu_count ELSE 1 END" is the counter value. 
        -- Expression "AVG(<counter_value>) * COUNT (DISTINCT pc.performance_instance_name)" returns the simulated "_Total" instance.
        -- Only valid for multi-instance counters that don't already have a "_Total"). 
        CONVERT (bigint, AVG( 1.0 * pc.formatted_value * #pci.multiply_by / CASE WHEN #pci.divide_by_cpu_count = 1 THEN @cpu_count ELSE 1 END)) 
            * COUNT (DISTINCT #pci.instance_name) AS multi_instance_avg_formatted_value
    FROM snapshots.performance_counter_values AS pc
    INNER JOIN #pci ON #pci.performance_counter_id = pc.performance_counter_instance_id
    INNER JOIN core.snapshots s ON s.snapshot_id = pc.snapshot_id
    WHERE s.instance_name = @ServerName AND s.collection_set_uid = @CollectionSetUid
        AND pc.collection_time BETWEEN @start_time_internal AND @end_time_internal
    GROUP BY 
        DATEDIFF (second, @end_time_internal, SWITCHOFFSET (CONVERT (datetimeoffset(7), pc.collection_time), '+00:00')) / @group_interval_sec, #pci.counter_subgroup_id, 
        #pci.counter_name, 
        REPLACE (#pci.series_name, '[COUNTER_INSTANCE]', ISNULL(#pci.instance_name, N''))
    ORDER BY #pci.counter_subgroup_id, interval_end_time, 2, #pci.counter_name
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END
GO


--
-- snapshots.rpt_generic_perfmon_pivot
--  Pivots the output of [rpt_generic_perfmon] so that multiple counter values can be returned in a 
--  single row. The value of all counters with the same [series_name] will be returned as a single row. 
--  The average of all values in the time window is returned
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of selected time window end (UTC)
--    @WindowSize - Number of minutes in the time window
--    @DataGroupID - Name of the calling report (used to retrieve the correct counters)
--    @CollectionSetUid - GUID identifier for the target collection set
--    @interval_count - Number of time intervals to divide the time window into (default 40)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_generic_perfmon_pivot', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_generic_perfmon_pivot] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_generic_perfmon_pivot]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_generic_perfmon_pivot] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_generic_perfmon_pivot]
    @ServerName sysname, 
    @EndTime datetime, 
    @WindowSize int, 
    @DataGroupID nvarchar(128), 
    @CollectionSetUid varchar(64), 
    @interval_count int = 1
AS
BEGIN
    SET NOCOUNT ON;

    CREATE TABLE #countervalues (
        counter_subgroup_id nvarchar(512),          -- name of the data subgroup (e.g. chart or table) that requested the counter (used by a chart to filter out its rows from the larger resultset)
        series_name nvarchar(1024),                 -- chart series label 
        interval_id int,                            -- not used here
        interval_end_time datetime,                 -- not used here
        performance_counter_name nvarchar(2048),    -- perfmon counter name
        avg_formatted_value bigint,                 -- avg perfmon counter value over the time period
        max_formatted_value bigint,                 -- max perfmon counter value over the time period
        min_formatted_value bigint,                 -- min perfmon counter value over the time period
        multi_instance_avg_formatted_value bigint   -- simulated "_Total" instance value for multi-instance counters that lack _Total
    )
    
    SET @DataGroupID = @DataGroupID + 'Pivot'
    INSERT INTO #countervalues
    EXEC [snapshots].[rpt_generic_perfmon] 
        @ServerName, 
        @EndTime, 
        @WindowSize, 
        @DataGroupID, 
        @CollectionSetUid, 
        @interval_count

    IF EXISTS (SELECT * FROM #countervalues)
    BEGIN
        -- @counterlist looks like "[Counter 1], [Counter 2]"
        DECLARE @counterlist nvarchar(max)
        -- @columnlist_min_inner looks like "[Counter 1] AS [Counter 1_min], [Counter 2] AS [Counter 2_min]"
        DECLARE @columnlist_min_inner nvarchar(max)
        -- @columnlist_max_inner looks like "[Counter 1] AS [Counter 1_max], [Counter 2] AS [Counter 2_max]"
        DECLARE @columnlist_max_inner nvarchar(max)
        -- @columnlist_min_outer looks like "[Counter 1_min], [Counter 2_min]"
        DECLARE @columnlist_min_outer nvarchar(max)
        -- @columnlist_max_outer looks like "[Counter 1_max], [Counter 2_max]"
        DECLARE @columnlist_max_outer nvarchar(max)
        SET @counterlist = ''
        SET @columnlist_min_inner = ''
        SET @columnlist_min_outer = ''
        SET @columnlist_max_inner = ''
        SET @columnlist_max_outer = ''
        
        -- Build counter lists
        SELECT 
            @counterlist = @counterlist 
                -- Escape any embedded ']' chars (we can't use QUOTENAME because it can't handle strings > 128 chars)
                + ', [' + REPLACE (performance_counter_name, ']', ']]') + ']'
            , @columnlist_min_outer = @columnlist_min_outer + ', [' + REPLACE (performance_counter_name, ']', ']]') + '_min]'
            , @columnlist_min_inner = @columnlist_min_inner + ', [' + REPLACE (performance_counter_name, ']', ']]') + ']'
                + ' AS [' + REPLACE (performance_counter_name, ']', ']]') + '_min]'
            , @columnlist_max_outer = @columnlist_max_outer + ', [' + REPLACE (performance_counter_name, ']', ']]') + '_max]'
            , @columnlist_max_inner = @columnlist_max_inner + ', [' + REPLACE (performance_counter_name, ']', ']]') + ']'
                + ' AS [' + REPLACE (performance_counter_name, ']', ']]') + '_max]'
        FROM (SELECT DISTINCT performance_counter_name FROM #countervalues) AS t
        GROUP BY performance_counter_name 
        OPTION (MAXDOP 1)
        -- Remove the leading comma
        SET @counterlist = SUBSTRING (@counterlist, 3, LEN (@counterlist)-2)
        SET @columnlist_min_inner = SUBSTRING (@columnlist_min_inner, 3, LEN (@columnlist_min_inner)-2)
        SET @columnlist_min_outer = SUBSTRING (@columnlist_min_outer, 3, LEN (@columnlist_min_outer)-2)
        SET @columnlist_max_inner = SUBSTRING (@columnlist_max_inner, 3, LEN (@columnlist_max_inner)-2)
        SET @columnlist_max_outer = SUBSTRING (@columnlist_max_outer, 3, LEN (@columnlist_max_outer)-2)

        -- We have to use three PIVOTs here because SQL only allows one aggregate function 
        -- per PIVOT, and we need AVG, MIN, and MAX.  They are over a very small temp table, 
        -- though (by default just one row per counter), so the execution cost isn't 
        -- excessive. 
        DECLARE @sql nvarchar(max)
        SET @sql = '
            SELECT avg_pivot.*, ' + @columnlist_min_outer + ', ' + @columnlist_max_outer + '
            FROM 
            (
                SELECT series_name, interval_end_time, ' + @counterlist + '
                FROM 
                (
                    SELECT series_name, interval_end_time, performance_counter_name, avg_formatted_value
                    FROM #countervalues
                ) AS SourceTable
                PIVOT 
                (
                    AVG (avg_formatted_value) 
                    FOR performance_counter_name IN (' + @counterlist + ')
                ) AS PivotTable
            ) AS avg_pivot
            
            INNER JOIN 
            (
                SELECT series_name, interval_end_time, ' + @columnlist_min_inner + '
                FROM 
                (
                    SELECT series_name, interval_end_time, performance_counter_name, min_formatted_value
                    FROM #countervalues
                ) AS SourceTable
                PIVOT 
                (
                    MIN (min_formatted_value) 
                    FOR performance_counter_name IN (' + @counterlist + ')
                ) AS PivotTable
            ) AS min_pivot ON min_pivot.series_name = avg_pivot.series_name AND min_pivot.interval_end_time = avg_pivot.interval_end_time
            
            INNER JOIN 
            (
                SELECT series_name, interval_end_time, ' + @columnlist_max_inner + '
                FROM 
                (
                    SELECT series_name, interval_end_time, performance_counter_name, max_formatted_value
                    FROM #countervalues
                ) AS SourceTable
                PIVOT 
                (
                    MAX (max_formatted_value) 
                    FOR performance_counter_name IN (' + @counterlist + ')
                ) AS PivotTable
            ) AS max_pivot ON max_pivot.series_name = avg_pivot.series_name AND max_pivot.interval_end_time = avg_pivot.interval_end_time
            '
        EXEC sp_executesql @sql
    END
END;
GO


--
-- snapshots.rpt_wait_stats
--  Returns wait time per wait type over a time interval
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of the user-selected time window (UTC)
--    @WindowSize - Number of minutes in the time window 
--    @CategoryName - (Optional) Name of wait category to filter on (all categories if NULL)
--    @WaitType - (Optional) Name of wait type to filter on (all wait types if NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_wait_stats', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_wait_stats] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_wait_stats]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_wait_stats] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_wait_stats]
    @ServerName sysname,
    @EndTime datetime = NULL,
    @WindowSize int,
    @CategoryName nvarchar(20) = NULL, 
    @WaitType nvarchar(45) = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Clean string params (on drillthrough, RS may pass in empty string instead of NULL)
    IF @CategoryName = '' SET @CategoryName = NULL
    IF @WaitType = '' SET @WaitType = NULL

    -- Divide our time window up into 40 evenly-sized time intervals, and find the last collection_time within each of these intervals
    CREATE TABLE #intervals (
        interval_time_id        int, 
        interval_start_time     datetimeoffset(7),
        interval_end_time       datetimeoffset(7),
        interval_id             int, 
        first_collection_time   datetimeoffset(7), 
        last_collection_time    datetimeoffset(7), 
        first_snapshot_id       int,
        last_snapshot_id        int, 
        source_id               int,
        snapshot_id             int, 
        collection_time         datetimeoffset(7), 
        collection_time_id      int
    )
    -- GUID 49268954-... is Server Activity
    INSERT INTO #intervals
    EXEC [snapshots].[rpt_interval_collection_times] 
        @ServerName, @EndTime, @WindowSize, 'snapshots.os_wait_stats', '49268954-4FD4-4EB6-AA04-CD59D9BB5714', 40, 0

    -- Get the earliest and latest snapshot_id values that contain data for the selected time interval. 
    -- This will allow a more efficient query plan. 
    DECLARE @start_snapshot_id int;
    DECLARE @end_snapshot_id int;
    SELECT @start_snapshot_id = MIN (first_snapshot_id)
    FROM #intervals
    SELECT @end_snapshot_id = MAX (last_snapshot_id)
    FROM #intervals
    
    -- Get the wait stats for these collection times
    SELECT 
        coll.interval_time_id, coll.interval_id, 
        last_collection_time AS collection_time, 
        coll.interval_start_time, 
        coll.interval_end_time, 
        coll.last_snapshot_id, 
        wt.category_name, ws.wait_type, ws.waiting_tasks_count, 
        ISNULL (ws.signal_wait_time_ms, 0) AS signal_wait_time_ms, 
        ISNULL (ws.wait_time_ms, 0) - ISNULL (ws.signal_wait_time_ms, 0) AS wait_time_ms, 
        wait_time_ms AS raw_wait_time_ms, 
        ISNULL (ws.wait_time_ms, 0) AS wait_time_ms_cumulative 
    INTO #wait_stats
    FROM snapshots.os_wait_stats AS ws
    INNER JOIN #intervals AS coll ON coll.last_snapshot_id = ws.snapshot_id AND coll.last_collection_time = ws.collection_time 
    INNER JOIN core.wait_types_categorized AS wt ON wt.wait_type = ws.wait_type
    WHERE wt.category_name = ISNULL (@CategoryName, wt.category_name)
        AND wt.wait_type = ISNULL (@WaitType, wt.wait_type)
        AND wt.ignore != 1

    -- Get wait times by waittype for each interval (plus CPU time, modeled as a waittype)
    ---- First get resource wait stats for this interval. We must convert all datetimeoffset values 
    ---- to UTC datetime values before returning to Reporting Services
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (w1.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w1.interval_start_time AS datetimeoffset(7)), '+00:00')) AS interval_start, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.interval_start_time AS datetimeoffset(7)), '+00:00')) AS interval_end, 
        w2.category_name, w2.wait_type, 
        -- All wait stats will be reset to zero by a service cycle, which will cause 
        -- (snapshot2waittime-snapshot1waittime) calculations to produce an incorrect 
        -- negative wait time for the interval.  Detect this and avoid calculating 
        -- negative wait time/wait count/signal time deltas. 
        CASE 
            WHEN (w2.waiting_tasks_count - w1.waiting_tasks_count) < 0 THEN w2.waiting_tasks_count 
            ELSE (w2.waiting_tasks_count - w1.waiting_tasks_count) 
        END AS waiting_tasks_count_delta, 
        CASE 
            WHEN (w2.raw_wait_time_ms - w1.raw_wait_time_ms) < 0 THEN w2.wait_time_ms
            ELSE (w2.wait_time_ms - w1.wait_time_ms)
        END AS resource_wait_time_delta, -- / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) 
        CASE 
            WHEN (w2.signal_wait_time_ms - w1.signal_wait_time_ms) < 0 THEN w2.signal_wait_time_ms 
            ELSE (w2.signal_wait_time_ms - w1.signal_wait_time_ms) 
        END AS resource_signal_time_delta, -- / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        w2.wait_time_ms_cumulative
    -- Self-join - w1 represents wait stats at the beginning of the sample interval, while w2 
    -- shows the wait stats at the end of the interval. 
    FROM #wait_stats AS w1 
    INNER JOIN #wait_stats AS w2 ON w1.wait_type = w2.wait_type AND w1.interval_id = w2.interval_id-1 

    UNION ALL 

    ---- Treat the sum of all signal waits as CPU "wait time"
    SELECT 
        MAX (CONVERT (datetime, SWITCHOFFSET (CAST (w1.collection_time AS datetimeoffset(7)), '+00:00'))) AS collection_time, 
        MIN (CONVERT (datetime, SWITCHOFFSET (CAST (w1.interval_start_time AS datetimeoffset(7)), '+00:00'))) AS interval_start, 
        MAX (CONVERT (datetime, SWITCHOFFSET (CAST (w2.interval_start_time AS datetimeoffset(7)), '+00:00'))) AS interval_end, 
        'CPU' AS category_name, 
        'CPU (Signal Wait)' AS wait_type, 
        0 AS waiting_tasks_count_delta, 
        -- Handle wait stats resets, as in the previous query
        SUM (
            CASE 
                WHEN (w2.signal_wait_time_ms - w1.signal_wait_time_ms) < 0 THEN w2.signal_wait_time_ms
                ELSE (w2.signal_wait_time_ms - w1.signal_wait_time_ms) 
            END -- / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal)
        ) AS resource_wait_time_delta, 
        0 AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        NULL AS wait_time_ms_cumulative
    FROM #wait_stats AS w1
    INNER JOIN #wait_stats AS w2 ON w1.wait_type = w2.wait_type AND w1.interval_id = w2.interval_id-1
    -- Only return CPU stats if we were told to return the 'CPU' category or all categories
    WHERE (@CategoryName IS NULL OR @CategoryName = 'CPU')
    GROUP BY 
        w1.interval_start_time, w2.interval_start_time, w1.interval_end_time, w2.interval_end_time, w1.collection_time, w2.collection_time

    UNION ALL 

    -- Get actual used CPU time from perfmon data (average across the whole snapshot_time_id interval, 
    -- and use this average for each sample time in this interval).  Note that the "% Processor Time" 
    -- counter in the "Process" object can exceed 100% (for example, it can range from 0-800% on an 
    -- 8 CPU server). 
    SELECT
        CONVERT (datetime, SWITCHOFFSET (CAST (w1.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w1.interval_start_time AS datetimeoffset(7)), '+00:00')) AS interval_start, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.interval_start_time AS datetimeoffset(7)), '+00:00')) AS interval_end, 
        'CPU' AS category_name, 
        'CPU (Consumed)' AS wait_type, 
        0 AS waiting_tasks_count_delta, 
        -- Get sqlservr %CPU usage for the perfmon sample that immediately precedes each wait stats sample.  
        -- Multiply by 10 to convert "% CPU" to "ms CPU/sec". This works because (for example) on an 8 proc 
        -- server, Process(...)\% Processor Time ranges from 0 to 800, not 0 to 100.  Multiply again by 
        -- the duration of interval in seconds to get the total ms of CPU time used in the interval. 
        DATEDIFF (second, w1.collection_time, w2.collection_time) * 10 * ( 
            SELECT TOP 1 formatted_value
            FROM snapshots.performance_counters AS pc
            INNER JOIN core.snapshots s ON pc.snapshot_id = s.snapshot_id
            WHERE pc.performance_object_name = 'Process' AND pc.performance_counter_name = '% Processor Time' 
                AND pc.performance_instance_name = '$(TARGETPROCESS)'
                AND pc.collection_time <= w2.collection_time
                AND s.instance_name = @ServerName AND s.collection_set_uid = '49268954-4FD4-4EB6-AA04-CD59D9BB5714' -- Server Activity CS
                AND s.snapshot_id BETWEEN @start_snapshot_id AND @end_snapshot_id 
            ORDER BY pc.collection_time DESC
        ) AS resource_wait_time_delta, 
        0 AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        NULL AS wait_time_ms_cumulative
    FROM #wait_stats AS w1
    INNER JOIN #wait_stats AS w2 ON w1.wait_type = w2.wait_type AND w1.interval_id = w2.interval_id-1
    -- Only return CPU stats if we weren't passed in a specific wait category
    WHERE (@CategoryName IS NULL OR @CategoryName = 'CPU')
    GROUP BY 
        w1.interval_start_time, w2.interval_start_time, w1.interval_end_time, w2.interval_end_time, w1.collection_time, w2.collection_time

    ORDER BY category_name, collection_time, wait_type
    -- These trace flags are necessary for a good plan, due to the join on ascending core.snapshot PK
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390);
    
END
GO


--
-- snapshots.fn_hexstrtovarbin
--  Converts a hex string into a varbinary(max). Reporting Services does not support binary 
--  form parameters, so sql_handle and plan_handle values must be passed to and from RS as strings. 
--  For conversion in the opposite direction, use master.dbo.fn_varbintohexstr. 
-- Parameters: 
--    @hexStr - a string representation of a binary value (e.g. "0x123C2F")
-- Returns: the input value converted to a true varbinary
--
IF (NOT OBJECT_ID (N'[snapshots].[fn_hexstrtovarbin]', 'FN') IS NULL)
BEGIN
    RAISERROR('Dropping function [snapshots].[fn_hexstrtovarbin] ...', 0, 1)  WITH NOWAIT;
    DROP FUNCTION [snapshots].[fn_hexstrtovarbin]
END
GO 

RAISERROR('Creating function [snapshots].[fn_hexstrtovarbin] ...', 0, 1)  WITH NOWAIT;
GO
CREATE FUNCTION [snapshots].[fn_hexstrtovarbin]
(
    @hexStr varchar(max) 
)
RETURNS varbinary(max)
AS
BEGIN
    DECLARE @ret varbinary(max)
    DECLARE @len int

    SET @ret = 0x;
    SET @len = LEN (@hexStr)-2;

    IF (@len >= 0) AND (LEFT (@hexStr, 2) = '0x')
        SET @hexStr = SUBSTRING (@hexStr, 3, @len);
    ELSE
        RETURN NULL;

    DECLARE @leftNibbleChar char(1), @rightNibbleChar char(1), @hexCharStr varchar(2)
    DECLARE @leftNibble int, @rightNibble int
    DECLARE @i int;
    SET @i = 1;
    WHILE (@i <= @len)
    BEGIN
        SET @hexCharStr = SUBSTRING (@hexStr, @i, 2)
        IF LEN (@hexCharStr) = 1 SET @hexCharStr = '0' + @hexCharStr
        SET @leftNibbleChar = LOWER (LEFT (@hexCharStr, 1))
        SET @rightNibbleChar = LOWER (RIGHT (@hexCharStr, 1))

        IF @leftNibbleChar BETWEEN 'a' AND 'f' COLLATE Latin1_General_BIN 
           SET @leftNibble = (CONVERT (int, CONVERT (binary(1), @leftNibbleChar)) - CONVERT (int, CONVERT (binary(1), 'a')) + 10) * 16;
        ELSE IF @leftNibbleChar BETWEEN '0' AND '9' COLLATE Latin1_General_BIN 
           SET @leftNibble = (CONVERT (int, CONVERT (binary(1), @leftNibbleChar)) - CONVERT (int, CONVERT (binary(1), '0'))) * 16;
        ELSE
            RETURN NULL;

        IF @rightNibbleChar BETWEEN 'a' AND 'f' COLLATE Latin1_General_BIN 
           SET @rightNibble = (CONVERT (int, CONVERT (binary(1), @rightNibbleChar)) - CONVERT (int, CONVERT (binary(1), 'a')) + 10);
        ELSE IF @rightNibbleChar  BETWEEN '0' AND '9' COLLATE Latin1_General_BIN 
           SET @rightNibble = (CONVERT (int, CONVERT (binary(1), @rightNibbleChar)) - CONVERT (int, CONVERT (binary(1), '0')));
        ELSE
            RETURN NULL;

        SET @ret = @ret + CONVERT (binary(1), @leftNibble + @rightNibble)
        SET @i = @i + 2
    END

    RETURN @ret
END
GO


--
-- snapshots.rpt_top_query_stats
--  Returns aggregate query stats for the most expensive notable queries observed 
--  over the specified time interval.
--  Returns the top 10 most expensive plans (ranking criteria is specified via @order_by_criteria). 
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--    @order_by_criteria - (Optional) 'CPU' (default), 'Physical Reads', 'Logical Writes', 'I/O' (reads + writes), or 'Duration'
--    @database_name - Optional filter criteria: Only queries within a particular database
--
IF (NOT OBJECT_ID(N'snapshots.rpt_top_query_stats', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_top_query_stats] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_top_query_stats]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_top_query_stats] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_top_query_stats]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint = NULL,
    @time_interval_min smallint = 1, 
    @order_by_criteria varchar(30) = 'CPU', 
    @database_name nvarchar(255) = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Clean string params (on drillthrough, RS may pass in an empty string instead of NULL)
    IF @database_name = '' SET @database_name = NULL

    -- @end_time should never be NULL when we are called from the Query Stats report
    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    DECLARE @interval_sec int;
    SET @interval_sec = DATEDIFF (s, @start_time, @end_time)

    SELECT 
        REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (
            LEFT (LTRIM (stmtsql.query_text), 100)
            , CHAR(9), ' '), CHAR(10), ' '), CHAR(13), ' '), '   ', ' '), '  ', ' '), '  ', ' ') AS flat_query_text, 
        t.*, 
        master.dbo.fn_varbintohexstr (t.sql_handle) AS sql_handle_str, 
        stmtsql.*
    FROM 
    (
        SELECT TOP 10 
            stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, snap.source_id, 
            SUM (stat.snapshot_execution_count) AS execution_count, 
            SUM (stat.snapshot_execution_count) / (@interval_sec / 60) AS executions_per_min, 
            SUM (stat.snapshot_worker_time / 1000) AS total_cpu, 
            SUM (stat.snapshot_worker_time / 1000) / @interval_sec AS avg_cpu_per_sec, 
            SUM (stat.snapshot_worker_time / 1000.0) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_cpu_per_exec, 
            SUM (stat.snapshot_physical_reads) AS total_physical_reads, 
            SUM (stat.snapshot_physical_reads) / @interval_sec AS avg_physical_reads_per_sec, 
            SUM (stat.snapshot_physical_reads) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_physical_reads_per_exec, 
            SUM (stat.snapshot_logical_writes) AS total_logical_writes, 
            SUM (stat.snapshot_logical_writes) / @interval_sec AS avg_logical_writes_per_sec, 
            SUM (stat.snapshot_logical_writes) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_logical_writes_per_exec, 
            SUM (stat.snapshot_elapsed_time / 1000) AS total_elapsed_time, 
            SUM (stat.snapshot_elapsed_time / 1000) / @interval_sec AS avg_elapsed_time_per_sec, 
            SUM (stat.snapshot_elapsed_time / 1000.0) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_elapsed_time_per_exec, 
            COUNT(*) AS row_count, COUNT (DISTINCT stat.creation_time) AS plan_count, 
            SUM (CASE @order_by_criteria WHEN 'Duration' THEN charted_value / 1000 ELSE charted_value END) AS charted_value, 
            -- TODO: Make this "sql.database_name" once database name is available in notable_query_text (VSTS #121662)
            CONVERT (nvarchar(255), '') AS database_name, 
            ROW_NUMBER() OVER (ORDER BY SUM (charted_value) DESC) as query_rank
        FROM 
        (
            SELECT *, 
                CASE @order_by_criteria 
                    WHEN 'CPU' THEN (snapshot_worker_time / 1000.0) / @interval_sec
                    WHEN 'Physical Reads' THEN 1.0 * snapshot_physical_reads / @interval_sec
                    WHEN 'Logical Writes' THEN 1.0 * snapshot_logical_writes / @interval_sec
                    WHEN 'I/O' THEN 1.0 * (snapshot_physical_reads + snapshot_logical_writes) / @interval_sec
                    WHEN 'Duration' THEN snapshot_elapsed_time / 1000.0
                    ELSE (snapshot_worker_time / 1000.0) / @interval_sec
                END AS charted_value
            FROM snapshots.query_stats 
        ) AS stat
        INNER JOIN core.snapshots snap ON stat.snapshot_id = snap.snapshot_id
        -- TODO: Uncomment this and the line in the WHERE clause once database name is available in notable_query_text (VSTS #121662)
        -- LEFT OUTER JOIN snapshots.notable_query_text AS sql ON t.sql_handle = sql.sql_handle and sql.source_id = t.source_id
        WHERE
            snap.instance_name = @instance_name 
            AND snap.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
            --  AND ISNULL (sql.database_name = ISNULL (@database_name, stat.database_name)
        GROUP BY stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, snap.source_id
        ORDER BY ROW_NUMBER() OVER (ORDER BY SUM (charted_value) DESC) ASC
    ) AS t
    LEFT OUTER JOIN snapshots.notable_query_text sql ON t.sql_handle = sql.sql_handle and sql.source_id = t.source_id
    OUTER APPLY snapshots.fn_get_query_text (t.source_id, t.sql_handle, t.statement_start_offset, t.statement_end_offset) AS stmtsql
    ORDER BY query_rank ASC
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END
GO


--
-- snapshots.rpt_query_stats
--  Returns aggregate query stats for all executions of a particular query within a specified time interval
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--    @sql_handle_str - String representation of a SQL handle (e.g. "0x1F27BC...")
--    @statement_start_offset - Start offset (byte count) for the statement within the batch identified by @sql_handle_str
--    @statement_start_offset - End offset (byte count) for the statement within the batch identified by @sql_handle_str
--
IF (NOT OBJECT_ID(N'snapshots.rpt_query_stats', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_query_stats] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_query_stats]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_query_stats] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_query_stats]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint,
    @time_interval_min smallint = 1, 
    @sql_handle_str varchar(130), 
    @statement_start_offset int, 
    @statement_end_offset int
AS
BEGIN
    SET NOCOUNT ON;

    -- @end_time should never be NULL when we are called from the Query Stats report
    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    DECLARE @interval_sec int;
    SET @interval_sec = DATEDIFF (s, @start_time, @end_time);

    DECLARE @sql_handle varbinary(64)
    SET @sql_handle = snapshots.fn_hexstrtovarbin (@sql_handle_str)

    SELECT 
        REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (
            LEFT (LTRIM (stmtsql.query_text), 100)
            , CHAR(9), ' '), CHAR(10), ' '), CHAR(13), ' '), '   ', ' '), '  ', ' '), '  ', ' ') AS flat_query_text, 
        t.*, 
        master.dbo.fn_varbintohexstr (t.sql_handle) AS sql_handle_str, 
        stmtsql.*
    FROM 
    (
        SELECT 
            stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, snap.source_id, 
            SUM (stat.snapshot_execution_count) AS execution_count, 
            SUM (stat.snapshot_execution_count) / (@interval_sec / 60) AS executions_per_min, 
            SUM (stat.snapshot_worker_time / 1000) AS total_cpu, 
            SUM (stat.snapshot_worker_time / 1000) / @interval_sec AS avg_cpu_per_sec, 
            SUM (stat.snapshot_worker_time / 1000.0) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_cpu_per_exec, 
            SUM (stat.snapshot_physical_reads) AS total_physical_reads, 
            SUM (stat.snapshot_physical_reads) / @interval_sec AS avg_physical_reads_per_sec, 
            SUM (stat.snapshot_physical_reads) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_physical_reads_per_exec, 
            SUM (stat.snapshot_logical_writes) AS total_logical_writes, 
            SUM (stat.snapshot_logical_writes) / @interval_sec AS avg_logical_writes_per_sec, 
            SUM (stat.snapshot_logical_writes) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_logical_writes_per_exec, 
            SUM (stat.snapshot_elapsed_time / 1000) AS total_elapsed_time, 
            SUM (stat.snapshot_elapsed_time / 1000) / @interval_sec AS avg_elapsed_time_per_sec, 
            SUM (stat.snapshot_elapsed_time / 1000.0) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_elapsed_time_per_exec, 
            COUNT(*) AS row_count, COUNT(DISTINCT plan_number) AS plan_count
        FROM
        (
            SELECT *, DENSE_RANK() OVER (ORDER BY plan_handle, creation_time) AS plan_number
            FROM snapshots.query_stats
        ) AS stat
        INNER JOIN core.snapshots snap ON stat.snapshot_id = snap.snapshot_id
        WHERE
            snap.instance_name = @instance_name 
            AND stat.sql_handle = @sql_handle 
            AND stat.statement_start_offset = @statement_start_offset 
            AND stat.statement_end_offset = @statement_end_offset
            AND snap.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        GROUP BY stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, snap.source_id
    ) t
    LEFT OUTER JOIN snapshots.notable_query_text sql ON t.sql_handle = sql.sql_handle and sql.source_id = t.source_id
    OUTER APPLY snapshots.fn_get_query_text (t.source_id, t.sql_handle, t.statement_start_offset, t.statement_end_offset) AS stmtsql
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END
GO


--
-- snapshots.rpt_query_plan_stats
--  Returns aggregate stats for the all plans observed for a query within a specified time interval. 
--  Returns the top 10 most expensive plans (ranking criteria is specified via @order_by_criteria). 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--    @sql_handle_str - String representation of a SQL handle (e.g. "0x1F27BC...")
--    @plan_handle_str - String representation of a plan handle (e.g. "0x1F27BC...")
--    @plan_creation_time - (Optional) Plan creation time
--    @statement_start_offset - Start offset (byte count) for the statement within the batch identified by @sql_handle_str
--    @statement_start_offset - End offset (byte count) for the statement within the batch identified by @sql_handle_str
--    @order_by_criteria - (Optional) 'CPU' (default), 'Physical Reads', 'Logical Writes', 'I/O' (reads+writes), or 'Duration'
--
IF (NOT OBJECT_ID(N'snapshots.rpt_query_plan_stats', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_query_plan_stats] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_query_plan_stats]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_query_plan_stats] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_query_plan_stats]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint,
    @time_interval_min smallint = 1, 
    @sql_handle_str varchar(130), 
    @plan_handle_str varchar(130) = NULL, 
    @plan_creation_time datetime = NULL, 
    @statement_start_offset int, 
    @statement_end_offset int, 
    @order_by_criteria varchar(30) = 'CPU'
AS
BEGIN
    SET NOCOUNT ON;

    -- @end_time should never be NULL when we are called from the Query Stats report
    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    DECLARE @interval_sec int;
    SET @interval_sec = DATEDIFF (s, @start_time, @end_time);

    -- SQL and plan handles are passed in as a hex-formatted string. Convert to varbinary. 
    DECLARE @sql_handle varbinary(64), @plan_handle varbinary(64)
    SET @sql_handle = snapshots.fn_hexstrtovarbin (@sql_handle_str)
    IF LEN (@plan_handle_str) > 0
    BEGIN
        SET @plan_handle = snapshots.fn_hexstrtovarbin (@plan_handle_str)
    END

    SELECT 
        t.*, 
        master.dbo.fn_varbintohexstr (t.sql_handle) AS sql_handle_str, 
        master.dbo.fn_varbintohexstr (t.plan_handle) AS plan_handle_str
    FROM 
    (
        SELECT 
            stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, stat.plan_handle, 
            CONVERT (datetime, SWITCHOFFSET (CAST (stat.creation_time AS datetimeoffset(7)), '+00:00')) AS creation_time, 
            CONVERT (varchar, CONVERT (datetime, SWITCHOFFSET (CAST (MAX (stat.creation_time) AS datetimeoffset(7)), '+00:00')), 126) AS creation_time_str, 
            CONVERT (datetime, SWITCHOFFSET (CAST (MAX (stat.last_execution_time) AS datetimeoffset(7)), '+00:00')) AS last_execution_time, 
            snap.source_id, 
            SUM (stat.snapshot_execution_count) AS execution_count, 
            SUM (stat.snapshot_execution_count) / (@interval_sec / 60) AS executions_per_min, 
            SUM (stat.snapshot_worker_time / 1000) AS total_cpu, 
            SUM (stat.snapshot_worker_time / 1000) / @interval_sec AS avg_cpu_per_sec, 
            SUM (stat.snapshot_worker_time / 1000.0) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_cpu_per_exec, 
            SUM (stat.snapshot_physical_reads) AS total_physical_reads, 
            SUM (stat.snapshot_physical_reads) / @interval_sec AS avg_physical_reads_per_sec, 
            SUM (stat.snapshot_physical_reads) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_physical_reads_per_exec, 
            SUM (stat.snapshot_logical_writes) AS total_logical_writes, 
            SUM (stat.snapshot_logical_writes) / @interval_sec AS avg_logical_writes_per_sec, 
            SUM (stat.snapshot_logical_writes) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_logical_writes_per_exec, 
            SUM (stat.snapshot_elapsed_time / 1000) AS total_elapsed_time, 
            SUM (stat.snapshot_elapsed_time / 1000) / @interval_sec AS avg_elapsed_time_per_sec, 
            SUM (stat.snapshot_elapsed_time / 1000.0) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_elapsed_time_per_exec, 
            COUNT(*) AS row_count, COUNT (DISTINCT stat.creation_time) AS plan_count, 
            SUM (charted_value) AS charted_value, 
            ROW_NUMBER() OVER (ORDER BY SUM (charted_value) DESC) as query_rank
        FROM 
        (
            SELECT *, 
                -- This is the criteria used to rank the returned rowset and determine the order within Top-N plans
                -- returned from here. It is important that this part of the query stays in sync with a similar
                -- part of the query in snapshots.rpt_query_plan_stats_timeline procedure
                CASE @order_by_criteria     
                    WHEN 'CPU' THEN ((snapshot_worker_time / 1000.0) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'Physical Reads' THEN 1.0 * (snapshot_physical_reads / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'Logical Writes' THEN 1.0 * (snapshot_logical_writes / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'I/O' THEN 1.0 * ((snapshot_physical_reads + snapshot_logical_writes) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'Duration' THEN ((snapshot_elapsed_time / 1000.0) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    ELSE ((snapshot_worker_time / 1000.0) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                END AS charted_value
            FROM snapshots.query_stats 
            WHERE 
                sql_handle = @sql_handle 
                AND (@plan_handle IS NULL OR plan_handle = @plan_handle) 
                AND (@plan_creation_time IS NULL OR creation_time = @plan_creation_time) 
                AND statement_start_offset = @statement_start_offset 
                AND statement_end_offset = @statement_end_offset
        ) stat
        INNER JOIN core.snapshots snap ON stat.snapshot_id = snap.snapshot_id
        WHERE
            snap.instance_name = @instance_name 
            AND snap.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        GROUP BY stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, stat.plan_handle, 
                stat.creation_time, snap.source_id
    ) t
    WHERE 
        (query_rank <= 10)
    ORDER BY query_rank ASC
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END
GO



--
-- snapshots.rpt_query_plan_stats_timeline
--  Returns stats for the top 10 plans observed for a query within a specified time interval. 
--  Output is intended for plotting this over a time window. 
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--    @sql_handle_str - String representation of a SQL handle (e.g. "0x1F27BC...")
--    @plan_handle_str - (Optional) String representation of a plan handle (e.g. "0x1F27BC..."). Omit to see stats for all plans
--    @plan_creation_time - (Optional) Plan creation time
--    @statement_start_offset - Start offset (byte count) for the statement within the batch identified by @sql_handle_str
--    @statement_start_offset - End offset (byte count) for the statement within the batch identified by @sql_handle_str
--    @order_by_criteria - (Optional) 'CPU' (default), 'Physical Reads', 'Logical Writes', 'I/O' (reads+writes), or 'Duration'
--
IF (NOT OBJECT_ID(N'snapshots.rpt_query_plan_stats_timeline', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_query_plan_stats_timeline] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_query_plan_stats_timeline]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_query_plan_stats_timeline] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_query_plan_stats_timeline]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint,
    @time_interval_min smallint = 1, 
    @sql_handle_str varchar(130), 
    @plan_handle_str varchar(130) = NULL, 
    @plan_creation_time datetime = NULL, 
    @statement_start_offset int, 
    @statement_end_offset int, 
    @order_by_criteria varchar(30) = 'CPU'
AS
BEGIN
    SET NOCOUNT ON;

    -- @end_time should never be NULL when we are called from the Query Stats report
    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    -- SQL and plan handles are passed in as a hex-formatted string. Convert to varbinary. 
    DECLARE @sql_handle varbinary(64), @plan_handle varbinary(64)
    SET @sql_handle = snapshots.fn_hexstrtovarbin (@sql_handle_str)
    IF LEN (ISNULL (@plan_handle_str, '')) > 0
    BEGIN
        SET @plan_handle = snapshots.fn_hexstrtovarbin (@plan_handle_str)
    END


    CREATE TABLE #top_plans (
        plan_handle varbinary(64), 
        creation_time datetimeoffset(7), 
        plan_rank int 
    )

    -- If we weren't told to focus on a particular plan...
    IF (@plan_handle IS NULL)
    BEGIN
        -- Get the top 10 most expensive plans for this query during the specified 
        -- time window. 
        INSERT INTO #top_plans
        SELECT * FROM
        (
            SELECT 
                plan_handle, 
                creation_time, 
                ROW_NUMBER() OVER (ORDER BY SUM (ranking_value) DESC) AS plan_rank 
            FROM 
            (
                SELECT *, 
                -- This is the criteria used to rank the returned rowset and determine the order within Top-N plans
                -- returned from here. It is important that this part of the query stays in sync with a similar
                -- part of the query in snapshots.rpt_query_plan_stats procedure
                CASE @order_by_criteria 
                    WHEN 'CPU' THEN ((snapshot_worker_time / 1000.0) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'Physical Reads' THEN 1.0 * (snapshot_physical_reads / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'Logical Writes' THEN 1.0 * (snapshot_logical_writes / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'I/O' THEN 1.0 * ((snapshot_physical_reads + snapshot_logical_writes) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    WHEN 'Duration' THEN ((snapshot_elapsed_time / 1000.0) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                    ELSE ((snapshot_worker_time / 1000.0) / CASE snapshot_execution_count WHEN 0 THEN 1 ELSE snapshot_execution_count END)
                END AS ranking_value
            FROM snapshots.query_stats 
            WHERE 
                sql_handle = @sql_handle 
                AND statement_start_offset = @statement_start_offset 
                AND statement_end_offset = @statement_end_offset
        ) AS stat
        INNER JOIN core.snapshots snap ON stat.snapshot_id = snap.snapshot_id
        WHERE
            snap.instance_name = @instance_name 
            AND snap.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        GROUP BY plan_handle, creation_time
    ) AS t
    WHERE t.plan_rank <= 10
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390);
    END
    ELSE 
    BEGIN
        -- @plan_handle is not NULL; we have been told to focus on a particular plan. 
        INSERT INTO #top_plans 
        VALUES (@plan_handle, @plan_creation_time, 1)
    END;

    -- Get statistics for these 10 plans for each collection point in the time window
    WITH raw_stat AS 
    (
        SELECT *, 
            CASE @order_by_criteria 
                WHEN 'CPU' THEN snapshot_worker_time / 1000.0
                WHEN 'Physical Reads' THEN snapshot_physical_reads 
                WHEN 'Logical Writes' THEN snapshot_logical_writes 
                WHEN 'I/O' THEN (snapshot_logical_writes + snapshot_physical_reads)
                WHEN 'Duration' THEN snapshot_elapsed_time / 1000.0
                ELSE snapshot_worker_time / 1000.0
            END AS charted_value
        FROM snapshots.query_stats AS stat
    )
    SELECT 
        t.*, 
        master.dbo.fn_varbintohexstr (t.sql_handle) AS sql_handle_str, 
        master.dbo.fn_varbintohexstr (t.plan_handle) AS plan_handle_str
    FROM 
    (
        SELECT 
            stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, stat.plan_handle, 
            CONVERT (datetime, SWITCHOFFSET (CAST (stat.creation_time AS datetimeoffset(7)), '+00:00')) AS creation_time, 
            CONVERT (varchar, CONVERT (datetime, SWITCHOFFSET (CAST (MAX (stat.creation_time) AS datetimeoffset(7)), '+00:00')), 126) AS creation_time_str,             
            CONVERT (datetime, SWITCHOFFSET (CAST (MAX (stat.last_execution_time) AS datetimeoffset(7)), '+00:00')) AS last_execution_time, 
            CONVERT (datetime, SWITCHOFFSET (CAST (stat.collection_time_chart AS datetimeoffset(7)), '+00:00')) AS collection_time, 
            snap.source_id, 
            SUM (stat.snapshot_execution_count) AS execution_count, 
            SUM (stat.snapshot_worker_time / 1000) AS total_cpu, 
            SUM (stat.snapshot_worker_time / 1000) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_cpu_per_exec, 
            SUM (stat.snapshot_physical_reads) AS total_physical_reads, 
            SUM (stat.snapshot_physical_reads) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_physical_reads_per_exec, 
            SUM (stat.snapshot_logical_writes) AS total_logical_writes, 
            SUM (stat.snapshot_logical_writes) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_logical_writes_per_exec, 
            SUM (stat.snapshot_elapsed_time / 1000) AS total_elapsed_time, 
            SUM (stat.snapshot_elapsed_time / 1000) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS avg_elapsed_time_per_exec, 
            COUNT(*) AS row_count, COUNT (DISTINCT stat.creation_time) AS plan_count, 
            SUM (stat.charted_value) AS charted_value, 
            SUM (stat.charted_value) / CASE SUM (stat.snapshot_execution_count) WHEN 0 THEN 1 ELSE SUM (stat.snapshot_execution_count) END AS charted_value_per_exec, 
            MAX (topN.plan_rank) AS plan_rank -- same value for all rows within a group
        FROM (
            -- Work around a RS chart limitation (single data points do not plot on line charts). 
            -- Fake a second data point shortly after the first so even short-lived plans will 
            -- gets plotted. 
            SELECT *, collection_time AS collection_time_chart FROM raw_stat
            UNION ALL
            SELECT *, DATEADD (mi, 1, collection_time) AS collection_time_chart FROM raw_stat
        ) AS stat
        INNER JOIN #top_plans AS topN 
            ON topN.plan_handle = stat.plan_handle AND topN.creation_time = stat.creation_time
        INNER JOIN core.snapshots snap ON stat.snapshot_id = snap.snapshot_id
        WHERE
            stat.sql_handle = @sql_handle 
            AND statement_start_offset = @statement_start_offset 
            AND statement_end_offset = @statement_end_offset
            AND snap.instance_name = @instance_name 
            AND snap.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        GROUP BY stat.sql_handle, stat.statement_start_offset, stat.statement_end_offset, stat.plan_handle, 
                stat.creation_time, snap.source_id, stat.collection_time_chart
    ) AS t
    WHERE 
        (plan_rank <= 10)
    ORDER BY plan_rank ASC, collection_time ASC
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END
GO


--
-- snapshots.rpt_query_plan_missing_indexes
--  Returns any missing indexes recorded in a query plan, formatted as CREATE INDEX statements. 
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @plan_handle_str - String representation of a plan handle (e.g. "0x1F27BC...")
--    @statement_start_offset - Start offset (byte count) for the statement within the plan identified by @plan_handle_str
--    @statement_start_offset - End offset (byte count) for the statement within the plan identified by @plan_handle_str
--
IF (NOT OBJECT_ID(N'snapshots.rpt_query_plan_missing_indexes', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_query_plan_missing_indexes] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_query_plan_missing_indexes]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_query_plan_missing_indexes] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_query_plan_missing_indexes]
    @instance_name sysname, 
    @plan_handle_str varchar(130), 
    @statement_start_offset int, 
    @statement_end_offset int 
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @showplan nvarchar(max)
    DECLARE @plan_handle varbinary(64)
    DECLARE @element nvarchar(512), @element_start nvarchar(512), @element_end nvarchar(512)
    DECLARE @xml_fragment xml
    DECLARE @start_offset int

    -- We may be invoked with a NULL/empty string plan handle if the user has not yet drilled down into 
    -- a specific query plan on the query_stats_detail report. 
    IF ISNULL (@plan_handle_str, '') = '' 
    BEGIN
        RETURN
    END

    SET @plan_handle = snapshots.fn_hexstrtovarbin (@plan_handle_str)

    SELECT TOP 1 
        @showplan = CONVERT (nvarchar(max), qp.query_plan) 
    FROM snapshots.notable_query_plan qp
    INNER JOIN core.snapshots snap ON snap.source_id = qp.source_id 
    WHERE plan_handle = @plan_handle 
        AND statement_start_offset = @statement_start_offset AND statement_end_offset = @statement_end_offset
        AND snap.instance_name = @instance_name
        -- Get sql_handle to enable a clustered index seek on notable_query_plans
        AND qp.sql_handle = 
        (
            SELECT TOP 1 sql_handle 
            FROM snapshots.notable_query_plan AS qp
            INNER JOIN core.snapshots snap ON snap.source_id = qp.source_id 
            WHERE plan_handle = @plan_handle
                AND statement_start_offset = @statement_start_offset AND statement_end_offset = @statement_end_offset 
                AND snap.instance_name = @instance_name
        );

    SET @element = 'MissingIndexes'
    -- Use non-XML methods to extract the <MissingIndexes> fragment.  Some query plans may be too complex 
    -- to represent in the T-SQL xml data type, but the <MissingIndexes> node will always be simple enough. 
    -- Doing this ensures that we will always be able to extract any missing index information from the 
    -- plan even if the plan itself is too complex for the T-SQL xml type. 
    SET @element_start = '<' + @element + '>'
    SET @element_end = '</' + @element + '>'
    SET @start_offset = ISNULL (PATINDEX ('%' + @element_start + '%', @showplan), 0)
    IF @start_offset > 0 
    BEGIN
        SET @xml_fragment = SUBSTRING (@showplan, @start_offset, PATINDEX ('%' + @element_end + '%', @showplan) - @start_offset + LEN (@element_end)); 

        --  Sample <MissingIndexes> fragment from an XML query plan: 
        --    <MissingIndexes>
        --      <MissingIndexGroup Impact="26.4126">
        --        <MissingIndex Database="[AdventureWorks]" Schema="[Sales]" Table="[CustomerAddress]">
        --          <ColumnGroup Usage="EQUALITY">
        --            <Column Name="[AddressTypeID]" ColumnId="3"/>
        --          </ColumnGroup>
        --          <ColumnGroup Usage="INCLUDE">
        --          <Column Name="[CustomerID]" ColumnId="1"/>
        --            <Column Name="[AddressID]" ColumnId="2"/>
        --          </ColumnGroup>
        --        </MissingIndex>
        --      </MissingIndexGroup>
        --    </MissingIndexes>
        SELECT 
            'CREATE INDEX [ncidx_mdw_' 
                + LEFT (target_object_name, 20)
                -- Random component to make name conflicts less likely
                + '_' + CONVERT (varchar(30), ABS (CONVERT (binary(6), NEWID()) % 1000))
                + '] ON ' + target_object_fullname 
                + ' (' + ISNULL (equality_columns, '')
                + CASE WHEN ISNULL (equality_columns, '') != '' AND ISNULL (inequality_columns, '') != '' THEN ',' ELSE '' END + ISNULL (inequality_columns, '')
                + ')'
                + CASE WHEN ISNULL (included_columns, '') != '' THEN ' INCLUDE (' + included_columns + ')' ELSE '' END
                AS create_idx_statement, 
            *
        FROM 
        (
            SELECT 
                index_node.value('(../@Impact)[1]', 'float') as index_impact,
                REPLACE (REPLACE (index_node.value('(./@Table)[1]', 'nvarchar(512)'), '[', ''), ']', '') AS target_object_name,
                CONVERT (nvarchar(1024), index_node.query('concat(
                        string((./@Database)[1]), 
                        ".",
                        string((./@Schema)[1]),
                        ".",
                        string((./@Table)[1])
                    )')) AS target_object_fullname,
                REPLACE (CONVERT (nvarchar(max), index_node.query('for $colgroup in ./ColumnGroup,
                        $col in $colgroup/Column
                        where $colgroup/@Usage = "EQUALITY"
                        return string($col/@Name)')), '] [', '],[') AS equality_columns,
                REPLACE (CONVERT (nvarchar(max), index_node.query('for $colgroup in ./ColumnGroup,
                        $col in $colgroup/Column
                        where $colgroup/@Usage = "INEQUALITY"
                        return string($col/@Name)')), '] [', '],[') AS inequality_columns,
                REPLACE (CONVERT (nvarchar(max), index_node.query('for $colgroup in .//ColumnGroup,
                        $col in $colgroup/Column
                        where $colgroup/@Usage = "INCLUDE"
                        return string($col/@Name)')), '] [', '],[') AS included_columns
            FROM (SELECT @xml_fragment AS fragment) AS missing_indexes_fragment
            CROSS APPLY missing_indexes_fragment.fragment.nodes('/MissingIndexes/MissingIndexGroup/MissingIndex') AS missing_indexes (index_node)
        ) AS t
    END
END
GO

--
-- snapshots.rpt_query_plan_parameters
--  Returns the compile-time parameters that a query plan was optimized for
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @plan_handle_str - String representation of a plan handle (e.g. "0x1F27BC...")
--    @statement_start_offset - Start offset (byte count) for the statement within the plan identified by @plan_handle_str
--    @statement_start_offset - End offset (byte count) for the statement within the plan identified by @plan_handle_str
--
IF (NOT OBJECT_ID(N'snapshots.rpt_query_plan_parameters', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_query_plan_parameters] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].rpt_query_plan_parameters
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_query_plan_parameters] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].rpt_query_plan_parameters
    @instance_name sysname, 
    @plan_handle_str varchar(130), 
    @statement_start_offset int, 
    @statement_end_offset int 
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @showplan xml
    DECLARE @plan_handle varbinary(64)

    -- We may be invoked with a NULL/empty string plan handle if the user has not yet drilled down into 
    -- a specific query plan on the query_stats_detail report. 
    IF ISNULL (@plan_handle_str, '') = '' 
    BEGIN
        RETURN
    END

    SET @plan_handle = snapshots.fn_hexstrtovarbin (@plan_handle_str)

    BEGIN TRY
        SELECT TOP 1 
            @showplan = CONVERT (xml, qp.query_plan) 
        FROM snapshots.notable_query_plan qp
        INNER JOIN core.snapshots snap ON snap.source_id = qp.source_id 
        WHERE plan_handle = @plan_handle 
            AND statement_start_offset = @statement_start_offset AND statement_end_offset = @statement_end_offset 
            AND snap.instance_name = @instance_name
            -- Get sql_handle to enable a clustered index seek on notable_query_plans
            AND qp.sql_handle = 
            (
                SELECT TOP 1 sql_handle 
                FROM snapshots.notable_query_plan AS qp
                INNER JOIN core.snapshots snap ON snap.source_id = qp.source_id 
                WHERE plan_handle = @plan_handle
                    AND statement_start_offset = @statement_start_offset AND statement_end_offset = @statement_end_offset 
                    AND snap.instance_name = @instance_name
            );
    END TRY
    BEGIN CATCH
        -- It is expected that we may end up here even under normal circumstances.  Some plans are simply too 
        -- complex to represent using T-SQL's xml datatype. Raise a low-severity message with the error details. 
        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE();
        -- "Unable to convert showplan to XML.  Error #%d on Line %d: %s"
        RAISERROR (14697, 0, 1, @ErrorNumber, @ErrorLine, @ErrorMessage);
    END CATCH;

    WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS sp)
    SELECT 
        param_list.param_node.value('(./@Column)[1]', 'nvarchar(512)') AS param_name,
        param_list.param_node.value('(./@ParameterCompiledValue)[1]', 'nvarchar(max)') AS param_compiled_value 
    FROM (SELECT @showplan AS query_plan) AS p
    CROSS APPLY p.query_plan.nodes ('/sp:ShowPlanXML/sp:BatchSequence/sp:Batch/sp:Statements/sp:StmtSimple/sp:QueryPlan[1]/sp:ParameterList[1]/sp:ColumnReference') as param_list (param_node)
END
GO


--
-- snapshots.rpt_query_plan_details
--  Returns details of a query plan (estimated cost, compile-time CPU, etc)
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @plan_handle_str - String representation of a plan handle (e.g. "0x1F27BC...")
--    @statement_start_offset - Start offset (byte count) for the statement within the plan identified by @plan_handle_str
--    @statement_start_offset - End offset (byte count) for the statement within the plan identified by @plan_handle_str
--
IF (NOT OBJECT_ID(N'snapshots.rpt_query_plan_details', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_query_plan_details] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].rpt_query_plan_details
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_query_plan_details] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].rpt_query_plan_details
    @instance_name sysname, 
    @plan_handle_str varchar(130), 
    @statement_start_offset int, 
    @statement_end_offset int 
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @showplan xml
    DECLARE @plan_handle varbinary(64)

    -- We may be invoked with a NULL/empty string plan handle if the user has not yet drilled down into 
    -- a specific query plan on the query_stats_detail report. 
    IF ISNULL (@plan_handle_str, '') = '' 
    BEGIN
        RETURN
    END

    SET @plan_handle = snapshots.fn_hexstrtovarbin (@plan_handle_str)

    BEGIN TRY
        SELECT TOP 1 
            @showplan = CONVERT (xml, qp.query_plan) 
        FROM snapshots.notable_query_plan AS qp 
        INNER JOIN core.snapshots snap ON snap.source_id = qp.source_id 
        WHERE plan_handle = @plan_handle 
            AND statement_start_offset = @statement_start_offset AND statement_end_offset = @statement_end_offset 
            AND snap.instance_name = @instance_name 
            -- Get sql_handle to enable a clustered index seek on notable_query_plans
            AND qp.sql_handle = 
            (
                SELECT TOP 1 sql_handle 
                FROM snapshots.notable_query_plan AS qp
                INNER JOIN core.snapshots snap ON snap.source_id = qp.source_id 
                WHERE plan_handle = @plan_handle
                    AND statement_start_offset = @statement_start_offset AND statement_end_offset = @statement_end_offset 
                    AND snap.instance_name = @instance_name
            );
    END TRY
    BEGIN CATCH
        -- It is expected that we may end up here even under normal circumstances.  Some plans are simply too 
        -- complex to represent using T-SQL's xml datatype. Raise a low-severity message with the error details. 
        DECLARE @ErrorMessage   NVARCHAR(4000);
        DECLARE @ErrorNumber    INT;
        DECLARE @ErrorLine      INT;
        SELECT @ErrorLine = ERROR_LINE(),
               @ErrorNumber = ERROR_NUMBER(),
               @ErrorMessage = ERROR_MESSAGE();
        -- "Unable to convert showplan to XML.  Error #%d on Line %d: %s"
        RAISERROR (14697, 0, 1, @ErrorNumber, @ErrorLine, @ErrorMessage);
        RETURN
    END CATCH;

    WITH XMLNAMESPACES ('http://schemas.microsoft.com/sqlserver/2004/07/showplan' AS sp)
    SELECT TOP 10 
        CONVERT (bigint, stmt_simple.stmt_node.value('(./@StatementEstRows)[1]', 'decimal(28,10)')) AS stmt_est_rows, 
        stmt_simple.stmt_node.value('(./@StatementOptmLevel)[1]', 'varchar(30)') AS stmt_optimization_level, 
        stmt_simple.stmt_node.value('(./@StatementOptmEarlyAbortReason)[1]', 'varchar(30)') AS stmt_optimization_early_abort_reason, 
        stmt_simple.stmt_node.value('(./@StatementSubTreeCost)[1]', 'float') AS stmt_est_subtree_cost, 
        stmt_simple.stmt_node.value('(./@StatementText)[1]', 'nvarchar(max)') AS stmt_text, 
        stmt_simple.stmt_node.value('(./@ParameterizedText)[1]', 'nvarchar(max)') AS stmt_parameterized_text, 
        stmt_simple.stmt_node.value('(./@StatementType)[1]', 'varchar(30)') AS stmt_type, 
        stmt_simple.stmt_node.value('(./@PlanGuideName)[1]', 'varchar(30)') AS plan_guide_name, 
        stmt_simple.stmt_node.value('(./sp:QueryPlan/@CachedPlanSize)[1]', 'int') AS plan_size, 
        stmt_simple.stmt_node.value('(./sp:QueryPlan/@CompileTime)[1]', 'bigint') AS plan_compile_time, 
        stmt_simple.stmt_node.value('(./sp:QueryPlan/@CompileCPU)[1]', 'bigint') AS plan_compile_cpu, 
        stmt_simple.stmt_node.value('(./sp:QueryPlan/@CompileMemory)[1]', 'int') AS plan_compile_memory 
    FROM (SELECT @showplan AS query_plan) AS p
    CROSS APPLY p.query_plan.nodes ('/sp:ShowPlanXML/sp:BatchSequence/sp:Batch/sp:Statements/sp:StmtSimple') as stmt_simple (stmt_node)
END
GO


--
-- snapshots.rpt_blocking_chains
--  Returns summary of blocking chains that existed in a specified time window
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - time window start (UTC)
--    @end_time - time window end (UTC)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_blocking_chains', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_blocking_chains] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_blocking_chains]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_blocking_chains] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_blocking_chains]
    @instance_name sysname,
    @start_time datetime = NULL, 
    @end_time datetime, 
    @WindowSize int = NULL
AS
BEGIN
SET NOCOUNT ON;

    -- Compensate for RS truncation of fractional seconds
    SET @end_time = DATEADD (second, 1, @end_time) 

    -- If @start_time is NULL, calc it using @end_time and @WindowSize
    IF @start_time IS NULL SET @start_time = DATEADD (minute, -1 * @WindowSize, @end_time)

    -- Get all collection times for the "Active Sessions and Requests" collection item
    SELECT DISTINCT r1.collection_time, r1.snapshot_id, 
        DENSE_RANK() OVER (ORDER BY r1.collection_time) AS collection_time_id
    INTO #collection_times
    FROM snapshots.active_sessions_and_requests AS r1 
    INNER JOIN core.snapshots s ON s.snapshot_id = r1.snapshot_id 
    WHERE 
        s.instance_name = @instance_name 
        AND r1.collection_time BETWEEN @start_time AND @end_time

    DECLARE @max_collection_time datetimeoffset(7)
    SELECT @max_collection_time = MAX (collection_time) FROM #collection_times

    -- Get all head blockers during the selected time window
    SELECT r1.*, times.collection_time_id
    INTO #blocking_participants 
    FROM #collection_times AS times
    LEFT OUTER JOIN snapshots.active_sessions_and_requests AS r1 
        ON r1.collection_time = times.collection_time AND r1.snapshot_id = times.snapshot_id
    WHERE r1.blocking_session_id = 0
        AND session_id IN (
            SELECT DISTINCT blocking_session_id 
            FROM snapshots.active_sessions_and_requests AS r2 
            WHERE r2.blocking_session_id != 0 AND r2.collection_time = r1.collection_time
                AND r2.snapshot_id = r1.snapshot_id
        )
    CREATE NONCLUSTERED INDEX IDX1_blocking_participants 
    ON #blocking_participants 
    (
        session_id, collection_time_id, blocking_session_id
    )

    -- List all blocking chains during this time window. 
    -- For the purposes of this overview, we define a blocking chain as a contiguous series 
    -- of samples where the same spid remains the head blocker.  Rolling blocking will be viewed 
    -- as multiple discrete chains. 
    SELECT 
        MIN (head_blockers.collection_time) AS blocking_start_time, 
        -- We know when the blocking ended within a (roughly) 10 second window. Assume it stopped approximately 
        -- at the midpoint. 
        DATEADD (second, 5, ISNULL (MAX (blocking_end_times.collection_time), @max_collection_time)) AS blocking_end_time, 
        DATEDIFF (second, MIN (head_blockers.collection_time), ISNULL (MAX (blocking_end_times.collection_time), @max_collection_time)) 
            AS blocking_duration_sec,
        head_blockers.session_id AS head_blocker_session_id, 
        MIN (head_blockers.[program_name]) AS [program_name], 
        MIN (head_blockers.[database_name]) AS [database_name], 
        COUNT(*) AS observed_sample_count,  -- Number of times we saw this blocking chain
        CASE WHEN MAX (blocking_end_times.collection_time) IS NULL THEN 1 ELSE 0 END AS still_active
    INTO #blocking_chains
    FROM 
    (
        SELECT 
            (   -- Find the end time for this blocking incident
                SELECT MIN (collection_time_id)
                FROM #collection_times AS times1
                WHERE times1.collection_time_id > blockers_start.collection_time_id 
                    AND times1.collection_time_id <= @end_time 
                    AND NOT EXISTS (
                        SELECT * FROM #blocking_participants AS blk1
                        WHERE blk1.session_id = blockers_start.session_id 
                            AND blk1.[program_name] = blockers_start.[program_name] 
                            AND blk1.login_time = blockers_start.login_time 
                            AND blk1.collection_time_id = times1.collection_time_id
                            AND blk1.blocking_session_id = 0
                    )
            ) AS blocking_end_collection_time_id, 
            *
        FROM #blocking_participants AS blockers_start 
        WHERE blockers_start.blocking_session_id = 0 
    ) AS head_blockers
    LEFT OUTER JOIN #collection_times AS blocking_end_times
        ON blocking_end_times.collection_time_id = head_blockers.blocking_end_collection_time_id - 1
    GROUP BY head_blockers.session_id, head_blockers.blocking_end_collection_time_id 
    ORDER BY MIN (head_blockers.collection_time)

    -- This proc supports two different chart elements: a table, with one row per blocking 
    -- chain, and a timeline chart, with one series per blocking chain.  The chart must 
    -- return two data points per series in order to correctly plot the blocking chain's 
    -- beginning and end times.  The chart uses the output of both of the following UNIONed 
    -- SELECT statements, while the table filters out the second resultset 
    -- ([chart_data_only]=0). Doing this avoids the need to waste time running two procs 
    -- that are almost identical.  
    SELECT 
        blocking_chain_number, 
        CONVERT (datetime, SWITCHOFFSET (CAST (blocking_start_time AS datetimeoffset(7)), '+00:00')) AS blocking_start_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (blocking_end_time AS datetimeoffset(7)), '+00:00')) AS blocking_end_time, 
        blocking_duration_sec, 
        head_blocker_session_id, 
        [program_name], 
        [database_name], 
        observed_sample_count, 
        still_active, 
        -- Represent this time as a string to avoid RS datetime truncation when the report passes it back to us on drillthrough
        CONVERT (varchar(40), SWITCHOFFSET (CAST (blocking_start_time AS datetimeoffset(7)), '+00:00'), 126) AS blocking_start_time_str, 
        CONVERT (datetime, SWITCHOFFSET (CAST (chart_time AS datetimeoffset(7)), '+00:00')) AS chart_time, 
        chart_data_only
    FROM 
    (
        SELECT TOP 10 ROW_NUMBER() OVER (ORDER BY blocking_duration_sec DESC) AS blocking_chain_number, 
            *, blocking_start_time AS chart_time, 
            0 AS chart_data_only 
        FROM #blocking_chains
        ORDER BY blocking_duration_sec DESC
        UNION ALL 
        SELECT TOP 10 ROW_NUMBER() OVER (ORDER BY blocking_duration_sec DESC) AS blocking_chain_number, 
            *, blocking_end_time AS chart_time, 
            1 AS chart_data_only 
        FROM #blocking_chains
        ORDER BY blocking_duration_sec DESC
    ) AS t
    ORDER BY blocking_chain_number, chart_data_only
END
GO


--
-- snapshots.rpt_blocking_chain_detail
--  Returns details about a blocking chain
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @blocking_time_str - a string representation of a datetimeoffset value during the 
--        period where @head_blocker_session_id was the cause of blocking (UTC)
--    @head_blocker_session_id - session ID (SPID) of the head of the blocker chain
--
IF (NOT OBJECT_ID(N'snapshots.rpt_blocking_chain_detail', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_blocking_chain_detail] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_blocking_chain_detail]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_blocking_chain_detail] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_blocking_chain_detail]
    @instance_name sysname, 
    @blocking_time_str varchar(40), 
    @head_blocker_session_id int
AS
BEGIN
SET NOCOUNT ON;
    DECLARE @blocking_start_time datetimeoffset(7)
    DECLARE @blocking_end_time datetimeoffset(7)
    DECLARE @blocking_time datetimeoffset(7)
    -- The report passed in the blocking time as a string to avoid RS date truncation. 
    -- Convert this to a datetimeoffset value in UTC time.
    SET @blocking_time = SWITCHOFFSET (CAST (@blocking_time_str AS datetimeoffset(7)), '+00:00')

    -- The time that we were passed in may have been in the middle of the blocking incident. 
    -- Find the true start time for this blocking chain. This might be 10 seconds prior, or 
    -- might be days prior.  For perf reasons, search backwards in time one hour at a time 
    -- until we find the start of the blocking incident. 
    DECLARE @blocking_end_snapshot_id int
    DECLARE @blocking_end_source_id int
    DECLARE @hour_count int
    SET @hour_count = -1
    WHILE (@blocking_start_time IS NULL)
    BEGIN
        -- Only if we have more rows left to search for the blocking end time
        IF EXISTS (
            SELECT * 
            FROM snapshots.active_sessions_and_requests AS r 
            INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
            WHERE s.instance_name = @instance_name 
                AND r.collection_time < DATEADD (hour, @hour_count+1, @blocking_time)
        )
        BEGIN
            SELECT TOP 1 @blocking_start_time = r.collection_time 
            FROM snapshots.active_sessions_and_requests AS r 
            INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
            WHERE s.instance_name = @instance_name
                AND r.collection_time BETWEEN DATEADD (hour, @hour_count, @blocking_time) AND @blocking_time 
                AND NOT EXISTS 
                ( 
                    SELECT * 
                    FROM snapshots.active_sessions_and_requests AS r2 
                    WHERE r.snapshot_id = r2.snapshot_id AND r.collection_time = r2.collection_time 
                        AND r2.blocking_session_id = @head_blocker_session_id 
                ) 
            ORDER BY r.collection_time DESC
        END
        ELSE
        BEGIN
            -- We've reached the beginning of the data in the warehouse, and the blocking incident was already 
            -- in-progress at that time. Use the earliest collection time as the approx blocking start time. 
            SELECT @blocking_start_time = ISNULL (DATEADD (second, -10, MIN (r.collection_time)), GETUTCDATE())
            FROM snapshots.active_sessions_and_requests AS r 
            INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
            WHERE s.instance_name = @instance_name
        END
        SET @hour_count = @hour_count - 1
    END
    -- We've found the collection_time just before the blocking began. Get the next collection time, 
    -- which is the first collection time where this blocking incident was detected. 
    SELECT TOP 1 @blocking_start_time = r.collection_time
    FROM snapshots.active_sessions_and_requests AS r 
    INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
    WHERE s.instance_name = @instance_name AND r.collection_time > @blocking_start_time
    ORDER BY r.collection_time ASC

    -- Now find the end of the blocking incident.  Here, again, do an optimistic search in 1-hour blocks. 
    SET @hour_count = 1
    WHILE (@blocking_end_time IS NULL)
    BEGIN
        -- Only if we have more rows left to search for the blocking end time
        IF EXISTS (
            SELECT * 
            FROM snapshots.active_sessions_and_requests AS r 
            INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
            WHERE s.instance_name = @instance_name 
                AND r.collection_time > DATEADD (hour, @hour_count-1, @blocking_time)
        )
        BEGIN
            SELECT TOP 1 @blocking_end_time = r.collection_time 
            FROM snapshots.active_sessions_and_requests AS r 
            INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
            WHERE s.instance_name = @instance_name
                AND r.collection_time BETWEEN DATEADD (hour, @hour_count-1, @blocking_time) AND DATEADD (hour, @hour_count, @blocking_time)
                AND NOT EXISTS 
                ( 
                    SELECT * 
                    FROM snapshots.active_sessions_and_requests AS r2 
                    WHERE r.snapshot_id = r2.snapshot_id AND r.collection_time = r2.collection_time 
                        AND r2.blocking_session_id = @head_blocker_session_id 
                ) 
            ORDER BY r.collection_time ASC
        END
        ELSE
        BEGIN
            -- We've reached the end of the data in the warehouse, and the blocking incident is still 
            -- in-progress. Use the last collection time as the approx blocking end time. 
            SELECT @blocking_end_time = ISNULL (DATEADD (second, 10, MAX (r.collection_time)), GETUTCDATE())
            FROM snapshots.active_sessions_and_requests AS r 
            INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
            WHERE s.instance_name = @instance_name
        END
        SET @hour_count = @hour_count + 1
    END
    -- We've found the collection_time just after before the blocking ended. Get the prior collection time, 
    -- which is the last collection time where the blocking incident was detected. 
    SELECT TOP 1 @blocking_end_time = r.collection_time, @blocking_end_snapshot_id = r.snapshot_id, @blocking_end_source_id = s.source_id
    FROM snapshots.active_sessions_and_requests AS r 
    INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
    WHERE s.instance_name = @instance_name AND r.collection_time < @blocking_end_time
    ORDER BY r.collection_time DESC

    -- DC captures a snapshot of session state every few seconds, which would mean hundreds of samples for 
    -- moderately long-lived blocking chains.  It would be too expensive to summarize the state of the blocking 
    -- chain at every one of these points.  Instead, select 10 evenly-spaced intervals during the blocking 
    -- incident to characterize the changes in the head blocker's state over the blocking period. 
    DECLARE @interval_sec int
    SET @interval_sec = DATEDIFF (second, @blocking_start_time, @blocking_end_time) / 10
    CREATE TABLE #sample_collection_times (snapshot_id int, source_id int, collection_time datetimeoffset(7))
    DECLARE @i int
    SET @i = 0
    WHILE (@i < 9)
    BEGIN
        INSERT INTO #sample_collection_times 
        SELECT TOP 1 r.snapshot_id, s.source_id, r.collection_time
        FROM snapshots.active_sessions_and_requests AS r 
        INNER JOIN core.snapshots AS s ON s.snapshot_id = r.snapshot_id
        WHERE s.instance_name = @instance_name 
            AND r.collection_time BETWEEN DATEADD (second, @interval_sec * @i, @blocking_start_time)
                AND DATEADD (second, @interval_sec * (@i+1)-1, @blocking_start_time)
            -- Only choose collection times where we have info for the head blocker
            AND r.session_id = @head_blocker_session_id 
        ORDER BY r.collection_time ASC
        SET @i = @i + 1
    END
    -- The 10th sample time is always the blocking incident's final collection time
    INSERT INTO #sample_collection_times VALUES (@blocking_end_snapshot_id, @blocking_end_source_id, @blocking_end_time);

    -- Use a recursive CTE to walk the tree of the blocking chain at each of these collection times
    -- and get the state of all the sessions that were part of the tree
    WITH blocking_hierarchy AS  
    (
        -- Head blocker at each of the selected sample times
        SELECT t.collection_time, t.snapshot_id, t.source_id, 0 AS [level], 
            r.session_id, r.request_id, r.exec_context_id, r.request_status, r.command, 
            r.blocking_session_id, r.blocking_exec_context_id, 
            r.wait_type, r.wait_duration_ms, r.wait_resource, r.resource_description, 
            r.login_name, r.login_time, r.[program_name], r.[host_name], r.database_name, 
            r.open_transaction_count, r.transaction_isolation_level, 
            r.request_cpu_time, r.request_total_elapsed_time, r.request_start_time, r.memory_usage, 
            r.session_cpu_time, r.session_total_scheduled_time, r.session_row_count, r.pending_io_count, r.prev_error, 
            r.session_last_request_start_time, r.session_last_request_end_time, r.open_resultsets, 
            r.plan_handle, r.sql_handle, r.statement_start_offset, r.statement_end_offset 
        FROM #sample_collection_times AS t
        INNER JOIN snapshots.active_sessions_and_requests AS r
            ON t.snapshot_id = r.snapshot_id AND t.collection_time = r.collection_time
        WHERE r.session_id = @head_blocker_session_id 
            AND ISNULL (r.exec_context_id, 0) IN (-1, 0) -- for the head blocker, only return the main worker's state
        UNION ALL 
        -- Tasks blocked by the head blocker at the same times
        SELECT r2.collection_time, r2.snapshot_id, parent.source_id, parent.[level] + 1 AS [level], 
            r2.session_id, r2.request_id, r2.exec_context_id, r2.request_status, r2.command, 
            r2.blocking_session_id, r2.blocking_exec_context_id, 
            r2.wait_type, r2.wait_duration_ms, r2.wait_resource, r2.resource_description, 
            r2.login_name, r2.login_time, r2.[program_name], r2.[host_name], r2.database_name, 
            r2.open_transaction_count, r2.transaction_isolation_level, 
            r2.request_cpu_time, r2.request_total_elapsed_time, r2.request_start_time, r2.memory_usage, 
            r2.session_cpu_time, r2.session_total_scheduled_time, r2.session_row_count, r2.pending_io_count, r2.prev_error, 
            r2.session_last_request_start_time, r2.session_last_request_end_time, r2.open_resultsets, 
            r2.plan_handle, r2.sql_handle, r2.statement_start_offset, r2.statement_end_offset 
        FROM snapshots.active_sessions_and_requests AS r2
        INNER JOIN blocking_hierarchy AS parent 
            ON parent.snapshot_id = r2.snapshot_id AND parent.collection_time = r2.collection_time
                AND parent.session_id = r2.blocking_session_id
    )
    SELECT * INTO #blocking_hierarchy 
    FROM blocking_hierarchy 
    
    -- Summarize the state of the head blocker at each of the sample times, along with 
    -- some aggregate stats (# of blocked sessions, etc) describing the blocked sessions
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (@blocking_start_time AS datetimeoffset(7)), '+00:00')) AS blocking_start_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (@blocking_end_time AS datetimeoffset(7)), '+00:00')) AS blocking_end_time, 
        DATEDIFF (second, @blocking_start_time, @blocking_end_time) AS blocking_duration, 
        -- Return these dates as strings to avoid RS date truncation
        CONVERT (varchar(40), SWITCHOFFSET (CAST (@blocking_start_time AS datetimeoffset(7)), '+00:00'), 126) AS blocking_start_time_str, 
        CONVERT (varchar(40), SWITCHOFFSET (CAST (@blocking_end_time AS datetimeoffset(7)), '+00:00'), 126) AS blocking_end_time_str, 
        CONVERT (datetime, SWITCHOFFSET (CAST (blocked.chart_collection_time AS datetimeoffset(7)), '+00:00')) AS chart_collection_time, 
        blocked.chart_only, 
        CONVERT (datetime, SWITCHOFFSET (CAST (blocked.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        blocked.snapshot_id, 
        CONVERT (varchar(40), SWITCHOFFSET (CAST (blocked.collection_time AS datetimeoffset(7)), '+00:00'), 126) AS collection_time_str, 
        @head_blocker_session_id AS head_blocker_session_id, 
        COUNT(DISTINCT blocked.session_id) AS blocked_session_count, 
        SUM (blocked.wait_duration_ms) AS total_wait_time, 
        AVG (blocked.wait_duration_ms) AS avg_wait_time, 
        (
            -- Get the description of the resource owned by the head blocker that 
            -- has caused the most wait time in this blocking chain
            SELECT TOP 1 resource_description
            FROM #blocking_hierarchy bh
            WHERE bh.collection_time = blocked.collection_time AND bh.snapshot_id = blocked.snapshot_id
                AND bh.blocking_session_id = @head_blocker_session_id
            GROUP BY resource_description
            ORDER BY SUM (wait_duration_ms) DESC
        ) AS primary_wait_resource_description, 
        ISNULL (blocker.command, 'AWAITING COMMAND') AS command, blocker.request_status, 
        wt.category_name, blocker.wait_type, blocker.wait_duration_ms, blocker.wait_resource, blocker.resource_description, 
        blocker.[program_name], blocker.[host_name], blocker.login_name, 
        CONVERT (datetime, SWITCHOFFSET (CAST (blocker.login_time AS datetimeoffset(7)), '+00:00')) AS login_time, 
        blocker.database_name, 
        MAX (blocker.open_transaction_count) AS open_transaction_count, MAX (blocker.transaction_isolation_level) AS transaction_isolation_level, 
        MAX (blocker.request_cpu_time) AS request_cpu_time, MAX (blocker.request_total_elapsed_time) AS request_total_elapsed_time, 
        MIN (blocker.request_start_time) AS request_start_time, MAX (blocker.memory_usage) AS memory_usage, 
        MAX (blocker.session_cpu_time) AS session_cpu_time, MAX (blocker.session_total_scheduled_time) AS session_total_scheduled_time, 
        MAX (blocker.session_row_count) AS session_row_count, MAX (blocker.pending_io_count) AS pending_io_count, 
        MAX (blocker.prev_error) AS prev_error, 
        CONVERT (datetime, SWITCHOFFSET (CAST (MAX (blocker.session_last_request_start_time) AS datetimeoffset(7)), '+00:00')) AS session_last_request_start_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (MAX (blocker.session_last_request_end_time) AS datetimeoffset(7)), '+00:00')) AS session_last_request_end_time, 
        MAX (blocker.open_resultsets) AS open_resultsets, 
        blocker.plan_handle, blocker.sql_handle, blocker.statement_start_offset, blocker.statement_end_offset, 
        -- RS can't handle binary values as parameters -- convert plan and sql handles to string types
        master.dbo.fn_varbintohexstr (blocker.plan_handle) AS plan_handle_str, 
        master.dbo.fn_varbintohexstr (blocker.sql_handle) AS sql_handle_str, 
        sql_text.[object_id], sql_text.[object_name], sql_text.query_text, 
        REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (
            LEFT (LTRIM (sql_text.query_text), 100), 
            CHAR(9), ' '), CHAR(10), ' '), CHAR(13), ' '), '   ', ' '), '  ', ' '), '  ', ' ') AS flat_query_text
    -- RS won't plot series that only have a single data point on a line graph.  Duplicate each data 
    -- point with a slight time shift, which will allow the chart to provide some visualization even if 
    -- the blocking was transient (only seen during a single sample). For performance reasons, this 
    -- same resultset feeds both a chart and a table.  The duplicate records are flagged with 
    -- chart_only=1 so that they can be filtered out in the table. 
    FROM (
        SELECT *, collection_time AS chart_collection_time, 0 AS chart_only
        FROM #blocking_hierarchy 
        WHERE blocking_session_id != 0 
        UNION ALL 
        SELECT *, DATEADD (second, 10, collection_time) AS chart_collection_time, 1 AS chart_only 
        FROM #blocking_hierarchy 
        WHERE blocking_session_id != 0 
    ) AS blocked
    INNER JOIN (
        SELECT *, collection_time AS chart_collection_time, 0 AS chart_only
        FROM #blocking_hierarchy 
        WHERE blocking_session_id = 0 
        UNION ALL 
        SELECT *, DATEADD (second, 10, collection_time) AS chart_collection_time, 1 AS chart_only
        FROM #blocking_hierarchy 
        WHERE blocking_session_id = 0 
    ) AS blocker
        ON blocked.collection_time = blocker.collection_time AND blocked.snapshot_id = blocker.snapshot_id
            AND blocked.chart_collection_time = blocker.chart_collection_time 
    OUTER APPLY [snapshots].[fn_get_query_text] (blocker.source_id, blocker.sql_handle, blocker.statement_start_offset, blocker.statement_end_offset) AS sql_text
    LEFT OUTER JOIN core.wait_types_categorized AS wt ON blocker.wait_type = wt.wait_type
    WHERE 
        blocker.blocking_session_id = 0 AND blocked.blocking_session_id != 0
    GROUP BY blocked.chart_collection_time, blocked.chart_only, blocked.collection_time, blocked.snapshot_id, 
        blocker.command, blocker.request_status, 
        wt.category_name, blocker.wait_type, blocker.wait_duration_ms, blocker.wait_resource, blocker.resource_description, 
        blocker.[program_name], blocker.[host_name], blocker.login_name, blocker.login_time, blocker.database_name, 
        blocker.plan_handle, blocker.sql_handle, blocker.statement_start_offset, blocker.statement_end_offset, 
        sql_text.[object_id], sql_text.[object_name], sql_text.query_text  
    ORDER BY blocked.collection_time ASC

END 
GO

--
-- snapshots.rpt_active_sessions_and_requests
--  Returns all the active sessions/requests at a particular moment in time
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @collection_time - a time that corresponds to a specific [collection_time] in snapshots.active_sessions_and_requests (UTC)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_active_sessions_and_requests', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_active_sessions_and_requests] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_active_sessions_and_requests]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_active_sessions_and_requests] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_active_sessions_and_requests]
    @instance_name sysname, 
    @collection_time datetime
AS
BEGIN
SET NOCOUNT ON;
    -- Find the nearest collection time on or before the user-specified time
    DECLARE @current_collection_time datetimeoffset(7)
    DECLARE @current_snapshot_id int
    DECLARE @query_stats_source_id int

    -- Compensate for RS truncation of fractional seconds 
    SET @current_collection_time = DATEADD(second, 1, @collection_time)

    SELECT TOP 1 @current_collection_time = r.collection_time, @current_snapshot_id = r.snapshot_id
    FROM core.snapshots AS s
    INNER JOIN snapshots.active_sessions_and_requests AS r ON s.snapshot_id = r.snapshot_id
    WHERE s.instance_name = @instance_name
      AND r.collection_time <= @current_collection_time
    ORDER BY collection_time DESC

    -- Get the source_id for the Query Stats collection set on this server
    SELECT @query_stats_source_id = s.source_id
    FROM core.snapshots AS s
    WHERE s.instance_name = @instance_name AND s.collection_set_uid = '2DC02BD6-E230-4C05-8516-4E8C0EF21F95'

    -- Get all active sessions/requests at that time
    SELECT 
        r.session_id, r.request_id, r.exec_context_id, ISNULL (r.blocking_session_id, 0) AS blocking_session_id, r.blocking_exec_context_id, 
        r.scheduler_id, r.database_name, r.[user_id], r.task_state, r.request_status, r.session_status, 
        r.executing_managed_code, 
        CONVERT (datetime, SWITCHOFFSET (CAST (r.login_time AS datetimeoffset(7)), '+00:00')) AS login_time, 
        r.is_user_process, r.[host_name], r.[program_name], r.login_name, r.wait_type, r.last_wait_type, 
        r.wait_duration_ms, r.wait_resource, r.resource_description, r.transaction_id, 
        r.open_transaction_count, r.transaction_isolation_level, r.request_cpu_time, 
        r.request_logical_reads, r.request_reads, r.request_writes, r.request_total_elapsed_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (r.request_start_time AS datetimeoffset(7)), '+00:00')) AS request_start_time, 
        r.memory_usage, r.session_cpu_time, r.session_reads, r.session_writes, 
        r.session_logical_reads, r.session_total_scheduled_time, r.session_total_elapsed_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (r.session_last_request_start_time AS datetimeoffset(7)), '+00:00')) AS session_last_request_start_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (r.session_last_request_end_time AS datetimeoffset(7)), '+00:00')) AS session_last_request_end_time, 
        r.open_resultsets, r.session_row_count, r.prev_error, r.pending_io_count, ISNULL (r.command, 'AWAITING COMMAND') AS command, 
        r.plan_handle, r.sql_handle, r.statement_start_offset, r.statement_end_offset, 
        CONVERT (datetime, SWITCHOFFSET (CAST (r.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        r.snapshot_id, 
        wt.category_name AS wait_category, 
        sql.*, 
        master.dbo.fn_varbintohexstr (r.sql_handle) AS sql_handle_str, 
        master.dbo.fn_varbintohexstr (r.plan_handle) AS plan_handle_str, 
        REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (
            LEFT (LTRIM (sql.query_text), 100)
            , CHAR(9), ' '), CHAR(10), ' '), CHAR(13), ' '), '   ', ' '), '  ', ' '), '  ', ' ') AS flat_query_text 
    FROM snapshots.active_sessions_and_requests AS r
    LEFT OUTER JOIN core.wait_types_categorized AS wt ON r.wait_type = wt.wait_type
    OUTER APPLY snapshots.fn_get_query_text (@query_stats_source_id, r.sql_handle, r.statement_start_offset, r.statement_end_offset) AS sql
    WHERE r.snapshot_id = @current_snapshot_id AND r.collection_time = @current_collection_time
END
GO


--
-- snapshots.rpt_sql_memory_clerks
--  Returns data from os_memory_clerks table
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of the user-selected time window (UTC)
--    @WindowSize - Number of minutes in the time window 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sql_memory_clerks', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_sql_memory_clerks] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_sql_memory_clerks]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_sql_memory_clerks] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_sql_memory_clerks]
    @ServerName sysname,
    @EndTime datetime = NULL,
    @WindowSize smallint = NULL
AS
BEGIN
    SET NOCOUNT ON;
    
    -- Divide our time window up into 40 evenly-sized time intervals, and find the last collection_time within each of these intervals
    CREATE TABLE #intervals (
        interval_time_id        int, 
        interval_start_time     datetimeoffset(7),
        interval_end_time       datetimeoffset(7),
        interval_id             int, 
        first_collection_time   datetimeoffset(7), 
        last_collection_time    datetimeoffset(7), 
        first_snapshot_id       int,
        last_snapshot_id        int, 
        source_id               int, 
        snapshot_id             int, 
        collection_time         datetimeoffset(7), 
        collection_time_id      int
    )
    -- GUID 49268954-... is the Server Activity CS
    INSERT INTO #intervals
    EXEC [snapshots].[rpt_interval_collection_times] 
        @ServerName, @EndTime, @WindowSize, 'snapshots.os_memory_clerks', '49268954-4FD4-4EB6-AA04-CD59D9BB5714', 40, 0
    
    -- Get memory clerk stats for these collection times
    SELECT 
        coll.interval_time_id, 
        -- Convert datetimeoffsets to UTC datetimes
        CONVERT (datetime, SWITCHOFFSET (coll.interval_start_time, '+00:00')) AS interval_start_time, 
        CONVERT (datetime, SWITCHOFFSET (coll.interval_end_time,  '+00:00')) AS interval_end_time, 
        coll.interval_id, 
        CONVERT (datetime, SWITCHOFFSET (coll.first_collection_time, '+00:00')) AS first_collection_time, 
        CONVERT (datetime, SWITCHOFFSET (coll.last_collection_time, '+00:00')) AS last_collection_time, 
        coll.first_snapshot_id, coll.last_snapshot_id, 
        mc.[type], mc.memory_node_id, mc.single_pages_kb, mc.multi_pages_kb, mc.virtual_memory_reserved_kb, 
        mc.virtual_memory_committed_kb, mc.awe_allocated_kb, mc.shared_memory_reserved_kb, mc.shared_memory_committed_kb, 
        CONVERT (datetime, SWITCHOFFSET (mc.collection_time, '+00:00')) AS collection_time, 
        CAST (mc.single_pages_kb AS bigint) 
            + mc.multi_pages_kb 
            + (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL' THEN mc.virtual_memory_committed_kb ELSE 0 END) 
            + mc.shared_memory_committed_kb AS total_kb
    INTO #memory_clerks
    FROM snapshots.os_memory_clerks AS mc
    INNER JOIN #intervals AS coll ON coll.last_snapshot_id = mc.snapshot_id AND coll.last_collection_time = mc.collection_time 

    -- Return memory stats to the caller
    SELECT 
        mc.*, 
        mc.single_pages_kb + mc.multi_pages_kb as allocated_kb,
        ta.total_kb_all_clerks, 
        mc.total_kb / CONVERT(decimal, ta.total_kb_all_clerks) AS percent_total_kb,
        -- There are many memory clerks. We'll chart any that make up 5% of SQL memory or more; less significant clerks will be lumped into an "Other" bucket
        CASE
            WHEN mc.total_kb / CONVERT(decimal, ta.total_kb_all_clerks) > 0.05 THEN mc.[type]
            ELSE N'Other'
        END AS graph_type
    FROM #memory_clerks AS mc
    -- Use a self-join to calculate the total memory allocated for each time interval
    JOIN 
    (
        SELECT 
            mc_ta.collection_time, 
            SUM (mc_ta.total_kb) AS total_kb_all_clerks
        FROM #memory_clerks AS mc_ta
        GROUP BY mc_ta.collection_time
    ) AS ta ON (mc.collection_time = ta.collection_time)
    ORDER BY collection_time
END;
GO


--
-- snapshots.rpt_sql_process_and_system_memory
--  Returns system and SQL process memory details over a time interval
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of the user-selected time window (UTC)
--    @WindowSize - Number of minutes in the time window 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sql_process_and_system_memory', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_sql_process_and_system_memory] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_sql_process_and_system_memory]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_sql_process_and_system_memory] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_sql_process_and_system_memory]
    @ServerName sysname,
    @EndTime datetime = NULL,
    @WindowSize int
AS
BEGIN
    SET NOCOUNT ON;

    -- Divide our time window up into 40 evenly-sized time intervals, and find the last collection_time within each of these intervals
    CREATE TABLE #intervals (
        interval_time_id        int, 
        interval_start_time     datetimeoffset(7),
        interval_end_time       datetimeoffset(7),
        interval_id             int, 
        first_collection_time   datetimeoffset(7), 
        last_collection_time    datetimeoffset(7), 
        first_snapshot_id       int,
        last_snapshot_id        int, 
        source_id               int,
        snapshot_id             int, 
        collection_time         datetimeoffset(7), 
        collection_time_id      int
    )
    -- GUID 49268954-... is Server Activity
    INSERT INTO #intervals
    EXEC [snapshots].[rpt_interval_collection_times] 
        @ServerName, @EndTime, @WindowSize, 'snapshots.sql_process_and_system_memory', '49268954-4FD4-4EB6-AA04-CD59D9BB5714', 40, 0

    -- Get the earliest and latest snapshot_id values that contain data for the selected time interval. 
    -- This will allow a more efficient query plan. 
    DECLARE @start_snapshot_id int;
    DECLARE @end_snapshot_id int;
    SELECT @start_snapshot_id = MIN (first_snapshot_id)
    FROM #intervals
    SELECT @end_snapshot_id = MAX (last_snapshot_id)
    FROM #intervals
    
    -- Get sys.dm_os_process_memory for these intervals
    SELECT 
        coll.interval_time_id, coll.interval_id, 
        CONVERT (datetime, SWITCHOFFSET (CAST (coll.last_collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (coll.interval_start_time AS datetimeoffset(7)), '+00:00')) AS interval_start_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (coll.interval_end_time AS datetimeoffset(7)), '+00:00')) AS interval_end_time, 
        coll.last_snapshot_id, 
        AVG (sql_physical_memory_in_use_kb)             AS avg_sql_physical_memory_in_use_kb, 
        MAX (sql_physical_memory_in_use_kb)             AS max_sql_physical_memory_in_use_kb, 
        MIN (sql_physical_memory_in_use_kb)             AS min_sql_physical_memory_in_use_kb, 
        AVG (sql_total_virtual_address_space_kb)        AS avg_sql_total_virtual_address_space_kb, 
        AVG (sql_virtual_address_space_reserved_kb)     AS avg_sql_virtual_address_space_reserved_kb, 
        AVG (sql_virtual_address_space_committed_kb)    AS avg_sql_virtual_address_space_committed_kb, 
        AVG (sql_virtual_address_space_available_kb)    AS avg_sql_virtual_address_space_available_kb, 
        MAX (sql_virtual_address_space_available_kb)    AS max_sql_virtual_address_space_available_kb, 
        MIN (sql_virtual_address_space_available_kb)    AS min_sql_virtual_address_space_available_kb, 
        AVG (sql_memory_utilization_percentage)         AS avg_sql_memory_utilization_percentage, 
        MIN (sql_memory_utilization_percentage)         AS min_sql_memory_utilization_percentage, 
        AVG (sql_available_commit_limit_kb)             AS avg_sql_available_commit_limit_kb, 
        MIN (sql_available_commit_limit_kb)             AS min_sql_available_commit_limit_kb, 
        AVG (sql_large_page_allocations_kb)             AS avg_sql_large_page_allocations_kb, 
        AVG (sql_locked_page_allocations_kb)            AS avg_sql_locked_page_allocations_kb, 
        SUM (CAST (sql_process_physical_memory_low AS int)) AS sql_process_physical_memory_low_count, 
        SUM (CAST (sql_process_virtual_memory_low AS int))  AS sql_process_virtual_memory_low_count, 
        MAX (sql_page_fault_count) - MIN (sql_page_fault_count) AS interval_sql_page_fault_count, 
        AVG (system_total_physical_memory_kb)           AS system_total_physical_memory_kb, 
        AVG (system_available_physical_memory_kb)       AS avg_system_available_physical_memory_kb, 
        MAX (system_available_physical_memory_kb)       AS max_system_available_physical_memory_kb, 
        MIN (system_available_physical_memory_kb)       AS min_system_available_physical_memory_kb, 
        AVG (system_total_page_file_kb)                 AS avg_system_total_page_file_kb, 
        AVG (system_available_page_file_kb)             AS avg_system_available_page_file_kb, 
        MIN (system_available_page_file_kb)             AS min_system_available_page_file_kb, 
        AVG (system_cache_kb)                           AS avg_system_cache_kb, 
        AVG (system_kernel_paged_pool_kb)               AS avg_system_kernel_paged_pool_kb, 
        AVG (system_kernel_nonpaged_pool_kb)            AS avg_system_kernel_nonpaged_pool_kb, 
        SUM (CAST (system_high_memory_signal_state AS int)) AS system_high_memory_signal_state_count, 
        SUM (CAST (system_low_memory_signal_state AS int))  AS system_low_memory_signal_state_count, 
        AVG (bpool_commit_target)                       AS avg_bpool_commit_target, 
        MAX (bpool_commit_target)                       AS max_bpool_commit_target,  
        MIN (bpool_commit_target)                       AS min_bpool_commit_target, 
        AVG (bpool_committed)                           AS avg_bpool_committed, 
        MAX (bpool_committed)                           AS max_bpool_committed,  
        MIN (bpool_committed)                           AS min_bpool_committed, 
        AVG (bpool_visible)                             AS avg_bpool_visible, 
        MAX (bpool_visible)                             AS max_bpool_visible,  
        MIN (bpool_visible)                             AS min_bpool_visible 
    FROM snapshots.sql_process_and_system_memory AS pm
    INNER JOIN #intervals AS coll ON coll.last_snapshot_id = pm.snapshot_id AND coll.last_collection_time = pm.collection_time     
    GROUP BY 
        coll.interval_start_time, coll.interval_end_time, coll.interval_time_id, coll.last_collection_time, coll.interval_id, coll.last_snapshot_id
    ORDER BY coll.last_collection_time ASC;
END
GO


--
-- snapshots.rpt_sampled_waits
--  Returns a list of the apps and queries that spent the most time waiting for the specified wait category. 
--  Note that this is based off data collected from regular samples of sys.dm_exec_requests, 
--  sys.dm_os_waiting_tasks, and related DMVs.  Because these are just samples, it is not expected that 
--  all waits will be captured; at best, a representative sampling will be returned.  
-- 
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - end of the time window (UTC)
--    @WindowSize - number of minutes in the time window
--    @CategoryName - Optional filter criteria: Name of wait category
--    @WaitType - Optional filter criteria: Name of wait type
--    @ProgramName - Optional filter criteria: Application name
--    @SqlHandleStr - Optional filter criteria: Handle to a particular query
--    @StatementStartOffset - Start offset for a particular statement within the batch/proc specified by @SqlHandleStr
--    @StatementEndOffset - End offset for a particular statement within the batch/proc specified by @SqlHandleStr
--    @SessionID - Optional filter criteria: Specific SPID
--    @Database - Optional filter criteria: Waits within a particular database
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sampled_waits', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_sampled_waits] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_sampled_waits]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_sampled_waits] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_sampled_waits]
    @ServerName sysname, 
    @EndTime datetime, 
    @WindowSize int, 
    @CategoryName nvarchar(20) = NULL, 
    @WaitType nvarchar(45) = NULL, 
    @ProgramName nvarchar(50) = NULL, 
    @SqlHandleStr varchar(130) = NULL, 
    @StatementStartOffset int = NULL, 
    @StatementEndOffset int = NULL, 
    @SessionID int = NULL, 
    @Database nvarchar(255) = NULL
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @start_time_internal datetimeoffset(7);
    DECLARE @end_time_internal datetimeoffset(7);

    -- Clean string params (on drillthrough, RS may pass in an empty string instead of NULL)
    IF @CategoryName = '' SET @CategoryName = NULL
    IF @WaitType = '' SET @WaitType = NULL
    IF @ProgramName = '' SET @ProgramName = NULL
    IF @SqlHandleStr = '' SET @SqlHandleStr = NULL
    IF @Database = '' SET @Database = NULL
    -- -1 is a potentially valid offset, but anything less is invalid. RS can't represent NULL in some places, so 
    -- translate int values that are out of range to NULL. 
    IF @StatementStartOffset < -1 SET @StatementStartOffset = NULL
    IF @StatementEndOffset < -1 SET @StatementStartOffset = NULL
    IF @SessionID < -1 SET @SessionID = NULL

    -- NOTE: The logic below is duplicated in snapshots.rpt_sampled_waits_longest.  It cannot be moved to a shared 
    -- child proc because of SQL restrictions (no nested INSERT EXECs, and table params are read only).  
    -- Also update snapshots.rpt_sampled_waits_longest if a change to this section is required. 

    /*** BEGIN DUPLICATED CODE SECTION ***/
        -- Start time should be passed in as a UTC datetime
        IF (@EndTime IS NOT NULL)
        BEGIN
            -- Assumed time zone offset for this conversion is +00:00 (datetime must be passed in as UTC)
            SET @end_time_internal = CAST (@EndTime AS datetimeoffset(7));
        END
        ELSE BEGIN
            SELECT @end_time_internal = MAX(ar.collection_time)
            FROM core.snapshots AS s
            INNER JOIN snapshots.active_sessions_and_requests AS ar ON s.snapshot_id = ar.snapshot_id
            WHERE s.instance_name = @ServerName -- AND collection_set_uid = '49268954-4FD4-4EB6-AA04-CD59D9BB5714' -- Server Activity CS
        END
        SET @start_time_internal = DATEADD (minute, -1 * @WindowSize, @end_time_internal);

        DECLARE @sql_handle varbinary(64)
        IF LEN (@SqlHandleStr) > 0
        BEGIN
            SET @sql_handle = snapshots.fn_hexstrtovarbin (@SqlHandleStr)
        END
        
        -- Divide our time window up into 40 evenly-sized time intervals, and find the first and last collection_time within each of these intervals
        CREATE TABLE #intervals (
            interval_time_id        int, 
            interval_start_time     datetimeoffset(7),
            interval_end_time       datetimeoffset(7),
            interval_id             int, 
            first_collection_time   datetimeoffset(7), 
            last_collection_time    datetimeoffset(7), 
            first_snapshot_id       int,
            last_snapshot_id        int, 
            source_id               int, 
            snapshot_id             int, 
            collection_time         datetimeoffset(7), 
            collection_time_id      int
        )
        INSERT INTO #intervals
        EXEC [snapshots].[rpt_interval_collection_times] 
            @ServerName, @EndTime, @WindowSize, 'snapshots.active_sessions_and_requests', '2dc02bd6-e230-4c05-8516-4e8c0ef21f95', 40, 1

        SELECT 
            ti.interval_id, ti.interval_time_id, ti.interval_start_time, ti.interval_end_time, 
            ti.collection_time, ti.collection_time_id, r.row_id, 
            r.snapshot_id, r.session_id, r.request_id, r.exec_context_id, r.wait_duration_ms, r.wait_resource, 
            r.login_time, r.program_name, r.sql_handle, r.statement_start_offset, r.statement_end_offset, r.plan_handle, 
            r.database_name, r.task_state, ti.source_id, wt.ignore, 
            -- Model CPU as a "wait type" in the sampling results.  Any active request without a wait type is assumed to be CPU-bound. 
            CASE -- same expression here as in the GROUP BY
                WHEN r.wait_type = '' AND r.task_state IS NOT NULL THEN 'CPU'
                ELSE wt.category_name 
            END AS category_name, 
            -- "Running" tasks are actively using the CPU.  A "runnable" task is able to run, but is momentarily waiting for the active 
            -- task to yield so the next runnable task can get scheduled. Map runnable to the SOS_SCHEDULER_YIELD wait type.
            CASE 
                WHEN r.wait_type = '' AND r.task_state = 'RUNNING' THEN 'CPU (Consumed)'
                WHEN r.wait_type = '' AND r.task_state != 'RUNNING' THEN 'SOS_SCHEDULER_YIELD'
                ELSE r.wait_type
            END AS wait_type
        INTO #waiting_tasks
        FROM snapshots.active_sessions_and_requests AS r
        LEFT OUTER JOIN core.wait_types_categorized AS wt ON wt.wait_type = r.wait_type 
        INNER JOIN #intervals AS ti ON r.collection_time = ti.collection_time AND r.snapshot_id = ti.snapshot_id 
        WHERE r.collection_time BETWEEN @start_time_internal AND @end_time_internal 
            AND r.command != 'AWAITING COMMAND' AND r.request_id != -1 -- exclude idle spids (e.g. head blockers)
            AND (r.program_name = @ProgramName OR @ProgramName IS NULL)
            AND (r.sql_handle = @sql_handle OR @SqlHandleStr IS NULL)
            AND (r.database_name = @Database OR @Database IS NULL)
            AND (r.statement_start_offset = @StatementStartOffset OR @SqlHandleStr IS NULL)
            AND (r.statement_end_offset = @StatementEndOffset OR @SqlHandleStr IS NULL)
            AND (r.session_id = @SessionID OR @SessionID IS NULL)
            AND 
            ( -- ... and wait category either matches the user-specified parameter ...
                (@CategoryName = 
                    CASE -- same expression here as in the select column list
                        WHEN r.wait_type = '' AND r.task_state IS NOT NULL THEN 'CPU'
                        ELSE wt.category_name 
                    END
                ) 
                -- ... or a filter parameter for wait category was not provided (return all categories that aren't marked as ignorable). 
                OR (@CategoryName IS NULL AND ISNULL (wt.ignore, 0) = 0)
            )
            AND 
            ( -- ... and wait type either matches the user-specified parameter ...
                (@WaitType = 
                    CASE 
                        WHEN r.wait_type = '' AND r.task_state = 'RUNNING' THEN 'CPU (Consumed)'
                        WHEN r.wait_type = '' AND r.task_state != 'RUNNING' THEN 'SOS_SCHEDULER_YIELD'
                        ELSE r.wait_type
                    END 
                )
                -- ... or a filter parameter for wait category was not provided 
                OR @WaitType IS NULL
            )            
        -- Force a recompile of this statement to take into account the actual values of @start_time_internal and 
        -- @end_time_internal, which were not available at the time of proc compilation. 
        OPTION (RECOMPILE);
        
    /*** END DUPLICATED CODE SECTION ***/
    
    -- Get query text (do this here instead of in the following query so that we don't waste time retrieving the 
    -- same query's text more than once). 
    SELECT 
        r.sql_handle, r.statement_start_offset, r.statement_end_offset, 
        REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (
            LEFT (LTRIM (qt.query_text), 100)
            , CHAR(9), ' '), CHAR(10), ' '), CHAR(13), ' '), '   ', ' '), '  ', ' '), '  ', ' ') AS flat_query_text
    INTO #queries
    FROM 
    (
        SELECT DISTINCT source_id, sql_handle, statement_start_offset, statement_end_offset
        FROM #waiting_tasks
        WHERE category_name IS NOT NULL 
    ) AS r
    OUTER APPLY snapshots.fn_get_query_text(r.source_id, r.sql_handle, r.statement_start_offset, r.statement_end_offset) AS qt

    -- Within each time interval, group the wait counts by waittype, app, db, and query. 
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (r.interval_start_time, '+00:00')) AS interval_start_time, 
        CONVERT (datetime, SWITCHOFFSET (r.interval_end_time, '+00:00')) AS interval_end_time, 
        r.interval_id, r.interval_time_id, 
        r.source_id, r.category_name, r.wait_type, r.[program_name], r.database_name, 
        COUNT (*) AS wait_count, r.sql_handle, r.statement_start_offset, r.statement_end_offset,
        master.dbo.fn_varbintohexstr (r.sql_handle) AS sql_handle_str, 
        q.flat_query_text
    FROM #waiting_tasks AS r 
    LEFT OUTER JOIN #queries AS q ON r.sql_handle = q.sql_handle AND r.statement_start_offset = q.statement_start_offset 
        AND r.statement_end_offset = q.statement_end_offset 
    WHERE r.category_name IS NOT NULL 
    GROUP BY r.interval_start_time, r.interval_end_time, r.interval_id, r.interval_time_id,
        r.category_name, r.wait_type, r.[program_name], r.database_name, r.[sql_handle], 
        r.source_id, r.statement_start_offset, r.statement_end_offset, q.flat_query_text
    ORDER BY r.category_name, r.interval_id, COUNT(*) DESC

END 
GO


--
-- snapshots.rpt_sampled_waits_longest
--  Returns list of the N longest waits that meet user-specified filter criteria.  
--  Note that this is based off data collected from regular samples of sys.dm_exec_requests, 
--  sys.dm_os_waiting_tasks, and related DMVs.  Because these are just samples, it is not expected that 
--  all waits will be captured; at best, a representative sampling will be returned.  
-- 
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - end of the time window (UTC)
--    @WindowSize - number of minutes in the time window
--    @CategoryName - Optional filter criteria: Name of wait category
--    @WaitType - Optional filter criteria: Name of wait type
--    @ProgramName - Optional filter criteria: Application name
--    @SqlHandleStr - Optional filter criteria: Name of the query to filter on
--    @StatementStartOffset - Start offset for a particular statement within the batch/proc specified by @SqlHandleStr
--    @StatementEndOffset - End offset for a particular statement within the batch/proc specified by @SqlHandleStr
--    @SessionID - Optional filter criteria: Specific SPID
--    @Database - Optional filter criteria: Waits within a particular database
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sampled_waits_longest', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_sampled_waits_longest] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_sampled_waits_longest]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_sampled_waits_longest] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_sampled_waits_longest]
    @ServerName sysname, 
    @EndTime datetime, 
    @WindowSize int, 
    @CategoryName nvarchar(20) = NULL, 
    @WaitType nvarchar(45) = NULL, 
    @ProgramName nvarchar(50) = NULL, 
    @SqlHandleStr varchar(130) = NULL, 
    @StatementStartOffset int = NULL, 
    @StatementEndOffset int = NULL, 
    @SessionID int = NULL, 
    @Database nvarchar(255) = NULL
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @start_time_internal datetimeoffset(7);
    DECLARE @end_time_internal datetimeoffset(7);

    -- Clean string params (on drillthrough, RS may pass in an empty string instead of NULL)
    IF @CategoryName = '' SET @CategoryName = NULL
    IF @WaitType = '' SET @WaitType = NULL
    IF @ProgramName = '' SET @ProgramName = NULL
    IF @SqlHandleStr = '' SET @SqlHandleStr = NULL
    IF @Database = '' SET @Database = NULL
    -- -1 is a potentially valid offset, but anything less is invalid. RS can't represent NULL in some places, so 
    -- translate int values that are out of range to NULL. 
    IF @StatementStartOffset < -1 SET @StatementStartOffset = NULL
    IF @StatementEndOffset < -1 SET @StatementStartOffset = NULL
    IF @SessionID < -1 SET @SessionID = NULL

    -- NOTE: The logic below is duplicated in snapshots.rpt_sampled_waits.  It cannot be moved to a shared 
    -- child proc because of SQL restrictions (no nested INSERT EXECs, and table params are read only).  
    -- Also update snapshots.rpt_sampled_waits if a change to this section is required. 

    /*** BEGIN DUPLICATED CODE SECTION ***/
        -- Start time should be passed in as a UTC datetime
        IF (@EndTime IS NOT NULL)
        BEGIN
            -- Assumed time zone offset for this conversion is +00:00 (datetime must be passed in as UTC)
            SET @end_time_internal = CAST (@EndTime AS datetimeoffset(7));
        END
        ELSE BEGIN
            SELECT @end_time_internal = MAX(ar.collection_time)
            FROM core.snapshots AS s
            INNER JOIN snapshots.active_sessions_and_requests AS ar ON s.snapshot_id = ar.snapshot_id
            WHERE s.instance_name = @ServerName -- AND collection_set_uid = '49268954-4FD4-4EB6-AA04-CD59D9BB5714' -- Server Activity CS
        END
        SET @start_time_internal = DATEADD (minute, -1 * @WindowSize, @end_time_internal);

        DECLARE @sql_handle varbinary(64)
        IF LEN (@SqlHandleStr) > 0
        BEGIN
            SET @sql_handle = snapshots.fn_hexstrtovarbin (@SqlHandleStr)
        END
        
        -- Divide our time window up into 40 evenly-sized time intervals, and find the first and last collection_time within each of these intervals
        CREATE TABLE #intervals (
            interval_time_id        int, 
            interval_start_time     datetimeoffset(7),
            interval_end_time       datetimeoffset(7),
            interval_id             int, 
            first_collection_time   datetimeoffset(7), 
            last_collection_time    datetimeoffset(7), 
            first_snapshot_id       int,
            last_snapshot_id        int, 
            source_id               int, 
            snapshot_id             int, 
            collection_time         datetimeoffset(7), 
            collection_time_id      int
        )
        INSERT INTO #intervals
        EXEC [snapshots].[rpt_interval_collection_times] 
            @ServerName, @EndTime, @WindowSize, 'snapshots.active_sessions_and_requests', '2dc02bd6-e230-4c05-8516-4e8c0ef21f95', 40, 1

        SELECT 
            ti.interval_id, ti.interval_time_id, ti.interval_start_time, ti.interval_end_time, 
            ti.collection_time, ti.collection_time_id, r.row_id, 
            r.snapshot_id, r.session_id, r.request_id, r.exec_context_id, r.wait_duration_ms, r.wait_resource, 
            r.login_time, r.program_name, r.sql_handle, r.statement_start_offset, r.statement_end_offset, r.plan_handle, 
            r.database_name, r.task_state, ti.source_id, wt.ignore, 
            -- Model CPU as a "wait type" in the sampling results.  Any active request without a wait type is assumed to be CPU-bound. 
            CASE -- same expression here as in the GROUP BY
                WHEN r.wait_type = '' AND r.task_state IS NOT NULL THEN 'CPU'
                ELSE wt.category_name 
            END AS category_name, 
            -- "Running" tasks are actively using the CPU.  A "runnable" task is able to run, but is momentarily waiting for the active 
            -- task to yield so the next runnable task can get scheduled. Map runnable to the SOS_SCHEDULER_YIELD wait type.
            CASE 
                WHEN r.wait_type = '' AND r.task_state = 'RUNNING' THEN 'CPU (Consumed)'
                WHEN r.wait_type = '' AND r.task_state != 'RUNNING' THEN 'SOS_SCHEDULER_YIELD'
                ELSE r.wait_type
            END AS wait_type
        INTO #waiting_tasks
        FROM snapshots.active_sessions_and_requests AS r
        LEFT OUTER JOIN core.wait_types_categorized AS wt ON wt.wait_type = r.wait_type 
        INNER JOIN #intervals AS ti ON r.collection_time = ti.collection_time AND r.snapshot_id = ti.snapshot_id 
        WHERE r.collection_time BETWEEN @start_time_internal AND @end_time_internal 
            AND r.command != 'AWAITING COMMAND' AND r.request_id != -1 -- exclude idle spids (e.g. head blockers)
            AND (r.program_name = @ProgramName OR @ProgramName IS NULL)
            AND (r.sql_handle = @sql_handle OR @SqlHandleStr IS NULL)
            AND (r.database_name = @Database OR @Database IS NULL)
            AND (r.statement_start_offset = @StatementStartOffset OR @SqlHandleStr IS NULL)
            AND (r.statement_end_offset = @StatementEndOffset OR @SqlHandleStr IS NULL)
            AND (r.session_id = @SessionID OR @SessionID IS NULL)
            AND 
            ( -- ... and wait category either matches the user-specified parameter ...
                (@CategoryName = 
                    CASE -- same expression here as in the select column list
                        WHEN r.wait_type = '' AND r.task_state IS NOT NULL THEN 'CPU'
                        ELSE wt.category_name 
                    END
                ) 
                -- ... or a filter parameter for wait category was not provided (return all categories that aren't marked as ignorable). 
                OR (@CategoryName IS NULL AND ISNULL (wt.ignore, 0) = 0)
            )
            AND 
            ( -- ... and wait type either matches the user-specified parameter ...
                (@WaitType = 
                    CASE 
                        WHEN r.wait_type = '' AND r.task_state = 'RUNNING' THEN 'CPU (Consumed)'
                        WHEN r.wait_type = '' AND r.task_state != 'RUNNING' THEN 'SOS_SCHEDULER_YIELD'
                        ELSE r.wait_type
                    END 
                )
                -- ... or a filter parameter for wait category was not provided 
                OR @WaitType IS NULL
            )
        -- Force a recompile of this statement to take into account the actual values of @start_time_internal and 
        -- @end_time_internal, which were not available at the time of proc compilation. 
        OPTION (RECOMPILE);
        
    /*** END DUPLICATED CODE SECTION ***/

    CREATE INDEX idx1 ON #waiting_tasks (collection_time_id, session_id)

    -- The same long-lived wait may be captured multiple times.  For a given wait, we only care about the max 
    -- wait time within the target time window. If we see that the immediately following sample shows the same 
    -- spid waiting with a wait time that spans both samples, we discard the preceding sample and keep only 
    -- the max wait time.  For example, in the data below, spid 54 was stuck in the same long-lived lock wait 
    -- from row 1 through row 3.  From this data, we would only keep rows #3 and #4. Rows #1 and #2 are 
    -- discarded to avoid reporting the same long wait several times. 
    -- 
    --     (row#)   collection_time     session_id  wait_type    wait_duration_ms  
    --              ------------------- ----------- ------------ -----------------
    --       1      2007-10-25 13:01:00          53 LCK_M_S                   8141
    --       2      2007-10-25 13:01:10          53 LCK_M_S                  18278
    --       3      2007-10-25 13:01:20          53 LCK_M_S                  28318
    --       4      2007-10-25 13:01:30          54 LCK_M_X                    755
    --
    -- Also, if there was one collection time where everyone was blocked momentarily, we want to avoid 
    -- reporting that collection time over and over w/only the spid # varying; that's not the most interesting 
    -- sampling.  Instead, report the longest wait in each of the top 10 collection times (top 10 times by max 
    -- wait duration at the collection time).  
    SELECT TOP 10
        CONVERT (datetime, SWITCHOFFSET (r1.collection_time, '+00:00')) AS collection_time, 
        CONVERT (varchar(30), CONVERT (datetime, SWITCHOFFSET (r1.collection_time, '+00:00')), 126) AS collection_time_str, 
        r1.snapshot_id, r1.row_id, 
        r1.session_id, r1.request_id, r1.exec_context_id, r1.wait_resource, 
        r1.source_id, r1.category_name, r1.wait_type, r1.wait_duration_ms, 
        r1.database_name, r1.[program_name], r1.[sql_handle], r1.statement_start_offset, r1.statement_end_offset, r1.plan_handle
    FROM #waiting_tasks AS r1
    -- Find the same spid in the next collection time
    LEFT OUTER JOIN #waiting_tasks AS r2 
        ON r2.collection_time_id = r1.collection_time_id + 1 AND r2.session_id = r1.session_id 
            AND r2.request_id = r1.request_id AND r2.exec_context_id = r1.exec_context_id AND r2.login_time = r1.login_time 
    WHERE 
        -- Prevent reporting the same wait spanning multiple collection times. 
        (   
            -- ... where the spid wasn't seen waiting at the next collection time
            r2.session_id IS NULL 
            -- ... or the wait at the next collection time is shorter than it would have been if the wait had spanned both collection times 
            OR r2.wait_duration_ms < (DATEDIFF (second, r1.collection_time, r2.collection_time) * 1000)
        )
        -- Exclude all but the longest wait in any given collection time
        AND NOT EXISTS 
        (
            SELECT * FROM #waiting_tasks AS r3
            WHERE r3.collection_time_id = r1.collection_time_id 
                AND r3.wait_duration_ms > r1.wait_duration_ms 
                OR (r3.wait_duration_ms = r1.wait_duration_ms AND r3.row_id > r1.row_id)
        )
    ORDER BY r1.wait_duration_ms DESC
   
END
GO


--
-- snapshots.rpt_sampled_waits_hottest_resources
--  Returns list of the N longest waits that meet user-specified filter criteria.  
--  Note that this is based off data collected from regular samples of sys.dm_exec_requests, 
--  sys.dm_os_waiting_tasks, and related DMVs.  Because these are just samples, it is not expected that 
--  all waits will be captured; at best, a representative sampling will be returned.  
-- 
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - end of the time window (UTC)
--    @WindowSize - number of minutes in the time window
--    @CategoryName - Optional filter criteria: Name of wait category
--    @WaitType - Optional filter criteria: Name of wait type
--    @ProgramName - Optional filter criteria: Application name
--    @SqlHandleStr - Optional filter criteria: Name of the query to filter on
--    @StatementStartOffset - Start offset for a particular statement within the batch/proc specified by @SqlHandleStr
--    @StatementEndOffset - End offset for a particular statement within the batch/proc specified by @SqlHandleStr
--    @SessionID - Option filter criteria: Specific SPID
--    @Database - Optional filter criteria: Waits within a particular database
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sampled_waits_hottest_resources', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_sampled_waits_hottest_resources] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_sampled_waits_hottest_resources]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_sampled_waits_hottest_resources] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_sampled_waits_hottest_resources]
    @ServerName sysname, 
    @EndTime datetime, 
    @WindowSize int, 
    @CategoryName nvarchar(20) = NULL, 
    @WaitType nvarchar(45) = NULL, 
    @ProgramName nvarchar(50) = NULL, 
    @SqlHandleStr varchar(130) = NULL, 
    @StatementStartOffset int = NULL, 
    @StatementEndOffset int = NULL, 
    @SessionID int = NULL, 
    @Database nvarchar(255) = NULL
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @start_time_internal datetimeoffset(7);
    DECLARE @end_time_internal datetimeoffset(7);

    -- Clean string params (on drillthrough, RS may pass in an empty string instead of NULL)
    IF @CategoryName = '' SET @CategoryName = NULL
    IF @WaitType = '' SET @WaitType = NULL
    IF @ProgramName = '' SET @ProgramName = NULL
    IF @SqlHandleStr = '' SET @SqlHandleStr = NULL
    IF @Database = '' SET @Database = NULL
    -- -1 is a potentially valid offset, but anything less is invalid. RS can't represent NULL in some places, so 
    -- translate int values that are out of range to NULL. 
    IF @StatementStartOffset < -1 SET @StatementStartOffset = NULL
    IF @StatementEndOffset < -1 SET @StatementStartOffset = NULL
    IF @SessionID < -1 SET @SessionID = NULL

    -- Start time should be passed in as a UTC datetime
    IF (@EndTime IS NOT NULL)
    BEGIN
        -- Assumed time zone offset for this conversion is +00:00 (datetime must be passed in as UTC)
        SET @end_time_internal = CAST (@EndTime AS datetimeoffset(7));
    END
    ELSE BEGIN
        SELECT @end_time_internal = MAX(ar.collection_time)
        FROM core.snapshots AS s
        INNER JOIN snapshots.active_sessions_and_requests AS ar ON s.snapshot_id = ar.snapshot_id
        WHERE s.instance_name = @ServerName -- AND collection_set_uid = '49268954-4FD4-4EB6-AA04-CD59D9BB5714' -- Server Activity CS
    END
    SET @start_time_internal = DATEADD (minute, -1 * @WindowSize, @end_time_internal);

    DECLARE @sql_handle varbinary(64)
    IF LEN (@SqlHandleStr) > 0
    BEGIN
        SET @sql_handle = snapshots.fn_hexstrtovarbin (@SqlHandleStr)
    END
    
    SELECT TOP 10 
        wt.category_name, r.wait_type, 
        CASE 
            WHEN LEN (ISNULL (r.wait_resource, '')) = 0 THEN 
                CASE 
                    WHEN LEN (r.resource_description) > 30 THEN LEFT (r.resource_description, 27) + '...'
                    ELSE LEFT (r.resource_description, 30)
                END
            ELSE r.wait_resource
        END AS wait_resource, 
        r.resource_description, 
        CONVERT (datetime, SWITCHOFFSET (MAX (r.collection_time), '+00:00')) AS example_collection_time, 
        CONVERT (varchar(30), CONVERT (datetime, SWITCHOFFSET (MAX (r.collection_time), '+00:00')), 126) AS example_collection_time_str, 
        COUNT(*) AS wait_count
    FROM snapshots.active_sessions_and_requests AS r 
    LEFT OUTER JOIN core.wait_types_categorized AS wt ON wt.wait_type = r.wait_type 
    WHERE r.collection_time BETWEEN @start_time_internal AND @end_time_internal 
        AND r.command != 'AWAITING COMMAND' AND r.request_id != -1 -- exclude idle spids (e.g. head blockers)
        AND (r.program_name = @ProgramName OR @ProgramName IS NULL)
        AND (r.sql_handle = @sql_handle OR @SqlHandleStr IS NULL)
        AND (r.database_name = @Database OR @Database IS NULL)
        AND (r.statement_start_offset = @StatementStartOffset OR @SqlHandleStr IS NULL)
        AND (r.statement_end_offset = @StatementEndOffset OR @SqlHandleStr IS NULL)
        AND (r.session_id = @SessionID OR @SessionID IS NULL)
        AND 
        ( -- ... and wait category either matches the user-specified parameter ...
            (@CategoryName = 
                CASE -- same expression here as in the select column list
                    WHEN r.wait_type = '' AND r.task_state IS NOT NULL THEN 'CPU'
                    ELSE wt.category_name 
                END
            ) 
            -- ... or a filter parameter for wait category was not provided (return all categories that aren't marked as ignorable). 
            OR (@CategoryName IS NULL AND ISNULL (wt.ignore, 0) = 0)
        )
        AND 
        ( -- ... and wait type either matches the user-specified parameter ...
            (@WaitType = 
                CASE 
                    WHEN r.wait_type = '' AND r.task_state = 'RUNNING' THEN 'CPU (Consumed)'
                    WHEN r.wait_type = '' AND r.task_state != 'RUNNING' THEN 'SOS_SCHEDULER_YIELD'
                    ELSE r.wait_type
                END 
            )
            -- ... or a filter parameter for wait category was not provided 
            OR @WaitType IS NULL
        )
        -- Exclude rows where there is no named resource
        AND (ISNULL (r.resource_description, '') != '' OR ISNULL (r.wait_resource, '') != '')
    GROUP BY wt.category_name, r.wait_type, r.wait_resource, r.resource_description
    ORDER BY COUNT(*) DESC
    -- Force a recompile of this statement to take into account the actual values of @start_time_internal and 
    -- @end_time_internal, which were not available at the time of proc compilation. 
    OPTION (RECOMPILE);
   
END
GO


--
-- snapshots.rpt_io_virtual_file_stats
--  Returns wait time per wait type over a time interval
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of the user-selected time window (UTC)
--    @WindowSize - Number of minutes in the time window 
--    @CategoryName - (Optional) Name of wait category to filter on (all categories if NULL)
--    @WaitType - (Optional) Name of wait type to filter on (all wait types if NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_io_virtual_file_stats', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_io_virtual_file_stats] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_io_virtual_file_stats]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_io_virtual_file_stats] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_io_virtual_file_stats]
    @ServerName sysname,
    @EndTime datetime = NULL,
    @WindowSize int,
    @LogicalDisk nvarchar(255) = NULL, 
    @Database nvarchar(255) = NULL 
AS
BEGIN
    SET NOCOUNT ON;

    -- Clean string params (on drillthrough, RS may pass in empty string instead of NULL)
    IF @LogicalDisk = '' SET @LogicalDisk = NULL
    IF @Database = '' SET @Database = NULL

    -- Divide our time window up into 40 evenly-sized time intervals, and find the last collection_time within each of these intervals
    CREATE TABLE #intervals (
        interval_time_id        int, 
        interval_start_time     datetimeoffset(7),
        interval_end_time       datetimeoffset(7),
        interval_id             int, 
        first_collection_time   datetimeoffset(7), 
        last_collection_time    datetimeoffset(7), 
        first_snapshot_id       int,
        last_snapshot_id        int, 
        source_id               int,
        snapshot_id             int, 
        collection_time         datetimeoffset(7), 
        collection_time_id      int
    )
    -- GUID 49268954-... is Server Activity
    INSERT INTO #intervals
    EXEC [snapshots].[rpt_interval_collection_times] 
        @ServerName, @EndTime, @WindowSize, 'snapshots.io_virtual_file_stats', '49268954-4FD4-4EB6-AA04-CD59D9BB5714', 40, 0

    -- Get the earliest and latest snapshot_id values that contain data for the selected time interval. 
    -- This will allow a more efficient query plan. 
    DECLARE @start_snapshot_id int;
    DECLARE @end_snapshot_id int;
    SELECT @start_snapshot_id = MIN (first_snapshot_id)
    FROM #intervals
    SELECT @end_snapshot_id = MAX (last_snapshot_id)
    FROM #intervals
    
    -- Get the file stats for these collection times
    SELECT 
        coll.interval_id, coll.interval_time_id, coll.interval_start_time, coll.interval_end_time, 
        coll.first_collection_time, coll.last_collection_time, coll.first_snapshot_id, coll.last_snapshot_id, 
        fs.*
    INTO #file_stats
    FROM snapshots.io_virtual_file_stats AS fs
    INNER JOIN #intervals AS coll ON coll.last_snapshot_id = fs.snapshot_id AND coll.last_collection_time = fs.collection_time 
    WHERE 
        fs.logical_disk = ISNULL (@LogicalDisk, fs.logical_disk)
        AND fs.database_name = ISNULL (@Database, fs.database_name)
    
    -- Get file stats deltas for each interval.  
    SELECT 
        t.*, 
        /**** Combined reads + write values ****/
        t.num_of_reads_delta + t.num_of_writes_delta                    AS num_of_transfers_delta, 
        t.num_of_reads_cumulative + t.num_of_writes_cumulative          AS num_of_transfers_cumulative,  
        t.num_of_mb_read_delta + t.num_of_mb_written_delta              AS num_of_mb_transferred_delta, 
        t.num_of_mb_read_cumulative + t.num_of_mb_written_cumulative    AS num_of_mb_transferred_cumulative, 
        /**** Calc "Disk sec/Transfer" type values ***/
        t.io_stall_read_ms_delta + t.io_stall_write_ms_delta            AS io_stall_ms_delta, 
        t.io_stall_read_ms_cumulative + t.io_stall_write_ms_cumulative  AS io_stall_ms_cumulative, 
        CASE 
            WHEN t.num_of_reads_delta = 0 THEN 0 
            ELSE t.io_stall_read_ms_delta / t.num_of_reads_delta
        END                                                             AS io_stall_ms_per_read_delta, 
        CASE 
            WHEN t.num_of_reads_cumulative = 0 THEN 0 
            ELSE t.io_stall_read_ms_cumulative / t.num_of_reads_cumulative
        END                                                             AS io_stall_ms_per_read_cumulative, 
        CASE
            WHEN t.num_of_writes_delta = 0 THEN 0
            ELSE t.io_stall_write_ms_delta / t.num_of_writes_delta
        END                                                             AS io_stall_ms_per_write_delta, 
        CASE
            WHEN t.num_of_writes_cumulative = 0 THEN 0
            ELSE t.io_stall_write_ms_cumulative / t.num_of_writes_cumulative
        END                                                             AS io_stall_ms_per_write_cumulative, 
        CASE
            WHEN (t.num_of_reads_delta + t.num_of_writes_delta) = 0 THEN 0
            ELSE (t.io_stall_read_ms_delta + t.io_stall_write_ms_delta) / (t.num_of_reads_delta + t.num_of_writes_delta)
        END                                                             AS io_stall_ms_per_transfer_delta, 
        CASE
            WHEN (t.num_of_reads_cumulative + t.num_of_writes_cumulative) = 0 THEN 0
            ELSE (t.io_stall_read_ms_cumulative + t.io_stall_write_ms_cumulative) / (t.num_of_reads_cumulative + t.num_of_writes_cumulative)
        END                                                             AS io_stall_ms_per_transfer_cumulative 
    FROM
    (
        SELECT 
            fs1.interval_id, fs1.interval_time_id, fs1.first_snapshot_id, fs1.last_snapshot_id, 
            -- Convert all datetimeoffset values to UTC datetime values before returning to Reporting Services
            CONVERT (datetime, SWITCHOFFSET (CAST (fs1.first_collection_time AS datetimeoffset(7)), '+00:00')) AS first_collection_time, 
            CONVERT (datetime, SWITCHOFFSET (CAST (fs2.first_collection_time AS datetimeoffset(7)), '+00:00')) AS last_collection_time, 
            CONVERT (datetime, SWITCHOFFSET (CAST (fs1.interval_start_time AS datetimeoffset(7)), '+00:00')) AS interval_start, 
            CONVERT (datetime, SWITCHOFFSET (CAST (fs2.interval_start_time AS datetimeoffset(7)), '+00:00')) AS interval_end, 
            fs2.database_name, fs2.database_id, fs2.logical_file_name, fs2.[file_id], fs2.type_desc, fs2.logical_disk, 
            -- All file stats will be reset to zero by a service cycle, which will cause 
            -- (snapshot2_io_time-snapshot1_io_time) calculations to produce an incorrect 
            -- negative wait time for the interval.  Detect this and avoid calculating 
            -- negative IO wait time deltas. 
            /***** READS ****/
            CASE 
                WHEN (fs2.num_of_reads - fs1.num_of_reads) < 0 THEN fs2.num_of_reads
                ELSE (fs2.num_of_reads - fs1.num_of_reads)
            END AS num_of_reads_delta,                                              -- num_of_reads_delta
            fs2.num_of_reads AS num_of_reads_cumulative,                            -- num_of_reads_cumulative
            CASE 
                WHEN (fs2.num_of_bytes_read - fs1.num_of_bytes_read) < 0 THEN fs2.num_of_bytes_read 
                ELSE (fs2.num_of_bytes_read - fs1.num_of_bytes_read)
            END / 1024 / 1024 AS num_of_mb_read_delta,                              -- num_of_mb_read_delta
            fs2.num_of_bytes_read /1024/1024 AS num_of_mb_read_cumulative,          -- num_of_mb_read_cumulative
            CASE
                WHEN (fs2.io_stall_read_ms - fs1.io_stall_read_ms) < 0 THEN fs2.io_stall_read_ms
                ELSE (fs2.io_stall_read_ms - fs1.io_stall_read_ms)
            END AS io_stall_read_ms_delta,                                          -- io_stall_read_ms_delta
            fs2.io_stall_read_ms AS io_stall_read_ms_cumulative,                    -- io_stall_read_ms_cumulative
            /**** WRITES ****/
            CASE
                WHEN (fs2.num_of_writes - fs1.num_of_writes) < 0 THEN fs2.num_of_writes
                ELSE (fs2.num_of_writes - fs1.num_of_writes)
            END AS num_of_writes_delta,                                             -- num_of_writes_delta
            fs2.num_of_writes AS num_of_writes_cumulative,                          -- num_of_writes_cumulative
            CASE
                WHEN (fs2.num_of_bytes_written - fs1.num_of_bytes_written) < 0 THEN fs2.num_of_bytes_written
                ELSE (fs2.num_of_bytes_written - fs1.num_of_bytes_written)
            END / 1024 / 1024 AS num_of_mb_written_delta,                           -- num_of_mb_written_delta
            fs2.num_of_bytes_written /1024/1024 AS num_of_mb_written_cumulative,    -- num_of_mb_written_cumulative
            CASE
                WHEN (fs2.io_stall_write_ms - fs1.io_stall_write_ms) < 0 THEN fs2.io_stall_write_ms
                ELSE (fs2.io_stall_write_ms - fs1.io_stall_write_ms)
            END AS io_stall_write_ms_delta,                                         -- io_stall_write_ms_delta
            fs2.io_stall_write_ms AS io_stall_write_ms_cumulative,                  -- io_stall_write_ms_cumulative
            fs1.size_on_disk_bytes / 1024 / 1024 AS size_on_disk_mb_interval_start, -- size_on_disk_mb_interval_start
            fs2.size_on_disk_bytes / 1024 / 1024 AS size_on_disk_mb_interval_end    -- size_on_disk_mb_interval_end
        FROM #file_stats AS fs1
        -- Self-join - fs1 represents IO stats at the beginning of the sample interval, while fs2 
        -- shows file stats at the end of the interval. 
        INNER JOIN #file_stats AS fs2 ON fs1.database_id = fs2.database_id AND fs1.[file_id] = fs2.[file_id] AND fs1.interval_id = fs2.interval_id-1 

    ) AS t
    ORDER BY t.database_name, t.logical_file_name, t.last_collection_time
END
GO


--
-- snapshots.rpt_list_all_servers
--  Shows a list of the servers that have data in this MDW database, plus information on how up-to-date 
--  the data is.  Used in the multi-server overview report. 
-- Parameters: 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_list_all_servers', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_list_all_servers] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_list_all_servers]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_list_all_servers] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_list_all_servers]
AS
BEGIN
    SET NOCOUNT ON;

    SELECT 
        instance_name, 
        query_statistics_last_upload, 
        disk_usage_last_upload, 
        server_activity_last_upload
    FROM 
    (
        SELECT 
            -- Name of the SQL Server instance
            instance_name, 
            -- Name of the collection set.  The names in the syscollector_collection_sets table can be localized. 
            -- We use well-known strings here because we want to defer selection of the appropriate localized 
            -- string to the client. 
            CASE 
                collection_set_uid
                WHEN '2DC02BD6-E230-4C05-8516-4E8C0EF21F95' THEN 'query_statistics_last_upload'
                WHEN '7B191952-8ECF-4E12-AEB2-EF646EF79FEF' THEN 'disk_usage_last_upload'
                WHEN '49268954-4FD4-4EB6-AA04-CD59D9BB5714' THEN 'server_activity_last_upload'
                ELSE NULL -- custom Collection set, not displayed on this report
            END AS top_level_report_name,
            -- Convert datetimeoffset to UTC datetime for RS 2005 compatibility 
            CONVERT (datetime, SWITCHOFFSET (MAX (snapshot_time), '+00:00')) AS latest_snapshot_time 
        FROM core.snapshots 
        -- For this report, only system collection sets matter
        WHERE collection_set_uid IN ('2DC02BD6-E230-4C05-8516-4E8C0EF21F95', '7B191952-8ECF-4E12-AEB2-EF646EF79FEF', '49268954-4FD4-4EB6-AA04-CD59D9BB5714')
        GROUP BY instance_name, collection_set_uid
    ) AS instance_report_list 
    PIVOT 
    (
        MAX (latest_snapshot_time)
        FOR top_level_report_name IN (query_statistics_last_upload, disk_usage_last_upload, server_activity_last_upload)
    ) AS pvt 
    ORDER BY instance_name;

END;
GO




/**********************************************************************/
/* CUSTOM_SNAPSHOTS SCHEMA                                            */
/**********************************************************************/
RAISERROR('', 0, 1)  WITH NOWAIT;
RAISERROR('Create schema custom_snpshots...', 0, 1)  WITH NOWAIT;
GO
IF (SCHEMA_ID('custom_snapshots') IS NULL)
BEGIN
    DECLARE @sql nvarchar(128)
    SET @sql = 'CREATE SCHEMA custom_snapshots'
    EXEC sp_executesql @sql
END
GO




/**********************************************************************/
/* Create MDW security roles                                          */
/**********************************************************************/
RAISERROR('', 0, 1)  WITH NOWAIT;
RAISERROR('Create mdw_admin role...', 0, 1)  WITH NOWAIT;
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'mdw_admin' AND type = 'R'))
BEGIN
    CREATE ROLE [mdw_admin]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'mdw_admin' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [mdw_admin]
        CREATE ROLE [mdw_admin]
    END
END
GO

RAISERROR('Create mdw_writer role...', 0, 1)  WITH NOWAIT;
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'mdw_writer' AND type = 'R'))
BEGIN
    CREATE ROLE [mdw_writer]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'mdw_writer' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [mdw_writer]
        CREATE ROLE [mdw_writer]
    END
END
GO

RAISERROR('Create mdw_reader role...', 0, 1)  WITH NOWAIT;
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'mdw_reader' AND type = 'R'))
BEGIN
    CREATE ROLE [mdw_reader]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'mdw_reader' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [mdw_reader]
        CREATE ROLE [mdw_reader]
    END
END
GO

-- permissions of mdw_writer and mdw_reader are inclusive to mdw_admin
EXECUTE sp_addrolemember @rolename = 'mdw_writer' , 
                   @membername = 'mdw_admin' 
GO

EXECUTE sp_addrolemember @rolename = 'mdw_reader' , 
                   @membername = 'mdw_admin' 
GO

RAISERROR('', 0, 1)  WITH NOWAIT;
RAISERROR('Create loginless user ...', 0, 1)  WITH NOWAIT;
IF (NOT EXISTS(SELECT * FROM sys.database_principals WHERE NAME = 'mdw_check_operator_admin'))
BEGIN
    CREATE USER [mdw_check_operator_admin] WITHOUT LOGIN
END
GO

EXECUTE sp_addrolemember @rolename = 'mdw_admin', @membername = 'mdw_check_operator_admin' 
GO



/**********************************************************************/
/* Setup permissions                                                  */
/**********************************************************************/
RAISERROR('', 0, 1)  WITH NOWAIT;
RAISERROR('Granting permissions to MDW security roles...', 0, 1)  WITH NOWAIT;

-- Database level permissions
GRANT VIEW DEFINITION                                           TO [mdw_writer]


-- Core schema permissions

-- custom_snaphots use the core.snapshots_internal.snapshot_id column as a FK, 
-- REFERENCES permission need to be granted to mdw_admin 
GRANT REFERENCES ON SCHEMA::[core]                              TO [mdw_admin] 

GRANT EXECUTE ON [core].[sp_create_snapshot]                    TO [mdw_writer]
GRANT EXECUTE ON [core].[sp_update_data_source]                 TO [mdw_writer]
GRANT EXECUTE ON [core].[sp_add_collector_type]                 TO [mdw_admin]
GRANT EXECUTE ON [core].[sp_remove_collector_type]              TO [mdw_admin]
GRANT EXECUTE ON [core].[sp_purge_data]                         TO [mdw_admin]
GRANT EXECUTE ON [core].[sp_stop_purge]                         TO [mdw_admin]

GRANT REFERENCES ON [core].[fn_check_operator]                  TO [mdw_check_operator_admin]

GRANT SELECT  ON [core].[snapshots]                             TO [mdw_admin]    
GRANT SELECT  ON [core].[snapshots]                             TO [mdw_writer]    
GRANT SELECT  ON [core].[snapshots]                             TO [mdw_reader]    

GRANT SELECT  ON [core].[supported_collector_types]             TO [mdw_admin]
GRANT SELECT  ON [core].[supported_collector_types]             TO [mdw_writer]
GRANT SELECT  ON [core].[supported_collector_types]             TO [mdw_reader]

GRANT SELECT  ON [core].[wait_categories]                       TO [mdw_reader]
GRANT SELECT  ON [core].[wait_types]                            TO [mdw_reader]
GRANT SELECT  ON [core].[wait_types_categorized]                TO [mdw_reader]

GRANT SELECT  ON [core].[snapshots_internal]                    TO [mdw_admin]
GRANT SELECT  ON [core].[source_info_internal]                  TO [mdw_admin]
GRANT SELECT  ON [core].[snapshot_timetable_internal]           TO [mdw_admin]
GRANT SELECT  ON [core].[supported_collector_types_internal]    TO [mdw_admin]
GRANT SELECT  ON [core].[fn_query_text_from_handle]             TO [mdw_reader]

GRANT SELECT  ON [core].[performance_counter_report_group_items] TO [mdw_reader]

-- Snapshot schema permissions
GRANT INSERT  ON SCHEMA :: [snapshots]                          TO [mdw_writer]
GRANT EXECUTE ON SCHEMA :: [snapshots]                          TO [mdw_writer]
GRANT DELETE  ON SCHEMA :: [snapshots]                          TO [mdw_admin]
GRANT SELECT  ON SCHEMA :: [snapshots]                          TO [mdw_reader]
-- mdw_writer needs SELECT permission to perform upload because bcp selects the table to verify its schema
GRANT SELECT  ON SCHEMA :: [snapshots]                          TO [mdw_writer]

GRANT EXECUTE ON [snapshots].[sp_get_unknown_query_plan]        TO [mdw_writer]
GRANT EXECUTE ON [snapshots].[sp_get_unknown_query_text]        TO [mdw_writer]
GRANT EXECUTE ON [snapshots].[sp_update_query_plan]             TO [mdw_writer]
GRANT EXECUTE ON [snapshots].[sp_update_query_text]             TO [mdw_writer]

-- Custom_snapshot schema permissions
GRANT CREATE TABLE                                              TO [mdw_writer]
GRANT ALTER   ON SCHEMA :: [custom_snapshots]                   TO [mdw_writer]
GRANT INSERT  ON SCHEMA :: [custom_snapshots]                   TO [mdw_writer]
GRANT DELETE  ON SCHEMA :: [custom_snapshots]                   TO [mdw_admin]
GRANT SELECT  ON SCHEMA :: [custom_snapshots]                   TO [mdw_reader]
-- mdw_writer needs SELECT permission to perform upload because bcp selects the table to verify its schema
GRANT SELECT  ON SCHEMA :: [custom_snapshots]                   TO [mdw_writer]

GRANT CREATE TABLE                                              TO [mdw_admin]
GRANT CONTROL ON SCHEMA :: [custom_snapshots]                   TO [mdw_admin]

-- Report procedures
GRANT EXECUTE ON [snapshots].[rpt_snapshot_times]                       TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_interval_collection_times]            TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_next_and_previous_collection_times]   TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_generic_perfmon]                      TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_generic_perfmon_pivot]                TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_wait_stats]                           TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[fn_hexstrtovarbin]                        TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_top_query_stats]                      TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_query_stats]                          TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_query_plan_stats]                     TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_query_plan_stats_timeline]            TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_query_plan_missing_indexes]           TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_query_plan_parameters]                TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_query_plan_details]                   TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_blocking_chains]                      TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_blocking_chain_detail]                TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_active_sessions_and_requests]         TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_sql_memory_clerks]                    TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_sql_process_and_system_memory]        TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_sampled_waits]                        TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_sampled_waits_longest]                TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_sampled_waits_hottest_resources]      TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_io_virtual_file_stats]                TO [mdw_reader]
GRANT EXECUTE ON [snapshots].[rpt_list_all_servers]                     TO [mdw_reader]

-- NOTE: In order to insert data into MDW the login used for uploading data
-- needs to have ADMINISTER BULK OPERATIONS server permission granted

GO

/**********************************************************************/
/* LEGACY OBJECTS - KEPT FOR OLDER CTPs BUT NO LONGER USED            */
/**********************************************************************/

/**********************************************************************/
/* Used by CTP5 (but not CTP6):                                       */
/**********************************************************************/

--
-- snapshots.rpt_waiting_sessions
--  Returns list of sessions waiting for specified wait type category
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - end of the time window (UTC)
--    @WindowSize - number of minutes in the time window
--    @CategoryName - Name of wait category (optional)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_waiting_sessions', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_waiting_sessions] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_waiting_sessions]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_waiting_sessions] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_waiting_sessions]
    @ServerName sysname, 
    @EndTime datetime, 
    @WindowSize int, 
    @CategoryName nvarchar(20)
AS
BEGIN
    SET NOCOUNT ON;
    DECLARE @start_time_internal datetimeoffset(7);
    DECLARE @end_time_internal datetimeoffset(7);
    
    -- Start time should be passed in as a UTC datetime
    IF (@EndTime IS NOT NULL)
    BEGIN
        -- Assumed time zone offset for this conversion is +00:00 (datetime must be passed in as UTC)
        SET @end_time_internal = CAST (@EndTime AS datetimeoffset(7));
    END
    ELSE BEGIN
        SELECT @end_time_internal = MAX(ar.collection_time)
        FROM core.snapshots AS s
        INNER JOIN snapshots.active_sessions_and_requests AS ar ON s.snapshot_id = ar.snapshot_id
        WHERE s.instance_name = @ServerName -- AND collection_set_uid = '49268954-4FD4-4EB6-AA04-CD59D9BB5714' -- Server Activity CS
    END
    SET @start_time_internal = DATEADD (minute, -1 * @WindowSize, @end_time_internal);
    
    DECLARE @total_waits TABLE
    (
        wait_type nvarchar(45),
        wait_count bigint, 
        wait_duration_ms bigint
    )

    INSERT INTO @total_waits
    SELECT 
        ar.wait_type,
        COUNT(*) AS wait_count, 
        SUM (wait_duration_ms)
    FROM snapshots.active_sessions_and_requests ar
    JOIN core.snapshots s ON (s.snapshot_id = ar.snapshot_id)
    WHERE s.instance_name = @ServerName -- AND collection_set_uid = '49268954-4FD4-4EB6-AA04-CD59D9BB5714' -- Server Activity CS
        AND ar.collection_time BETWEEN @start_time_internal AND @end_time_internal
        AND ar.wait_type != ''
    GROUP BY ar.wait_type
    
    SELECT
        t.source_id,
        t.session_id,
        t.request_id,
        t.database_name,
        CONVERT (datetime, SWITCHOFFSET (CAST (t.login_time AS datetimeoffset(7)), '+00:00')) AS login_time, 
        t.login_name,
        t.[program_name],
        t.sql_handle,
        master.dbo.fn_varbintohexstr (t.sql_handle) AS sql_handle_str,
        t.statement_start_offset,
        t.statement_end_offset,
        t.plan_handle,
        master.dbo.fn_varbintohexstr (t.plan_handle) AS plan_handle_str,
        t.wait_type,
        t.wait_count,
        t.wait_total_percent,
        t.wait_type_wait_percent,
        qt.query_text, 
        REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (REPLACE (
            LEFT (LTRIM (qt.query_text), 100)
            , CHAR(9), ' '), CHAR(10), ' '), CHAR(13), ' '), '   ', ' '), '  ', ' '), '  ', ' ') AS flat_query_text
    FROM
    (
        SELECT 
            s.source_id,
            ar.session_id,
            ar.request_id,
            ar.database_name,
            ar.login_time,
            ar.login_name,
            ar.program_name,
            ar.sql_handle,
            ar.statement_start_offset,
            ar.statement_end_offset,
            ar.plan_handle,
            ar.wait_type,
            COUNT(*) AS wait_count,
            (COUNT(*)) / CONVERT(decimal, (SELECT SUM(wait_count) FROM @total_waits)) AS wait_total_percent,
            (COUNT(*)) / CONVERT(decimal, (SELECT wait_count FROM @total_waits WHERE wait_type = ar.wait_type)) AS wait_type_wait_percent
        FROM snapshots.active_sessions_and_requests AS ar
        INNER JOIN core.snapshots AS s ON (s.snapshot_id = ar.snapshot_id)
        INNER JOIN core.wait_types_categorized AS wt on (wt.wait_type = ar.wait_type)
        WHERE s.instance_name = @ServerName -- AND s.collection_set_uid = '49268954-4FD4-4EB6-AA04-CD59D9BB5714' -- Server Activity CS
            AND ar.collection_time BETWEEN @start_time_internal AND @end_time_internal
            AND wt.category_name = @CategoryName
        GROUP BY s.source_id, ar.session_id, ar.request_id, ar.database_name, ar.login_time, ar.login_name, ar.program_name, 
            ar.wait_type, ar.sql_handle, ar.statement_start_offset, ar.statement_end_offset, ar.plan_handle
    ) AS t 
    OUTER APPLY snapshots.fn_get_query_text(t.source_id, t.sql_handle, t.statement_start_offset, t.statement_end_offset) AS qt

END;
GO
GRANT EXECUTE ON [snapshots].[rpt_waiting_sessions]                     TO [mdw_reader]
GO


-- This function returns a list of all the known query plans from a disctinct source 
IF (NOT OBJECT_ID(N'snapshots.fn_get_notable_query_plans', 'TF') IS NULL)
BEGIN
    RAISERROR('Dropping function [snapshots].[fn_get_notable_query_plans] ...', 0, 1)  WITH NOWAIT;
    DROP FUNCTION [snapshots].[fn_get_notable_query_plans]
END

RAISERROR('Creating function [snapshots].[fn_get_notable_query_plans]', 0, 1)  WITH NOWAIT;
Go
CREATE FUNCTION [snapshots].[fn_get_notable_query_plans](
    @source_id  int
)
RETURNS @notable_queries TABLE (sql_handle varbinary(64) , 
                                plan_handle varbinary(64), 
                                statement_start_offset int, 
                                statement_end_offset int,
                                creation_time datetime)
BEGIN
    INSERT INTO @notable_queries
    SELECT  [sql_handle], 
        [plan_handle],
        [statement_start_offset],
        [statement_end_offset],
            -- Convert datetimeoffset to datetime so that SSIS can easily join the output back 
            -- to the new sys.dm_exec_query_stats data
            CONVERT (datetime, [creation_time]) AS [creation_time]
    FROM    [snapshots].[notable_query_plan]
    WHERE   [source_id] = @source_id
    ORDER BY [sql_handle] ASC, [plan_handle], [statement_start_offset], [statement_end_offset], [creation_time] ASC

    RETURN
END
GO

GRANT SELECT  ON [snapshots].[fn_get_notable_query_plans]       TO [mdw_writer]
GO



-- This function returns a list of all the known sql_handles (i.e., query text) from a disctinct source 
IF (NOT OBJECT_ID(N'snapshots.fn_get_notable_query_text', 'TF') IS NULL)
BEGIN
    RAISERROR('Dropping function [snapshots].[fn_get_notable_query_text] ...', 0, 1)  WITH NOWAIT;
    DROP FUNCTION [snapshots].[fn_get_notable_query_text]
END

RAISERROR('Creating function [snapshots].[fn_get_notable_query_text]', 0, 1)  WITH NOWAIT;
Go
CREATE FUNCTION [snapshots].[fn_get_notable_query_text](
    @source_id int
)
RETURNS @notable_text TABLE (sql_handle varbinary(64))
BEGIN
    INSERT INTO @notable_text
    SELECT  [sql_handle]
    FROM    [snapshots].[notable_query_text]
    WHERE   [source_id] = @source_id
    ORDER BY [sql_handle] ASC

    RETURN
END
GO

GRANT SELECT  ON [snapshots].[fn_get_notable_query_text]        TO [mdw_writer]
GO


--
-- snapshots.rpt_sql_memory_counters_one_snapshot
--  Returns values for page life expectancy and other SQL Server memory counters
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sql_memory_counters_one_snapshot', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_sql_memory_counters_one_snapshot] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_sql_memory_counters_one_snapshot]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_sql_memory_counters_one_snapshot] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_sql_memory_counters_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        pc.performance_counter_name AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value 
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name LIKE '%SQL%:Buffer Manager'
        AND pc.performance_counter_name = 'Page life expectancy'
    ORDER BY 
        pc.collection_time, pc.performance_counter_name
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_sql_memory_counters_one_snapshot]     TO [mdw_reader]
GO


--
-- snapshots.rpt_sql_memory_clerks_one_snapshot
--  Returns data from os_memory_clerks table
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sql_memory_clerks_one_snapshot', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_sql_memory_clerks_one_snapshot] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_sql_memory_clerks_one_snapshot]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_sql_memory_clerks_one_snapshot] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_sql_memory_clerks_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @total_allocated TABLE (
        collection_time datetimeoffset(7),
        total_kb bigint
    );

    INSERT INTO @total_allocated 
    SELECT 
        mc.collection_time,
        SUM(mc.single_pages_kb + 
            mc.multi_pages_kb + 
            (CASE WHEN type <> N'MEMORYCLERK_SQLBUFFERPOOL' THEN mc.virtual_memory_committed_kb 
            ELSE 0 END) + 
            mc.shared_memory_committed_kb)
    FROM snapshots.os_memory_clerks mc
    JOIN core.snapshots s ON (s.snapshot_id = mc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
    GROUP BY mc.collection_time

    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (mc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        mc.type,
        mc.single_pages_kb + mc.multi_pages_kb as allocated_kb,
        mc.virtual_memory_reserved_kb as virtual_reserved_kb,
        mc.virtual_memory_committed_kb as virtual_committed_kb,
        mc.awe_allocated_kb as awe_allocated_kb,
        mc.shared_memory_reserved_kb as shared_reserved_kb,
        mc.shared_memory_committed_kb as shared_committed_kb,
        (mc.single_pages_kb + mc.multi_pages_kb + (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL' THEN mc.virtual_memory_committed_kb ELSE 0 END) + mc.shared_memory_committed_kb) as total_kb,
        (mc.single_pages_kb + mc.multi_pages_kb + (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL' THEN mc.virtual_memory_committed_kb ELSE 0 END) + mc.shared_memory_committed_kb) / CONVERT(decimal, ta.total_kb) AS percent_total_kb,
        CASE
            WHEN (mc.single_pages_kb + mc.multi_pages_kb + (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL' THEN mc.virtual_memory_committed_kb ELSE 0 END) + mc.shared_memory_committed_kb) / CONVERT(decimal, ta.total_kb) > 0.05 THEN mc.type
            ELSE N'Other'
        END AS graph_type
    FROM snapshots.os_memory_clerks mc
    JOIN @total_allocated ta ON (mc.collection_time = ta.collection_time)
    ORDER BY collection_time
    
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_sql_memory_clerks_one_snapshot]       TO [mdw_reader]
GO


--
-- snapshots.rpt_sql_process_memory_one_snapshot
--  Returns details on sql process memory usage
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sql_process_memory_one_snapshot', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_sql_process_memory_one_snapshot] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_sql_process_memory_one_snapshot]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_sql_process_memory_one_snapshot] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_sql_process_memory_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;

    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        CASE 
            WHEN series = 'virtual_address_space_reserved_kb' THEN 'Virtual Memory Reserved'
            WHEN series = 'virtual_address_space_committed_kb' THEN 'Virtual Memory Committed'
            WHEN series = 'physical_memory_in_use_kb' THEN 'Physical Memory In Use'
            WHEN series = 'process_physical_memory_low' THEN 'Physical Memory Low'
            WHEN series = 'process_virtual_memory_low' THEN 'Virtual Memory Low'
            ELSE series
        END AS series,
        [value] / 1024 AS [value] -- convert KB to MB
    FROM 
    (
        SELECT 
            pm.collection_time,
            pm.virtual_address_space_reserved_kb,
            pm.virtual_address_space_committed_kb,
            pm.physical_memory_in_use_kb,
            CONVERT (bigint, pm.process_physical_memory_low) AS process_physical_memory_low,
            CONVERT (bigint, pm.process_virtual_memory_low) AS process_virtual_memory_low
        FROM [snapshots].[os_process_memory] pm
        JOIN core.snapshots s ON (s.snapshot_id = pm.snapshot_id)
        WHERE s.instance_name = @instance_name
            AND s.snapshot_time_id = @snapshot_time_id
    ) AS pvt
    UNPIVOT
    (
        [value] for [series] in 
            (virtual_address_space_reserved_kb, virtual_address_space_committed_kb, 
            physical_memory_in_use_kb)
    ) AS unpvt
    UNION ALL 
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        'Stolen Buffer Pool' AS [series], 
        pc.formatted_value * 8 AS [value] -- Convert from pages to KB
    FROM snapshots.performance_counters AS pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name LIKE '%SQL%:Buffer Manager'
        AND pc.performance_counter_name = 'Stolen pages'
    ORDER BY collection_time
END
GO

GRANT EXECUTE ON [snapshots].[rpt_sql_process_memory_one_snapshot]      TO [mdw_reader]
GO

--
-- snapshots.rpt_memory_counters_one_snapshot
--  Returns values for memory usage counters
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_memory_counters_one_snapshot', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_memory_counters_one_snapshot] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_memory_counters_one_snapshot]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_memory_counters_one_snapshot] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_memory_counters_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        pc.performance_counter_name AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.collection_time,
        pc.formatted_value / (1024*1024) AS formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND 
        (
            (pc.performance_object_name = 'Memory' AND pc.performance_counter_name IN ('Cache Bytes', 'Pool Nonpaged Bytes'))
            OR (pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'Working Set' AND pc.performance_instance_name = '_Total')
        )
    ORDER BY 
        pc.collection_time, series
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_memory_counters_one_snapshot]         TO [mdw_reader]
GO


--
-- snapshots.rpt_memory_rates_one_snapshot
--  Returns values for various memory ratios 
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_memory_rates_one_snapshot', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_memory_rates_one_snapshot] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_memory_rates_one_snapshot]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_memory_rates_one_snapshot] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_memory_rates_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        pc.performance_counter_name AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.collection_time,
        pc.formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name = 'Memory' 
        AND pc.performance_counter_name IN ('Page Faults/sec', 'Page Reads/sec', 'Page Writes/sec', 'Cache Faults/sec')
    ORDER BY 
        pc.collection_time, series
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_memory_rates_one_snapshot]            TO [mdw_reader]
GO

--
-- snapshots.rpt_cpu_counters_one_snapshot
--  Returns values for CPU usage counters per processor
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_cpu_counters_one_snapshot', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_cpu_counters_one_snapshot] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_cpu_counters_one_snapshot]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_cpu_counters_one_snapshot] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_cpu_counters_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        N'CPU ' + CONVERT (nvarchar(10), ISNULL(pc.performance_instance_name, N'')) as series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name = 'Processor' AND pc.performance_counter_name = '% Processor Time' 
        AND pc.performance_instance_name != '_Total'
    ORDER BY 
        pc.collection_time, series
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_cpu_counters_one_snapshot]            TO [mdw_reader]
GO

--
-- snapshots.rpt_cpu_queues_one_snapshot
--  Returns values for queue length counter per processor 
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_cpu_queues_one_snapshot', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_cpu_queues_one_snapshot] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_cpu_queues_one_snapshot]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_cpu_queues_one_snapshot] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_cpu_queues_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        CASE pc.performance_counter_name 
            WHEN 'Processor Queue Length' THEN N'Processor Queue Length'
            ELSE N'CPU ' + CONVERT (nvarchar(10), ISNULL(pc.performance_instance_name, N'')) 
        END AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND 
        (
            (pc.performance_object_name = 'Server Work Queues' AND pc.performance_counter_name = 'Queue Length'
                AND pc.performance_instance_name != '_Total' AND ISNUMERIC (pc.performance_instance_name) = 1)
            OR (pc.performance_object_name = 'System' AND pc.performance_counter_name = 'Processor Queue Length')
        )
    ORDER BY 
        pc.collection_time, series
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_cpu_queues_one_snapshot]              TO [mdw_reader]
GO

--
-- snapshots.rpt_cpu_usage_per_process
--  Returns min, max, avg CPU usage and avg thread count for top 10 processes
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_cpu_usage_per_process', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_cpu_usage_per_process] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_cpu_usage_per_process]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_cpu_usage_per_process] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_cpu_usage_per_process]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    -- Determine the CPU count on the target system by querying the number of "Processor" 
    -- counter instances we captured in a perfmon sample that was captured around the same 
    -- time. 
    DECLARE @cpu_count smallint
    SELECT @cpu_count = COUNT (DISTINCT pc.performance_instance_name)
    FROM snapshots.performance_counters AS pc
    INNER JOIN core.snapshots s ON s.snapshot_id = pc.snapshot_id 
    WHERE pc.performance_object_name = 'Processor' AND pc.performance_counter_name = '% Processor Time'
        AND pc.performance_instance_name != '_Total'
        AND s.snapshot_time_id = @snapshot_time_id
        AND s.instance_name = @instance_name
        AND pc.collection_time = 
            (SELECT TOP 1 collection_time FROM snapshots.performance_counter_values pcv2 WHERE pcv2.snapshot_id = s.snapshot_id)

    SELECT TOP 10
        cpu.process_name,
        cpu.minimum_value / @cpu_count AS cpu_minimum_value,
        cpu.maximum_value / @cpu_count AS cpu_maximum_value,
        cpu.average_value / @cpu_count AS cpu_average_value,
        tc.average_value AS  tc_average_value
    FROM
    (  SELECT
            ISNULL(pc.performance_instance_name, N'') AS process_name,
            MIN(pc.formatted_value)         AS minimum_value,
            MAX(pc.formatted_value)         AS maximum_value,
            AVG(pc.formatted_value)         AS average_value
        FROM [snapshots].[performance_counters] AS pc
        INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
        WHERE
            s.snapshot_time_id = @snapshot_time_id
            AND s.instance_name = @instance_name
            AND (pc.performance_object_name = 'Process' AND pc.performance_counter_name = '% Processor Time'
                AND pc.performance_instance_name NOT IN ('_Total', 'Idle'))
        GROUP BY pc.performance_instance_name
    ) AS cpu
    INNER JOIN
    (  SELECT
            ISNULL(pc.performance_instance_name, N'') AS process_name,
            MIN(pc.formatted_value)         AS minimum_value,
            MAX(pc.formatted_value)         AS maximum_value,
            AVG(pc.formatted_value)         AS average_value
        FROM [snapshots].[performance_counters] as pc
        INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
        WHERE
            s.snapshot_time_id = @snapshot_time_id
            AND s.instance_name = @instance_name
            AND (pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'Thread Count'
                AND pc.performance_instance_name NOT IN ('_Total', 'Idle'))
        GROUP BY pc.performance_instance_name
    ) AS tc ON (tc.process_name = cpu.process_name)
    ORDER BY cpu_average_value DESC

END;
GO

GRANT EXECUTE ON [snapshots].[rpt_cpu_usage_per_process]                TO [mdw_reader]
GO

--
-- snapshots.rpt_sessions_and_connections
--  Returns counts for sessions and connections within the given snapshot
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sessions_and_connections', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_sessions_and_connections] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_sessions_and_connections]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_sessions_and_connections] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_sessions_and_connections]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;


    SELECT 
        pc.performance_counter_name AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND (pc.performance_object_name LIKE '%SQL%:General Statistics' OR pc.performance_object_name LIKE '%SQL%:Databases')
        AND pc.performance_counter_name IN ('User Connections', 'Active Transactions')

    UNION ALL 

    SELECT 
        N'Active sessions' AS series,
        ar.collection_time,
        COUNT(DISTINCT ar.session_id) AS formatted_value
    FROM snapshots.active_sessions_and_requests ar
    INNER JOIN core.snapshots s ON (s.snapshot_id = ar.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
    GROUP BY ar.collection_time    

    ORDER BY collection_time
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_sessions_and_connections]             TO [mdw_reader]
GO


--
-- snapshots.rpt_requests_and_compilations
--  Returns counts for number of requests/sec and related compilations counters
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_requests_and_compilations', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_requests_and_compilations] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_requests_and_compilations]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_requests_and_compilations] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_requests_and_compilations]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;

    SELECT 
        pc.performance_counter_name AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name LIKE '%SQL%:SQL Statistics'
        AND pc.performance_counter_name IN ('Batch Requests/sec', 'SQL Compilations/sec', 
            'SQL Re-Compilations/sec', 'Auto-Param Attempts/sec', 'Failed Auto-Params/sec')
    ORDER BY pc.collection_time, pc.performance_counter_name
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_requests_and_compilations]            TO [mdw_reader]
GO

--
-- snapshots.rpt_plan_cache_hit_ratio
--  Returns details on plan cache hit ratio counter
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_plan_cache_hit_ratio', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_plan_cache_hit_ratio] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_plan_cache_hit_ratio]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_plan_cache_hit_ratio] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_plan_cache_hit_ratio]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;

    SELECT 
        ISNULL(pc.performance_instance_name, N'') AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name LIKE '%SQL%:Plan Cache'
        AND pc.performance_counter_name = 'Cache Hit Ratio'
        AND pc.performance_instance_name != '_Total'
    ORDER BY pc.collection_time, ISNULL(pc.performance_instance_name, N'')
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_plan_cache_hit_ratio]                 TO [mdw_reader]
GO


--
-- snapshots.rpt_tempdb
--  Returns counters related to tempdb
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_tempdb', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_tempdb] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_tempdb]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_tempdb] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_tempdb]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;

    SELECT 
        pc.performance_counter_name AS series,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND (pc.performance_object_name LIKE '%SQL%:Transactions' OR pc.performance_object_name LIKE '%SQL%:General Statistics')
        AND pc.performance_counter_name IN ('Free Space in tempdb (KB)', 'Active Temp Tables')
    ORDER BY pc.collection_time, pc.performance_counter_name
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_tempdb]                               TO [mdw_reader]
GO

--
-- snapshots.rpt_disk_speed_one_snapshot
--  Returns values for disk usage counters
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_disk_speed_one_snapshot', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_disk_speed_one_snapshot] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_disk_speed_one_snapshot]
END
GO

RAISERROR('Creating procedure [snapshots].[rpt_disk_speed_one_snapshot] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_disk_speed_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        ISNULL(pc.performance_instance_name, N'') AS disk_letter,
        pc.performance_counter_name AS counter,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value AS formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name = 'LogicalDisk' 
        AND pc.performance_counter_name IN ('Avg. Disk sec/Read', 'Avg. Disk sec/Write')
        AND pc.performance_instance_name != '_Total'
    ORDER BY 
        pc.collection_time, pc.performance_counter_name, ISNULL(pc.performance_instance_name, N'')
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_disk_speed_one_snapshot]              TO [mdw_reader]
GO


--
-- snapshots.rpt_disk_queues_one_snapshot
--  Returns values for disk queue counters
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_disk_queues_one_snapshot', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_disk_queues_one_snapshot] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_disk_queues_one_snapshot]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_disk_queues_one_snapshot] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_disk_queues_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        ISNULL(pc.performance_instance_name, N'') AS disk_letter,
        pc.performance_counter_name AS counter,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name = 'LogicalDisk' 
        AND pc.performance_counter_name IN ('Current Disk Queue Length', 'Avg. Disk Read Queue Length', 'Avg. Disk Write Queue Length')
        AND pc.performance_instance_name != '_Total'
    ORDER BY 
        pc.collection_time, pc.performance_counter_name, ISNULL(pc.performance_instance_name, N'')
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_disk_queues_one_snapshot]             TO [mdw_reader]
GO


--
-- snapshots.rpt_disk_ratios_one_snapshot
--  Returns values for disk ratio counters
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_disk_ratios_one_snapshot', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_disk_ratios_one_snapshot] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_disk_ratios_one_snapshot]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_disk_ratios_one_snapshot] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_disk_ratios_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT 
        ISNULL(pc.performance_instance_name, N'') AS disk_letter,
        pc.performance_counter_name AS counter,
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id = @snapshot_time_id
        AND pc.performance_object_name = 'LogicalDisk' 
        AND pc.performance_counter_name IN ('Disk Reads/sec', 'Disk Writes/sec')
        AND pc.performance_instance_name != '_Total'
    ORDER BY 
        pc.collection_time, pc.performance_counter_name, ISNULL(pc.performance_instance_name, N'')
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_disk_ratios_one_snapshot]             TO [mdw_reader]
GO


--
-- snapshots.rpt_disk_usage_per_process
--  Returns min, max, avg CPU usage and avg thread count for top 10 processes
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_disk_usage_per_process', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_disk_usage_per_process] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_disk_usage_per_process]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_disk_usage_per_process] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_disk_usage_per_process]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT TOP 10
        wb.process_name,
        wb.minimum_value AS wb_minimum_value,
        wb.maximum_value AS wb_maximum_value,
        wb.average_value AS wb_average_value,
        rb.minimum_value AS rb_minimum_value,
        rb.maximum_value AS rb_maximum_value,
        rb.average_value AS rb_average_value
    FROM
    (  SELECT
            ISNULL(pc.performance_instance_name, N'') AS process_name,
            MIN(pc.formatted_value / (1024))        as minimum_value,
            MAX(pc.formatted_value / (1024))        as maximum_value,
            AVG(pc.formatted_value / (1024))        as average_value
        FROM [snapshots].[performance_counters] as pc
        INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
        WHERE
            s.snapshot_time_id = @snapshot_time_id
            AND s.instance_name = @instance_name
            AND pc.performance_object_name = 'Process' 
            AND pc.performance_counter_name = 'IO Read Bytes/sec'
            AND pc.performance_instance_name NOT IN ('_Total', 'Idle')
        GROUP BY pc.performance_instance_name
    ) AS rb
    INNER JOIN
    (  SELECT
            ISNULL(pc.performance_instance_name, N'') AS process_name,
            MIN(pc.formatted_value / (1024))        as minimum_value,
            MAX(pc.formatted_value / (1024))        as maximum_value,
            AVG(pc.formatted_value / (1024))        as average_value
        FROM [snapshots].[performance_counters] as pc
        INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
        WHERE
            s.snapshot_time_id = @snapshot_time_id
            AND s.instance_name = @instance_name
            AND pc.performance_object_name = 'Process' 
            AND pc.performance_counter_name = 'IO Write Bytes/sec'
            AND pc.performance_instance_name NOT IN ('_Total', 'Idle')
        GROUP BY pc.performance_instance_name
    ) AS wb ON (wb.process_name = rb.process_name)
    ORDER BY wb_average_value DESC, rb_average_value DESC
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_disk_usage_per_process]               TO [mdw_reader]
GO


-- snapshots.rpt_waiting_sessions_per_snapshot
--  Returns list of sessions waiting for specified wait type category
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--    @wait_category_name - Name of wait category to filter on 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_waiting_sessions_per_snapshot', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_waiting_sessions_per_snapshot] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_waiting_sessions_per_snapshot]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_waiting_sessions_per_snapshot] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_waiting_sessions_per_snapshot]
    @instance_name sysname,
    @snapshot_time_id int,
    @wait_category_name nvarchar(20)
AS
BEGIN
    SET NOCOUNT ON;
    
    DECLARE @total_waits TABLE
    (
        wait_type nvarchar(45),
        wait_count bigint
    )

    INSERT INTO @total_waits
    SELECT 
        ar.wait_type,
        COUNT(ar.wait_type) 
    FROM snapshots.active_sessions_and_requests ar
    JOIN core.snapshots s ON (s.snapshot_id = ar.snapshot_id)
    WHERE s.snapshot_time_id = @snapshot_time_id
      AND s.instance_name = @instance_name
    GROUP BY ar.wait_type
    
    SELECT
        t.source_id,
        t.session_id,
        t.request_id,
        t.database_name,
        CONVERT (datetime, SWITCHOFFSET (CAST (t.login_time AS datetimeoffset(7)), '+00:00')) AS login_time, 
        t.login_name,
        t.[program_name],
        t.sql_handle,
        master.dbo.fn_varbintohexstr (t.sql_handle) AS sql_handle_str,
        t.statement_start_offset,
        t.statement_end_offset,
        t.plan_handle,
        master.dbo.fn_varbintohexstr (t.plan_handle) AS plan_handle_str,
        t.wait_type,
        t.wait_count,
        t.wait_total_precent,
        t.wait_type_wait_precent,
        qt.query_text
    FROM
    (
        SELECT 
            s.source_id,
            ar.session_id,
            ar.request_id,
            ar.database_name,
            ar.login_time,
            ar.login_name,
            ar.program_name,
            ar.sql_handle,
            ar.statement_start_offset,
            ar.statement_end_offset,
            ar.plan_handle,
            ar.wait_type,
            COUNT(ar.wait_type) AS wait_count,
            (COUNT(ar.wait_type)) / CONVERT(decimal, (SELECT SUM(wait_count) FROM @total_waits)) AS wait_total_precent,
            (COUNT(ar.wait_type)) / CONVERT(decimal, (SELECT wait_count FROM @total_waits WHERE wait_type = ar.wait_type)) AS wait_type_wait_precent
        FROM snapshots.active_sessions_and_requests ar
        JOIN core.snapshots s ON (s.snapshot_id = ar.snapshot_id)
        JOIN core.wait_types ev on (ev.wait_type = ar.wait_type)
        JOIN core.wait_categories ct on (ct.category_id = ev.category_id)
        WHERE s.snapshot_time_id = @snapshot_time_id
          AND s.instance_name = @instance_name
          AND ct.category_name = @wait_category_name
        GROUP BY s.source_id, ar.session_id, ar.request_id, ar.database_name, ar.login_time, ar.login_name, ar.program_name, ar.wait_type, ar.sql_handle, ar.statement_start_offset, ar.statement_end_offset, ar.plan_handle
    ) t 
    OUTER APPLY snapshots.fn_get_query_text(t.source_id, t.sql_handle, t.statement_start_offset, t.statement_end_offset) AS qt

END;
GO
GRANT EXECUTE ON [snapshots].[rpt_waiting_sessions_per_snapshot]        TO [mdw_reader]
GO


--
-- snapshots.rpt_memory_usage_per_process
--  Returns min, max, avg CPU usage and avg thread count for top 10 processes
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_memory_usage_per_process', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_memory_usage_per_process] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_memory_usage_per_process]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_memory_usage_per_process] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_memory_usage_per_process]
    @instance_name sysname,
    @snapshot_time_id int
AS
BEGIN
    SET NOCOUNT ON;
    
    SELECT TOP 10
        ws.process_name,
        ws.minimum_value AS ws_minimum_value,
        ws.maximum_value AS ws_maximum_value,
        ws.average_value AS ws_average_value,
        vb.minimum_value AS vb_minimum_value,
        vb.maximum_value AS vb_maximum_value,
        vb.average_value AS vb_average_value
    FROM
    (  SELECT
            ISNULL(pc.performance_instance_name, N'') as process_name,
            MIN(pc.formatted_value / (1024*1024))        as minimum_value,
            MAX(pc.formatted_value / (1024*1024))        as maximum_value,
            AVG(pc.formatted_value / (1024*1024))        as average_value
        FROM [snapshots].[performance_counters] as pc
        INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
        WHERE
            s.snapshot_time_id = @snapshot_time_id
            AND s.instance_name = @instance_name
            AND pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'Working Set'
            AND pc.performance_instance_name NOT IN ('_Total', 'Idle')
        GROUP BY pc.performance_instance_name
    ) AS ws
    INNER JOIN
    (  SELECT
            ISNULL(pc.performance_instance_name, N'') as process_name,
            MIN(pc.formatted_value / (1024*1024))        as minimum_value,
            MAX(pc.formatted_value / (1024*1024))        as maximum_value,
            AVG(pc.formatted_value / (1024*1024))        as average_value
        FROM [snapshots].[performance_counters] as pc
        INNER JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
        WHERE
            s.snapshot_time_id = @snapshot_time_id
            AND s.instance_name = @instance_name
            AND pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'Virtual Bytes'
            AND pc.performance_instance_name NOT IN ('_Total', 'Idle')
        GROUP BY pc.performance_instance_name
    ) AS vb ON (vb.process_name = ws.process_name)
    ORDER BY ws_average_value DESC

END;
GO

GRANT EXECUTE ON [snapshots].[rpt_memory_usage_per_process]             TO [mdw_reader]
GO


-- snapshots.rpt_wait_stats_by_category_by_snapshot
--  Returns deltas of wait stats with one snapshot interval and aggregated by category
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--    @category_name - (Optional) Name of wait category to filter on (all categories if NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_wait_stats_by_category_by_snapshot', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_wait_stats_by_category_by_snapshot] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_wait_stats_by_category_by_snapshot]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_wait_stats_by_category_by_snapshot] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_wait_stats_by_category_by_snapshot]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint = NULL,
    @time_interval_min smallint = NULL, 
    @category_name nvarchar(20) = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset(7)) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    -- If the selected time window is > 1 hour, we'll chart wait times at 15 minute intervals.  If the selected 
    -- time window is < 1 hour, we'll use 5 minute intervals. 
    DECLARE @group_interval_min int
    IF DATEDIFF (minute, @start_time, @end_time) > 60
    BEGIN
        SET @group_interval_min = 15
    END 
    ELSE BEGIN
        SET @group_interval_min = 5
    END;
    -- Get wait times by waittype (plus CPU time, modeled as a waittype)
    WITH wait_times AS 
    ( 
        SELECT 
            s.snapshot_id, s.snapshot_time_id, s.snapshot_time, 
            DENSE_RANK() OVER (ORDER BY ws.collection_time) AS [rank], 
            ws.collection_time, wt.category_name, ws.wait_type, 
            ws.waiting_tasks_count, 
        ISNULL (ws.signal_wait_time_ms, 0) AS signal_wait_time_ms, 
        ISNULL (ws.wait_time_ms, 0) - ISNULL (ws.signal_wait_time_ms, 0) AS wait_time_ms, 
        ISNULL (ws.wait_time_ms, 0) AS wait_time_ms_cumulative 
    FROM snapshots.os_wait_stats AS ws
        INNER JOIN core.wait_types_categorized wt ON wt.wait_type = ws.wait_type
        INNER JOIN core.snapshots s ON ws.snapshot_id = s.snapshot_id
        WHERE s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
            AND s.instance_name = @instance_name
            AND wt.category_name = ISNULL (@category_name, wt.category_name)
        AND wt.ignore != 1

            AND ws.collection_time IN 
            (
                SELECT MAX(collection_time) 
                FROM snapshots.os_wait_stats ws2 
                WHERE ws2.collection_time BETWEEN @start_time AND @end_time
                GROUP BY DATEDIFF (minute, '19000101', ws2.collection_time) / @group_interval_min
            )
    )

    -- Get resource wait stats for this snapshot_time_id interval
    -- We must convert all datetimeoffset values to UTC datetime values before returning to Reporting Services
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        w2.snapshot_time_id, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w1.collection_time AS datetimeoffset(7)), '+00:00')) AS interval_start, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS interval_end, 
        w2.category_name, w2.wait_type, 
        -- All wait stats will be reset to zero by a service cycle, which will cause 
        -- (snapshot2waittime-snapshot1waittime) calculations to produce an incorrect 
        -- negative wait time for the interval.  Detect this and avoid calculating 
        -- negative wait time/wait count/signal time deltas. 
        CASE 
            WHEN (w2.waiting_tasks_count - w1.waiting_tasks_count) < 0 THEN w2.waiting_tasks_count 
            ELSE (w2.waiting_tasks_count - w1.waiting_tasks_count) 
        END AS waiting_tasks_count_delta, 
        CASE 
            WHEN (w2.wait_time_ms - w1.wait_time_ms) < 0 THEN w2.wait_time_ms
            ELSE (w2.wait_time_ms - w1.wait_time_ms)
        END / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) AS resource_wait_time_delta, 
        CASE 
            WHEN (w2.signal_wait_time_ms - w1.signal_wait_time_ms) < 0 THEN w2.signal_wait_time_ms 
            ELSE (w2.signal_wait_time_ms - w1.signal_wait_time_ms) 
        END / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        w2.wait_time_ms_cumulative
    -- Self-join - w1 represents wait stats at the beginning of the sample interval, while w2 
    -- shows the wait stats at the end of the interval. 
    FROM wait_times AS w1 
    INNER JOIN wait_times AS w2 ON w1.wait_type = w2.wait_type AND w1.[rank] = w2.[rank]-1 

    UNION ALL 

    -- Treat sum of all signal waits as CPU "wait time"
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        w2.snapshot_time_id, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w1.collection_time AS datetimeoffset(7)), '+00:00')) AS interval_start, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS interval_end, 
        'CPU' AS category_name, 
        'CPU (Signal Wait)' AS wait_type, 
        0 AS waiting_tasks_count_delta, 
        -- Handle wait stats resets
        CASE 
            WHEN (SUM (w2.signal_wait_time_ms) - SUM (w1.signal_wait_time_ms)) < 0 THEN SUM (w2.signal_wait_time_ms)
            ELSE (SUM (w2.signal_wait_time_ms) - SUM (w1.signal_wait_time_ms)) 
        END / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) AS resource_wait_time_delta, 
        0 AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        w2.wait_time_ms_cumulative
    FROM wait_times AS w1
    INNER JOIN wait_times AS w2 ON w1.wait_type = w2.wait_type AND w1.[rank] = w2.[rank]-1
    -- Only return CPU stats if we were told to return the 'CPU' category or all categories
    WHERE (@category_name IS NULL OR @category_name = 'CPU')
    GROUP BY w1.collection_time, w1.collection_time, w2.collection_time, w2.snapshot_time, w2.snapshot_time_id, w2.wait_time_ms_cumulative

    UNION ALL 

    -- Get actual used CPU time from perfmon data (average across the whole snapshot_time_id interval, 
    -- and use this average for each sample time in this interval).  Note that the "% Processor Time" 
    -- counter in the "Process" object can exceed 100% (for example, it can range from 0-800% on an 
    -- 8 CPU server). 
    SELECT
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        w2.snapshot_time_id, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w1.collection_time AS datetimeoffset(7)), '+00:00')) AS interval_start, 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS interval_end, 
        'CPU' AS category_name, 
        'CPU (Consumed)' AS wait_type, 
        0 AS waiting_tasks_count_delta, 
        -- Get sqlservr %CPU usage for the perfmon sample that immediately precedes 
        -- each wait stats sample.  Multiply by 10 to convert "% CPU" to "ms CPU/sec". 
        10 * (  
            SELECT TOP 1 formatted_value
            FROM snapshots.performance_counters AS pc
            INNER JOIN core.snapshots s ON pc.snapshot_id = s.snapshot_id
            WHERE pc.performance_object_name = 'Process' AND pc.performance_counter_name = '% Processor Time' 
                AND pc.performance_instance_name = 'sqlservr'
                AND s.snapshot_time_id <= @end_snapshot_time_id
                AND s.instance_name = @instance_name
                AND pc.snapshot_id < w2.snapshot_id
            ORDER BY pc.snapshot_id DESC
        ) AS resource_wait_time_delta, 
        0 AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        NULL
    FROM wait_times AS w1
    INNER JOIN wait_times AS w2 ON w1.wait_type = w2.wait_type AND w1.[rank] = w2.[rank]-1
    -- Only return CPU stats if we weren't passed in a specific wait category
    WHERE (@category_name IS NULL OR @category_name = 'CPU')
    GROUP BY w1.collection_time, w2.collection_time, w2.snapshot_time, w2.snapshot_time_id, w2.snapshot_id

    ORDER BY collection_time, category_name, wait_type
    -- These trace flags are necessary for a good plan, due to the join on ascending core.snapshot PK
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390);
END
GO

GRANT EXECUTE ON [snapshots].[rpt_wait_stats_by_category_by_snapshot]   TO [mdw_reader]
GO


--
-- snapshots.rpt_cpu_usage
--  Returns values for System CPU usage and SQL Server CPU usage collected through perf counters.
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_cpu_usage', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_cpu_usage] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_cpu_usage]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_cpu_usage] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_cpu_usage]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint = NULL,
    @time_interval_min smallint = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Convert snapshot_time (datetimeoffset(7)) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset(7)) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    -- Determine the CPU count on the target system by querying the number of "Processor" 
    -- counter instances we captured in a perfmon sample that was captured around the same 
    -- time. 
    DECLARE @cpu_count smallint
        SELECT @cpu_count = COUNT (DISTINCT pc.performance_instance_name)
        FROM snapshots.performance_counters AS pc
        INNER JOIN core.snapshots s ON s.snapshot_id = pc.snapshot_id 
        WHERE pc.performance_object_name = 'Processor' AND pc.performance_counter_name = '% Processor Time'
        AND pc.performance_instance_name != '_Total'
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id 
        AND s.instance_name = @instance_name
            AND pc.collection_time = 
                (SELECT TOP 1 collection_time FROM snapshots.performance_counter_values pcv2 WHERE pcv2.snapshot_id = s.snapshot_id)
    SELECT 
        CASE pc.performance_object_name
            WHEN 'Processor' THEN 'System'
            ELSE 'SQL Server'
        END AS series,
        s.snapshot_time_id, 
        CONVERT (datetime, SWITCHOFFSET (CAST (s.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        -- Processor time on an eight proc system is 0-800% for the Process object, 
        -- but 0-100% for the Processor object
        CASE pc.performance_object_name
            WHEN 'Processor' THEN pc.formatted_value 
            ELSE pc.formatted_value / @cpu_count
        END AS formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        AND 
        (
            (pc.performance_object_name = 'Processor' AND pc.performance_counter_name = '% Processor Time' AND pc.performance_instance_name = '_Total')
            OR (pc.performance_object_name = 'Process' AND pc.performance_counter_name = '% Processor Time' AND pc.performance_instance_name = 'sqlservr')
        )
    ORDER BY pc.collection_time, series
        -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
        OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
        
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_cpu_usage]                            TO [mdw_reader]
GO


--
-- snapshots.rpt_mem_usage
--  Returns values for System memory usage and SQL Server memory usage collected through perf counters.
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_mem_usage', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_mem_usage] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_mem_usage]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_mem_usage] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_mem_usage]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint = NULL,
    @time_interval_min smallint = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    SELECT 
        N'System' AS series,
        s.snapshot_time_id,
        CONVERT (datetime, SWITCHOFFSET (CAST (s.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        AVG(pc.formatted_value/(1024*1024)) AS formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        AND (pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'Working Set' AND pc.performance_instance_name = '_Total')
             -- uncomment when defect 109313 is fixed
             --OR pc.path LIKE N'\Memory\Pool Nonpaged Bytes' 
             --OR pc.path LIKE N'\Memory\Cache Bytes')
    GROUP BY
        s.snapshot_time_id,
        s.snapshot_time
    UNION ALL 
    SELECT 
        N'SQL Server' AS series,
        s.snapshot_time_id,
        CONVERT (datetime, SWITCHOFFSET (CAST (s.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        AVG(pc.formatted_value/(1024*1024)) AS formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        AND (pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'Working Set' AND pc.performance_instance_name = 'sqlservr')
    GROUP BY
        s.snapshot_time_id,
        s.snapshot_time
    ORDER BY 
        snapshot_time_id, series
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_mem_usage]                            TO [mdw_reader]
GO

--
-- snapshots.rpt_io_usage
--  Returns values for System Disk IO usage and SQL Server Disk IO usage collected through perf counters.
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_io_usage', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_io_usage] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_io_usage]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_io_usage] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_io_usage]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint = NULL,
    @time_interval_min smallint = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    SELECT 
        N'System' AS series,
        s.snapshot_time_id,
        CONVERT (datetime, SWITCHOFFSET (CAST (s.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        AVG(pc.formatted_value/(1024)) AS formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        AND (pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'IO Read Bytes/sec' AND pc.performance_instance_name = '_Total')
             -- uncomment when defect 109313 is fixed
             --OR pc.path LIKE N'\Process(!_Total)\IO Write Bytes/sec' ESCAPE N'!')
    GROUP BY
        s.snapshot_time_id,
        s.snapshot_time
    UNION ALL 
    SELECT 
        N'SQL Server' AS series,
        s.snapshot_time_id,
        CONVERT (datetime, SWITCHOFFSET (CAST (s.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        AVG(pc.formatted_value/(1024)) AS formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        AND (pc.performance_object_name = 'Process' AND pc.performance_counter_name = 'IO Read Bytes/sec' AND pc.performance_instance_name = 'sqlservr')
             -- uncomment when defect 109313 is fixed
             --OR pc.path LIKE N'\Process(sqlservr)\IO Write Bytes/sec')
    GROUP BY
        s.snapshot_time_id,
        s.snapshot_time
    ORDER BY 
        snapshot_time_id, series
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_io_usage]                             TO [mdw_reader]
GO


--
-- snapshots.rpt_network_usage
--  Returns values for System network usage and SQL Server network usage collected through perf counters.
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_network_usage', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_network_usage] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_network_usage]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_network_usage] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_network_usage]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint = NULL,
    @time_interval_min smallint = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;

    SELECT 
        N'System' AS series,
        s.snapshot_time_id,
        CONVERT (datetime, SWITCHOFFSET (CAST (s.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        AVG(pc.formatted_value/(1024)) AS formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        -- all instances 
        AND (pc.performance_object_name = 'Network Interface' AND pc.performance_counter_name = 'Bytes Total/sec')
    GROUP BY
        s.snapshot_time_id,
        s.snapshot_time
    ORDER BY 
        snapshot_time_id
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_network_usage]                        TO [mdw_reader]
GO


--
-- snapshots.rpt_wait_stats_one_snapshot
--  Returns summary of wait stats grouped per wait category for one snapshot
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--    @category_name - (Optional) Name of wait category to filter on (all categories if NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_wait_stats_one_snapshot', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_wait_stats_one_snapshot] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_wait_stats_one_snapshot]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_wait_stats_one_snapshot] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_wait_stats_one_snapshot]
    @instance_name sysname,
    @snapshot_time_id int, 
    @category_name nvarchar(20) = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Find the snapshot_id of the last wait stats data set in the following 
    -- snapshot_time_id interval
    DECLARE @current_snapshot_id int
    SELECT TOP 1 @current_snapshot_id = s.snapshot_id
    FROM core.snapshots AS s
    INNER JOIN snapshots.os_wait_stats ws ON s.snapshot_id = ws.snapshot_id
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id > @snapshot_time_id
    ORDER BY s.snapshot_time_id ASC, s.snapshot_id ASC, ws.collection_time ASC;

    -- Find the snapshot_id of the last wait stats data set in the preceding 
    -- snapshot_time_id interval
    DECLARE @previous_snapshot_id int
    SELECT TOP 1 @previous_snapshot_id = ISNULL (s.snapshot_id, 0)
    FROM core.snapshots AS s
    INNER JOIN snapshots.os_wait_stats ws ON s.snapshot_id = ws.snapshot_id
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id < @snapshot_time_id
    ORDER BY s.snapshot_time_id DESC, s.snapshot_id DESC, ws.collection_time DESC;

    -- Get wait times by waittype (plus CPU time, modeled as a waittype)
    WITH wait_times AS 
    ( 
        SELECT 
            s.snapshot_id, s.snapshot_time_id, 
            DENSE_RANK() OVER (ORDER BY collection_time) AS [rank], 
            ws.collection_time, wt.category_name, ws.wait_type, 
            ws.waiting_tasks_count, 
            ISNULL (ws.signal_wait_time_ms, 0) AS signal_wait_time_ms, 
            ISNULL (ws.wait_time_ms - ISNULL (ws.signal_wait_time_ms, 0), 0) AS wait_time_ms, 
            ws.wait_time_ms AS wait_time_ms_cumulative
        FROM snapshots.os_wait_stats AS ws
        INNER JOIN core.wait_types_categorized wt ON wt.wait_type = ws.wait_type
        INNER JOIN core.snapshots s ON ws.snapshot_id = s.snapshot_id
        WHERE s.snapshot_id BETWEEN @previous_snapshot_id AND @current_snapshot_id
            AND s.instance_name = @instance_name
            AND (wt.category_name = ISNULL (@category_name, wt.category_name))
            AND wt.ignore != 1
    )

    -- Get resource wait stats for this snapshot_time_id interval
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        w2.category_name, w2.wait_type, 
        -- All wait stats will be reset to zero by a service cycle, which will cause 
        -- (snapshot2waittime-snapshot1waittime) calculations to produce an incorrect 
        -- negative wait time for the interval.  Detect this and avoid calculating 
        -- negative wait time/wait count/signal time deltas. 
        CASE 
            WHEN (w2.waiting_tasks_count - w1.waiting_tasks_count) < 0 THEN w2.waiting_tasks_count 
            ELSE (w2.waiting_tasks_count - w1.waiting_tasks_count) 
        END AS waiting_tasks_count_delta, 
        CASE 
            WHEN (w2.wait_time_ms - w1.wait_time_ms) < 0 THEN w2.wait_time_ms
            ELSE (w2.wait_time_ms - w1.wait_time_ms)
        END / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) AS resource_wait_time_delta, 
        CASE 
            WHEN (w2.signal_wait_time_ms - w1.signal_wait_time_ms) < 0 THEN w2.signal_wait_time_ms 
            ELSE (w2.signal_wait_time_ms - w1.signal_wait_time_ms) 
        END / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        w2.wait_time_ms_cumulative
    -- Self-join - w1 represents wait stats at the beginning of the sample interval, while w2 
    -- shows the wait stats at the end of the interval. 
    FROM wait_times AS w1 
    INNER JOIN wait_times AS w2 ON w1.wait_type = w2.wait_type AND w1.[rank] = w2.[rank]-1 

    UNION ALL 

    -- Treat sum of all signal waits as CPU "wait time"
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        'CPU' AS category_name, 
        'CPU (Signal Wait)' AS wait_type, 
        0 AS waiting_tasks_count_delta, 
        -- Handle wait stats resets
        CASE 
            WHEN (SUM (w2.signal_wait_time_ms) - SUM (w1.signal_wait_time_ms)) < 0 THEN SUM (w2.signal_wait_time_ms)
            ELSE (SUM (w2.signal_wait_time_ms) - SUM (w1.signal_wait_time_ms)) 
        END / CAST (DATEDIFF (second, w1.collection_time, w2.collection_time) AS decimal) AS resource_wait_time_delta, 
        0 AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        w2.wait_time_ms_cumulative
    FROM wait_times AS w1
    INNER JOIN wait_times AS w2 ON w1.wait_type = w2.wait_type AND w1.[rank] = w2.[rank]-1
    -- Only return CPU stats if we weren't passed in a specific wait category
    WHERE (@category_name IS NULL OR @category_name = 'CPU')
    GROUP BY w1.collection_time, w2.collection_time, w2.wait_time_ms_cumulative

    UNION ALL 

    -- Get actual used CPU time from perfmon data (average across the whole snapshot_time_id interval, 
    -- and use this average for each sample time in this interval).  Note that the "% Processor Time" 
    -- counter in the "Process" object can exceed 100% (for example, it can range from 0-800% on an 
    -- 8 CPU server). 
    SELECT
        CONVERT (datetime, SWITCHOFFSET (CAST (w2.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        'CPU' AS category_name, 
        'CPU (Consumed)' AS wait_type, 
        0 AS waiting_tasks_count_delta, 
        -- Get sqlservr %CPU usage for the perfmon sample that immediately precedes 
        -- each wait stats sample.  Multiply by 10 to convert "% CPU" to "ms CPU/sec". 
        10 * (  
            SELECT TOP 1 formatted_value
            FROM snapshots.performance_counters AS pc
            INNER JOIN core.snapshots s ON pc.snapshot_id = s.snapshot_id
            WHERE pc.performance_object_name = 'Process' AND pc.performance_counter_name = '% Processor Time' 
                AND pc.performance_instance_name = 'sqlservr'
                AND s.snapshot_id < @current_snapshot_id
                AND s.instance_name = @instance_name
                AND pc.snapshot_id < w2.snapshot_id
            ORDER BY pc.snapshot_id DESC
        ) AS resource_wait_time_delta, 
        0 AS resource_signal_time_delta, 
        DATEDIFF (second, w1.collection_time, w2.collection_time) AS interval_sec, 
        NULL
    FROM wait_times AS w1
    INNER JOIN wait_times AS w2 ON w1.wait_type = w2.wait_type AND w1.[rank] = w2.[rank]-1
    -- Only return CPU stats if we weren't passed in a specific wait category
    WHERE (@category_name IS NULL OR @category_name = 'CPU')
    GROUP BY w1.collection_time, w2.collection_time, w2.snapshot_id

    ORDER BY collection_time, category_name, wait_type
    -- These trace flags are necessary for a good plan, due to the join on ascending core.snapshot PK
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390);

END;
GO

GRANT EXECUTE ON [snapshots].[rpt_wait_stats_one_snapshot]              TO [mdw_reader]
GO

--
-- snapshots.rpt_server_activity
--  Returns values for various perf counters indicating server's activity
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @start_time - (Optional) time window start (UTC)
--    @end_time - time window end (UTC)
--    @time_window_size - Number of intervals in the time window (provide if @start_time is NULL)
--    @time_interval_min - Number of minutes in each interval (provide if @start_time is NULL)
--
IF (NOT OBJECT_ID(N'snapshots.rpt_server_activity', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_server_activity] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_server_activity]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_server_activity] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_server_activity]
    @instance_name sysname,
    @start_time datetime = NULL,
    @end_time datetime = NULL,
    @time_window_size smallint = NULL,
    @time_interval_min smallint = NULL
AS
BEGIN
    SET NOCOUNT ON;

    -- Convert snapshot_time (datetimeoffset) to a UTC datetime
    IF (@end_time IS NULL)
        SET @end_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MAX(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));

    IF (@start_time IS NULL)
    BEGIN
        -- If time_window_size and time_interval_min are set use them
        -- to determine the start time
        -- Otherwise use the earliest available snapshot_time
        IF @time_window_size IS NOT NULL AND @time_interval_min IS NOT NULL
        BEGIN
            SET @start_time = DATEADD(minute, @time_window_size * @time_interval_min * -1.0, @end_time);
        END
        ELSE
        BEGIN
            -- Convert min snapshot_time (datetimeoffset) to a UTC datetime
            SET @start_time = CONVERT (datetime, SWITCHOFFSET (CAST ((SELECT MIN(snapshot_time) FROM core.snapshots) AS datetimeoffset(7)), '+00:00'));
        END
    END

    DECLARE @end_snapshot_time_id int;
    SELECT @end_snapshot_time_id = MAX(snapshot_time_id) FROM core.snapshots WHERE snapshot_time <= @end_time;

    DECLARE @start_snapshot_time_id int;
    SELECT @start_snapshot_time_id = MIN(snapshot_time_id) FROM core.snapshots WHERE snapshot_time >= @start_time;


    SELECT 
        SUBSTRING(pc.path, CHARINDEX(N'\', pc.path, 2)+1, LEN(pc.path) - CHARINDEX(N'\', pc.path, 2)) as series,
        s.snapshot_time_id,
        CONVERT (datetime, SWITCHOFFSET (CAST (s.snapshot_time AS datetimeoffset(7)), '+00:00')) AS snapshot_time, 
        CONVERT (datetime, SWITCHOFFSET (CAST (pc.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        pc.formatted_value
    FROM snapshots.performance_counters pc
    JOIN core.snapshots s ON (s.snapshot_id = pc.snapshot_id)
    WHERE s.instance_name = @instance_name
        AND s.snapshot_time_id BETWEEN @start_snapshot_time_id AND @end_snapshot_time_id
        AND (pc.performance_object_name LIKE '%SQL%:General Statistics' OR pc.performance_object_name LIKE '%SQL%:SQL Statistics')
        AND pc.performance_counter_name IN ('Logins/sec', 'Logouts/sec', 'Batch Requests/sec', 'Transactions', 
            'User Connections', 'SQL Compilations/sec', 'SQL Re-Compilations/sec')
    ORDER BY 
        pc.collection_time, series
    -- These trace flags are necessary for a good plan, due to the join on ascending PK w/range filter
    OPTION (QUERYTRACEON 2389, QUERYTRACEON 2390)
END;
GO

GRANT EXECUTE ON [snapshots].[rpt_server_activity]                      TO [mdw_reader]
GO

--
-- snapshots.rpt_wait_stats_one_snapshot_one_category
--  Returns details of wait stats for one snapshot and one wait category
-- Parameters: 
--    @instance_name - SQL Server instance name
--    @snapshot_time_id - time window identifier (snapshot ID from core.snapshots) 
--    @category_name - Name of wait category to filter on
--
IF (NOT OBJECT_ID(N'snapshots.rpt_wait_stats_one_snapshot_one_category', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_wait_stats_one_snapshot_one_category] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_wait_stats_one_snapshot_one_category]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_wait_stats_one_snapshot_one_category] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_wait_stats_one_snapshot_one_category]
    @instance_name sysname,
    @snapshot_time_id int,
    @category_name nvarchar(20)
AS
BEGIN
    SET NOCOUNT ON;
    
    -- Use CTE to select first all raw data that we neeed
    -- For each snapshot append a [rank] column, which later will be used to do the self-join
    WITH wait_times AS 
    (
    -- First part gets resource wait times for each wait_type,
    -- calculated as wait_time_ms - signal_wait_time_ms 
    SELECT 
        DENSE_RANK() OVER (ORDER BY ws1.collection_time) AS [rank],
        s.snapshot_time_id,
        ws1.collection_time, 
        ct.category_name,
        ev.wait_type,
        ws1.waiting_tasks_count,
        ws1.wait_time_ms - ws1.signal_wait_time_ms as resource_wait_time_ms
    FROM core.snapshots s
    JOIN snapshots.os_wait_stats ws1 on (ws1.snapshot_id = s.snapshot_id)
    JOIN core.wait_types ev on (ev.wait_type = ws1.wait_type)
    JOIN core.wait_categories ct on (ct.category_id = ev.category_id)
    WHERE ct.category_name = @category_name
      AND s.instance_name = @instance_name
      AND (s.snapshot_time_id = @snapshot_time_id OR s.snapshot_time_id = @snapshot_time_id - 1)
    )
    -- Do a self-join to calculate deltas between snapshots
    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (t1.collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        t1.wait_type,
        (t1.waiting_tasks_count - t2.waiting_tasks_count) AS waiting_tasks_count_delta,
        (t1.resource_wait_time_ms - ISNULL(t2.resource_wait_time_ms,0))/CAST(DATEDIFF(second, t2.collection_time, t1.collection_time) AS decimal) AS resource_wait_time_delta
    FROM wait_times t1
    LEFT OUTER JOIN wait_times t2 on (t2.rank = t1.rank-1 and t2.wait_type = t1.wait_type)
    WHERE t1.snapshot_time_id = @snapshot_time_id
    ORDER BY collection_time, wait_type
END;
GO
GRANT EXECUTE ON [snapshots].[rpt_wait_stats_one_snapshot_one_category]  TO [mdw_reader]
GO



--
-- snapshots.rpt_sql_process_memory
--  Returns details of the SQL process' memory usage
-- Parameters: 
--    @ServerName - SQL Server instance name
--    @EndTime - End of the user-selected time window (UTC)
--    @WindowSize - Number of minutes in the time window 
--
IF (NOT OBJECT_ID(N'snapshots.rpt_sql_process_memory', 'P') IS NULL)
BEGIN
    RAISERROR('Dropping procedure [snapshots].[rpt_sql_process_memory] ...', 0, 1)  WITH NOWAIT;
    DROP PROCEDURE [snapshots].[rpt_sql_process_memory]
END
GO 

RAISERROR('Creating procedure [snapshots].[rpt_sql_process_memory] ...', 0, 1)  WITH NOWAIT;
GO
CREATE PROCEDURE [snapshots].[rpt_sql_process_memory]
    @ServerName sysname,
    @EndTime datetime = NULL,
    @WindowSize int
AS
BEGIN
    SET NOCOUNT ON;
    
    -- Divide our time window up into 40 evenly-sized time intervals, and find the last collection_time within each of these intervals
    CREATE TABLE #intervals (
        interval_time_id        int, 
        interval_start_time     datetimeoffset(7),
        interval_end_time       datetimeoffset(7),
        interval_id             int, 
        first_collection_time   datetimeoffset(7), 
        last_collection_time    datetimeoffset(7), 
        first_snapshot_id       int,
        last_snapshot_id        int, 
        source_id               int, 
        snapshot_id             int, 
        collection_time         datetimeoffset(7), 
        collection_time_id      int
    )
    -- GUID 49268954-... is the Server Activity CS
    INSERT INTO #intervals
    EXEC [snapshots].[rpt_interval_collection_times] 
        @ServerName, @EndTime, @WindowSize, 'snapshots.sql_process_and_system_memory', '49268954-4FD4-4EB6-AA04-CD59D9BB5714', 40, 0

    SELECT 
        CONVERT (datetime, SWITCHOFFSET (CAST (collection_time AS datetimeoffset(7)), '+00:00')) AS collection_time, 
        CASE 
            WHEN series = 'sql_virtual_address_space_reserved_kb' THEN 'Virtual Memory Reserved'
            WHEN series = 'sql_virtual_address_space_committed_kb' THEN 'Virtual Memory Committed'
            WHEN series = 'sql_physical_memory_in_use_kb' THEN 'Physical Memory In Use'
            WHEN series = 'process_physical_memory_low' THEN 'Physical Memory Low'
            WHEN series = 'process_virtual_memory_low' THEN 'Virtual Memory Low'
            ELSE series
        END AS series,
        [value] / 1024 AS [value] -- convert KB to MB
    FROM 
    (
        SELECT 
            pm.collection_time,
            pm.sql_virtual_address_space_reserved_kb,
            pm.sql_virtual_address_space_committed_kb,
            pm.sql_physical_memory_in_use_kb,
            CONVERT (bigint, pm.process_physical_memory_low) AS process_physical_memory_low,
            CONVERT (bigint, pm.process_virtual_memory_low) AS process_virtual_memory_low
        FROM [snapshots].[sql_process_and_system_memory] AS pm
        INNER JOIN #intervals AS i ON (i.last_snapshot_id = pm.snapshot_id AND i.last_collection_time = pm.collection_time)
    ) AS pvt
    UNPIVOT
    (
        [value] for [series] in 
            (sql_virtual_address_space_reserved_kb, sql_virtual_address_space_committed_kb, 
            sql_physical_memory_in_use_kb)
    ) AS unpvt
END
GO
GRANT EXECUTE ON [snapshots].[rpt_sql_process_memory]                   TO [mdw_reader]
GO



/**********************************************************************/
/* END OF LEGACY OBJECTS                                              */
/**********************************************************************/



/**********************************************************************/
/* OPERATOR CHECK                                                     */
/**********************************************************************/

-- This trigger needs to be defined after we have created all our tables,
-- otherwise it would interfere with them
IF EXISTS (SELECT object_id FROM sys.triggers WHERE name = N'add_operator_check' AND type = N'TR')
BEGIN
    RAISERROR('Dropping database trigger [add_operator_check] ...', 0, 1)  WITH NOWAIT;
    DROP TRIGGER [add_operator_check] ON DATABASE
END
GO 

RAISERROR('Creating database trigger [add_operator_check] ...', 0, 1)  WITH NOWAIT;
GO
CREATE TRIGGER [add_operator_check]
ON DATABASE
WITH EXECUTE AS 'mdw_check_operator_admin'
FOR CREATE_TABLE 
AS 
BEGIN
    DECLARE @schema_name sysname;
    DECLARE @table_name sysname;

    -- Set options required by the rest of the code in this SP.
    SET ANSI_NULLS ON
    SET ANSI_PADDING ON
    SET ANSI_WARNINGS ON
    SET ARITHABORT ON
    SET CONCAT_NULL_YIELDS_NULL ON
    SET NUMERIC_ROUNDABORT OFF
    SET QUOTED_IDENTIFIER ON 


    SELECT @schema_name = EVENTDATA().value('(/EVENT_INSTANCE/SchemaName)[1]', 'sysname')
    IF (@schema_name = N'custom_snapshots')
    BEGIN
        SELECT @table_name = EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname')

        -- Dynamically add a constraint on the newly created table
        -- Table must have the snapshot_id column
        DECLARE @check_name sysname;
        SELECT @check_name = N'CHK_check_operator_' + CONVERT(nvarchar(36), NEWID());
        DECLARE @sql nvarchar(2000);
        SELECT @sql = N'ALTER TABLE ' + QUOTENAME(@schema_name) + N'.' + QUOTENAME(@table_name) +
                      N' ADD CONSTRAINT ' + QUOTENAME(@check_name) + ' CHECK (core.fn_check_operator(snapshot_id) = 1);';

        -- We dont expect any result set returned while executing ALTER TABLE statement in Dynamic SQL
        EXEC(@sql)
        WITH RESULT SETS NONE
        
        -- Dynamically revoke the CONTROL right on the table for mdw_writer
        -- That way mdw_writer creates the table but cannot remove it or alter it
        SELECT @sql = N'DENY ALTER ON ' + QUOTENAME(@schema_name) + N'.' + QUOTENAME(@table_name) +
                      N'TO [mdw_writer]';

        -- We dont expect any result set returned while executing DENY statement in Dynamic SQL
        EXEC(@sql)
        WITH RESULT SETS NONE
    END
END;
GO



IF EXISTS (SELECT object_id FROM sys.triggers WHERE name = N'deny_drop_table' AND type = N'TR')
BEGIN
    RAISERROR('Dropping database trigger [deny_drop_table] ...', 0, 1)  WITH NOWAIT;
    DROP TRIGGER [deny_drop_table] ON DATABASE
END
GO 

RAISERROR('Creating database trigger [deny_drop_table] ...', 0, 1)  WITH NOWAIT;
GO
CREATE TRIGGER [deny_drop_table]
ON DATABASE
FOR DROP_TABLE 
AS 
BEGIN
    -- Security check (role membership)
    IF (NOT (ISNULL(IS_MEMBER(N'mdw_admin'), 0) = 1) AND NOT (ISNULL(IS_SRVROLEMEMBER(N'sysadmin'), 0) = 1))
    BEGIN
        RAISERROR(14677, 16, -1, 'mdw_admin');
    END;
END;
GO

/**********************************************************************/
/* install system collector types                                     */
/**********************************************************************/
RAISERROR('', 0, 1)  WITH NOWAIT;
RAISERROR('Adding collector types...', 0, 1)  WITH NOWAIT;
GO

-- Install TSQL Query collector type
EXEC [core].[sp_add_collector_type] @collector_type_uid = '302e93d1-3424-4be7-aa8e-84813ecf2419'
GO

-- Install SqlTrace collector type
EXEC [core].[sp_add_collector_type] @collector_type_uid = '0E218CF8-ECB5-417B-B533-D851C0251271'
GO

-- Install Performance Counters collector type
EXEC [core].[sp_add_collector_type] @collector_type_uid = '294605dd-21de-40b2-b20f-f3e170ea1ec3'

-- Install Query Activity collectory type
EXEC [core].[sp_add_collector_type] @collector_type_uid = '14AF3C12-38E6-4155-BD29-F33E7966BA23'
GO




/**********************************************************************/
/* Data Purge job                                            */
/**********************************************************************/
RAISERROR('', 0, 1)  WITH NOWAIT;
RAISERROR('Creating purge data job...', 0, 1)  WITH NOWAIT;
GO

BEGIN TRY
    BEGIN TRANSACTION;

    DECLARE @job_owner NVARCHAR(256);
    SELECT @job_owner = SUSER_SNAME();

    DECLARE @db_name sysname
    SELECT @db_name = DB_NAME();

    DECLARE @job_name_for_purge sysname
    SELECT @job_name_for_purge = N'mdw_purge_data_' + QUOTENAME(@db_name)

    DECLARE @job_id UNIQUEIDENTIFIER; 
    SELECT @job_id = job_id FROM [msdb].[dbo].[sysjobs_view] WHERE [name] = @job_name_for_purge
    IF @job_id IS NULL
    BEGIN 
        -- Add job category if it does not exist. On Katmai server this will be already created.
        IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'Data Collector' AND category_class=1)
        BEGIN
            EXEC msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'Data Collector'
        END

        -- Add job
        EXEC msdb.dbo.sp_add_job 
                @job_name=@job_name_for_purge, 
                @enabled=1, 
                @notify_level_eventlog=0, 
                @notify_level_email=0, 
                @notify_level_netsend=0, 
                @notify_level_page=0, 
                @delete_level=0, 
                @description=N'Runs every day and removes data from Management Data Warehouse database that reached its expiration date.', 
                @category_name=N'Data Collector', 
                @owner_login_name=@job_owner,
                @job_id = @job_id OUTPUT;

        -- Add job step
        EXEC msdb.dbo.sp_add_jobstep 
                @job_id=@job_id, 
                @step_name=N'Step 1', 
                @step_id=1, 
                @cmdexec_success_code=0, 
                @on_success_action=1, 
                @on_success_step_id=0, 
                @on_fail_action=2, 
                @on_fail_step_id=0, 
                @retry_attempts=0, 
                @retry_interval=0, 
                @os_run_priority=0, 
                @subsystem=N'TSQL', 
                @command=N'exec core.sp_purge_data', 
                @database_name=@db_name, 
                @flags=4;

        -- Set the job step to start
        EXEC msdb.dbo.sp_update_job @job_id = @job_id, @start_step_id = 1;

        -- Add schedule
        EXEC msdb.dbo.sp_add_jobschedule 
                @job_id=@job_id, 
                @name=N'mdw_purge_data_schedule', 
                @enabled=1, 
                @freq_type=4, 
                @freq_interval=1, 
                @freq_subday_type=1, 
                @freq_subday_interval=0, 
                @freq_relative_interval=0, 
                @freq_recurrence_factor=0, 
                @active_start_time=20000;

        -- Set the job server
        EXEC msdb.dbo.sp_add_jobserver @job_id = @job_id, @server_name = N'(local)';
    END
    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    IF (@@TRANCOUNT > 0) 
        ROLLBACK TRANSACTION

    -- Rethrow the error
    DECLARE @ErrorMessage   NVARCHAR(4000);
    DECLARE @ErrorSeverity  INT;
    DECLARE @ErrorState     INT;
    DECLARE @ErrorNumber    INT;
    DECLARE @ErrorLine      INT;
    DECLARE @ErrorProcedure NVARCHAR(200);
    SELECT @ErrorLine = ERROR_LINE(),
           @ErrorSeverity = ERROR_SEVERITY(),
           @ErrorState = ERROR_STATE(),
           @ErrorNumber = ERROR_NUMBER(),
           @ErrorMessage = ERROR_MESSAGE(),
           @ErrorProcedure = ISNULL(ERROR_PROCEDURE(), '-');

    RAISERROR (14684, -1, -1 , @ErrorNumber, @ErrorSeverity, @ErrorState, @ErrorProcedure, @ErrorLine, @ErrorMessage);

END CATCH
GO



SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

/**********************************************************************/
/* UTILITY SCHEMAS                                                    */
/* Utility objects (in MDW) live in one of the following schemas      */
/*   [Snapshots] - stores the "live" tables (the tables loaded by DC  */
/*                 collection items                                   */
/*   [Sysutility_ucp_staging] - contains the views, SPs, functions    */
/*                 that help to transform the "live" table data into  */
/*                 the "cache" tables (see below)                     */
/*   [sysutility_ucp_core] - contains the cleansed tables, and     */
/*                 views, SPs, and functions that represent the guts  */
/*                 of the Utility data warehouse                      */
/*   [sysutility_ucp_misc] - contains other objects that don't fit    */
/*                 the schemas above. This schema currently contains  */
/*                 only two objects (both functions), and should be   */
/*                 eliminated at some point                           */
/*                                                                    */
/* In an ideal world, the UCP tables in the [snapshots] schema would  */
/* live in the [sysutility_ucp_staging] schema instead. Current DC    */
/* limitations do not allow for that                                  */
/*                                                                    */
/* The objects in the [snapshots] and [sysutility_ucp_staging] schema */
/* should be entirely private to the MDW database. Only objects in    */
/* the [sysutility_ucp_core] schema and the [sysutility_ucp_misc]  */
/* schemas should be referenced externally - either by objects in MSDB*/
/* or by Utility object model code                                    */
/**********************************************************************/
RAISERROR('', 0, 1)  WITH NOWAIT;
BEGIN
  DECLARE @sql nvarchar(128)

  -- 
  -- Unfortunately, CREATE SCHEMA needs to be the only statement in its 
  -- batch. That's why we use dynamic sql below
  --
  
  RAISERROR('Create schema [sysutility_ucp_misc]', 0, 1)  WITH NOWAIT;
  IF (SCHEMA_ID('sysutility_ucp_misc') IS NULL)
  BEGIN
    SET @sql = 'CREATE SCHEMA [sysutility_ucp_misc]'
    EXEC sp_executesql @sql
  END
  
  RAISERROR('Create schema [sysutility_ucp_staging]', 0, 1)  WITH NOWAIT;
  IF (SCHEMA_ID('sysutility_ucp_staging') IS NULL)
  BEGIN
    SET @sql = 'CREATE SCHEMA [sysutility_ucp_staging]'
    EXEC sp_executesql @sql
  END
  
  RAISERROR('Create schema [sysutility_ucp_core]', 0, 1)  WITH NOWAIT;
  IF (SCHEMA_ID('sysutility_ucp_core') IS NULL)
  BEGIN
    SET @sql = 'CREATE SCHEMA [sysutility_ucp_core]'
    EXEC sp_executesql @sql
  END
  
END
GO



-----------------------------------------------------------------------
-- View sysutility_ucp_misc.utility_objects_internal
--   Categorizes all Utility-owned objects in the MDW database.  Used, for 
--   example, to dynamically discover which tables are cache tables in order 
--   to update stats on them, or to purge data from all tables that play a 
--   given role. 
-----------------------------------------------------------------------
IF OBJECT_ID ('sysutility_ucp_misc.utility_objects_internal') IS NOT NULL
BEGIN
   RAISERROR('Dropping view sysutility_ucp_misc.utility_objects_internal', 0, 1) WITH NOWAIT;
   DROP VIEW sysutility_ucp_misc.utility_objects_internal;
END
GO

RAISERROR('Creating view sysutility_ucp_misc.utility_objects_internal', 0, 1) WITH NOWAIT;
GO

CREATE VIEW sysutility_ucp_misc.utility_objects_internal AS 
SELECT 
   SCHEMA_NAME (obj.[schema_id]) AS [object_schema], 
   obj.name AS [object_name], 
   obj.type_desc AS sql_object_type, 
   CAST (prop.value AS varchar(60)) AS utility_object_type 
FROM sys.extended_properties AS prop
INNER JOIN sys.objects AS obj ON prop.major_id = obj.[object_id]
WHERE prop.name = 'MS_UtilityObjectType';
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'MISC', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_misc', 
   @level1type = 'VIEW', @level1name = 'utility_objects_internal';
GO


-----------------------------------------------------------------------
-- Function to get max file size using growth type = KB. Parameters include file_size, max_size, growth size (all in KB)
-- The algorithm is:
--    the max file size = (max_size - ((max_size - file_size) % growth_size))
-- 
-- max_size - file_size means the remaining available free space.
-- because we grow a fixed chunk specified by growth_size, so we need to figure out
-- what would the remainder free space after the maximum growth, and then subtract that
-- from max_size.
-----------------------------------------------------------------------
IF (OBJECT_ID(N'[sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_kb]') IS NOT NULL)
BEGIN
   RAISERROR('Dropping function [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_kb]', 0, 1)  WITH NOWAIT;
   DROP FUNCTION [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_kb]
END
GO

RAISERROR('Creating function [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_kb]', 0, 1)  WITH NOWAIT;
GO
CREATE FUNCTION [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_kb](
                @file_size_kb REAL,
                @max_size_kb REAL, 
                @growth_size_kb REAL)
RETURNS REAL 
AS
BEGIN
    DECLARE @max_size_available_kb REAL;
    
    SELECT @max_size_available_kb = @file_size_kb;
    
    IF (@growth_size_kb > 0 AND @max_size_kb > @file_size_kb)
    BEGIN
        SELECT @max_size_available_kb = 
            (@max_size_kb - 
              CONVERT(REAL, CONVERT(BIGINT, @max_size_kb - @file_size_kb) % CONVERT(BIGINT, @growth_size_kb)))
    END
    
    RETURN @max_size_available_kb
END
GO

-----------------------------------------------------------------------
-- Function to get max file size using growth type = percentage. Parameters include file_size, max_size (in KB) and percentage
-- The algorithm is:
--    exp = log(  (max_size / file_size) / (1 + growth_percent / 100)  )
--    the max file size = file_size * (1 + growth_percent / 100) ^ exp
-----------------------------------------------------------------------
IF (OBJECT_ID(N'[sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_percent]') IS NOT NULL)
BEGIN
   RAISERROR('Dropping function [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_percent]', 0, 1)  WITH NOWAIT;
   DROP FUNCTION [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_percent]
END
GO

RAISERROR('Creating function [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_percent]', 0, 1)  WITH NOWAIT;
GO
CREATE FUNCTION [sysutility_ucp_misc].[fn_get_max_size_available_by_growth_type_percent](
                @file_size_kb REAL,
                @max_size_kb REAL, 
                @growth_percent REAL)
RETURNS REAL 
AS
BEGIN
    DECLARE @max_size_available_kb REAL;
    DECLARE @one_plus_growth_percent REAL;
    DECLARE @exponent REAL;
    
    SELECT @max_size_available_kb = @file_size_kb;
    SELECT @one_plus_growth_percent = 1 + @growth_percent / 100;
    --- @file_size_kb > 0 is added to avoid the divided by zero exception. When a database is in the Emergency state, the size
    --- of its log file is zero. 
    IF (@growth_percent > 0 AND @max_size_kb > @file_size_kb AND @file_size_kb > 0)
    BEGIN
        SELECT @exponent = FLOOR(LOG10(@max_size_kb / @file_size_kb) / LOG10(@one_plus_growth_percent));
        SELECT @max_size_available_kb = @file_size_kb * POWER(@one_plus_growth_percent, @exponent);
    END
    
    RETURN @max_size_available_kb
END
GO

-----------------------------------------------------------------------
--  Function to get max size available depending on the file_size, its mx_size, growth and free_space_on_drive (everything in KB).
--  Algorithm used for calculating max size available on the drive:
--
--      if no auto grow then return the current file size, done.
--
--      assuming auto grow now:
--      if max size is configured and it's less than the (current file size + volume free space)
--          which means the max size boundary is in effect, then we use the max_size as the boundary.
--      if max size is not configured (unlimited growth) or it's already bigger than the remaining size,
--          which means the max size boundary is NOT in effect, then we use the (curent file size + volume free space)
--          as the boundary.
--
--      now we got the boundary size, we need to take a look the growth type:
--      if growth type = kb (0), then we simply try to fit as many chunks as possible (need to use % operation here)
--      if growth type = percent (1), then it's a little tricky, we need to find the max exp where
--          (current file size) * (1 + growth percent) ^ exp <= boundary size.
--      log/power operations involved. one we figure out the exp, we can compute the max available size.
-- 
--  NOTE: the return value is 
--      (the current file size      +   the max possible additional space the file can grow into)
-----------------------------------------------------------------------
IF (OBJECT_ID(N'[sysutility_ucp_misc].[fn_get_max_size_available]') IS NOT NULL)
BEGIN
   RAISERROR('Dropping function [sysutility_ucp_misc].[fn_get_max_size_available]', 0, 1)  WITH NOWAIT;
   DROP FUNCTION [sysutility_ucp_misc].[fn_get_max_size_available]
END
GO

RAISERROR('Creating function [sysutility_ucp_misc].[fn_get_max_size_available]', 0, 1)  WITH NOWAIT;
GO
CREATE FUNCTION [sysutility_ucp_misc].[fn_get_max_size_available](
                @file_size_kb REAL,
                @max_size_kb REAL, 
                @growth REAL,       --  growth is KB when type is not percentage, or a whole number percentage when percentage
                @smo_growth_type SMALLINT,  -- @smo_growth_type == 0 is KB growth, == 1 means percentage, or == 99 not supported to grow
                @free_space_on_drive_kb BIGINT)
RETURNS REAL 
AS
BEGIN
    DECLARE @max_size_available_kb REAL;
    DECLARE @projected_max_file_size_kb REAL;
    
    -- Be conservative and initialize total space to @file_size_kb (assume no autogrow)
    SELECT @max_size_available_kb  = @file_size_kb;
    
    -- Let projected size be the current file size + volume free space (assuming no one else is competing and its completely available for this file)
    SELECT @projected_max_file_size_kb = @file_size_kb + @free_space_on_drive_kb;

    -- No auto grow, return the configured file size
    IF (@smo_growth_type = 99)
    BEGIN
        SELECT @max_size_available_kb = @file_size_kb;
    END
    ELSE
    BEGIN
        IF (0 < @max_size_kb AND @max_size_kb < @projected_max_file_size_kb)
        BEGIN
            -- if maxsize is configured and it's less than the project space
            -- then we use the maxsize as the growth boundary.
            SELECT @max_size_available_kb =
                CASE
                    WHEN (@smo_growth_type = 1) -- percent growth
                    THEN sysutility_ucp_misc.fn_get_max_size_available_by_growth_type_percent(@file_size_kb, @max_size_kb, @growth)

                    WHEN (@smo_growth_type = 0) -- KB growth
                    THEN sysutility_ucp_misc.fn_get_max_size_available_by_growth_type_kb(@file_size_kb, @max_size_kb, @growth)
                    
                    ELSE @max_size_kb
                END
        END
        ELSE
        BEGIN
            -- either maxsize is not configured, in this case we use the project space
            -- or maxsize is bigger than the project space, and we suse the project space as well.
            SELECT @max_size_available_kb =
                CASE
                    WHEN (@smo_growth_type = 1) -- percent growth
                    THEN sysutility_ucp_misc.fn_get_max_size_available_by_growth_type_percent(@file_size_kb, @projected_max_file_size_kb, @growth)

                    WHEN (@smo_growth_type = 0) -- KB growth
                    THEN sysutility_ucp_misc.fn_get_max_size_available_by_growth_type_kb(@file_size_kb, @projected_max_file_size_kb, @growth)
                    
                    ELSE @projected_max_file_size_kb
                END
        END
    END

    -- VSTS 351411
    -- In SQL2008 and SQL2008 R2, unfortunately the support for file stream file
    -- in SMO and dmv aren't complete: it always return 0 for everything including 
    -- @file_size_kb, @max_size_kb, growth, thus walking through the code path above, 
    -- we'll return 0, then the our data file storage utilization property use this 
    -- value (as the denominator) to compute the percentage which would result 
    -- in divide by zero (DBZ).
    -- 
    -- So for that case, we simply return the @projected_max_file_size_kb to avoid DBZ. Of course
    -- there is a tiny chance @projected_max_file_size_kb is also 0 (due to volume free space is 0
    -- the volume is full!) so we'll simply return 1 (kb)
    --
    -- Note 1: to avoid comparing equality of double-typed variable to 0, check against 1 KB
    --         it wouldn't be any faster but readability is lower.
    IF (@max_size_available_kb < 1.0) 
    BEGIN
        SELECT @max_size_available_kb = @projected_max_file_size_kb;
        -- what if @projected_max_file_size_kb is still 0 (or near 0)? use 1 kb.
        IF (@max_size_available_kb < 1.0)
        BEGIN
            SELECT @max_size_available_kb = 1.0;
        END
    END
    
    RETURN @max_size_available_kb;
END
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'MISC', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_misc', 
   @level1type = 'FUNCTION', @level1name = 'fn_get_max_size_available';
GO


-------------------------------------------------------------------------
-- Create table: sysutility_ucp_batch_manifests_internal
-- This is the target table to store the manifest information for every batch collected 
-- The purpose of the manifest is to qualify the integrity of the data uploaded on the UCP.
-- The batch manifest primarily includes:
-- 1. server_instance_name: the server\instance name of the MI
-- 2. xx_row_count: row count for each of the table collected/uploaded by the utility T-SQL collection item query
-- 3. batch_time: the batch creation date-time stamp 
--
-- Note: the manifest info is stored in the form of name/value pair. This pattern is 
-- used is to minimize structural changes to the table in case there are collection 
-- queries added/removed in future that affects the manifest.
-------------------------------------------------------------------------
IF NOT EXISTS (SELECT name FROM sys.objects WHERE object_id = OBJECT_ID(N'[snapshots].[sysutility_ucp_batch_manifests_internal]') )
BEGIN
    RAISERROR('Creating table [snapshots].[sysutility_ucp_batch_manifests_internal]', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[sysutility_ucp_batch_manifests_internal]
    (
        [server_instance_name]  SYSNAME NOT NULL,
        [batch_time]            DATETIMEOFFSET(7) NOT NULL,  
        [parameter_name]        SYSNAME NOT NULL,           -- Name, representing the collection query uploading data to the live table
        [parameter_value]       SQL_VARIANT NULL,           -- Value, indicating the number of rows collected/uploaded by the respective query
        [collection_time]       DATETIMEOFFSET(7) NULL,
        [snapshot_id]           INT NULL
    ) ON [PRIMARY]

    ALTER TABLE [snapshots].[sysutility_ucp_batch_manifests_internal]  WITH CHECK ADD  CONSTRAINT [FK_sysutility_ucp_batch_manifests_internal] FOREIGN KEY([snapshot_id])
    REFERENCES [core].[snapshots_internal] ([snapshot_id])
    ON DELETE CASCADE
    ALTER TABLE [snapshots].[sysutility_ucp_batch_manifests_internal] CHECK CONSTRAINT [FK_sysutility_ucp_batch_manifests_internal]

    ALTER TABLE [snapshots].[sysutility_ucp_batch_manifests_internal]  WITH CHECK ADD  CONSTRAINT [CHK_check_operator_E4F8A95D-2C44-48B6-85BA-E78E47C7ACCE] CHECK  (([core].[fn_check_operator]([snapshot_id])=(1)))
    ALTER TABLE [snapshots].[sysutility_ucp_batch_manifests_internal] CHECK CONSTRAINT [CHK_check_operator_E4F8A95D-2C44-48B6-85BA-E78E47C7ACCE]

    -- This index is used by the caching job to identify the latest consistent batches 
    -- This index is also used by the DC purge job to seek against snapshot_id
    CREATE CLUSTERED INDEX CI_sysutility_ucp_batch_manifests_internal 
        ON [snapshots].[sysutility_ucp_batch_manifests_internal](snapshot_id)
    
    EXEC sp_addextendedproperty 
        @name = 'MS_UtilityObjectType', @value = 'LIVE', 
        @level0type = 'SCHEMA', @level0name = 'snapshots', 
        @level1type = 'TABLE', @level1name = 'sysutility_ucp_batch_manifests_internal';
END
GO


--*********************************************************************
-- Create table: consistent_batch_manifests_internal
-- This table stores the consistent batch information for the enrolled MI's.
-- The data returned by the view consistent_batch_manifests is stored 
-- in this table and is consumed by the caching job step.  
--*********************************************************************
IF (OBJECT_ID(N'[sysutility_ucp_staging].[consistent_batch_manifests_internal]', 'U') IS NULL)
BEGIN
   RAISERROR('Creating [sysutility_ucp_staging].[consistent_batch_manifests_internal] table', 0, 1)  WITH NOWAIT;
   CREATE TABLE [sysutility_ucp_staging].[consistent_batch_manifests_internal]
   (
        [server_instance_name]      SYSNAME NOT NULL,
        [batch_time]                DATETIMEOFFSET NOT NULL
        
      CONSTRAINT PK_consistent_batch_manifests_internal PRIMARY KEY CLUSTERED 
         (server_instance_name, batch_time) 
   )
   
   EXEC sp_addextendedproperty 
      @name = 'MS_UtilityObjectType', @value = 'STAGING', 
      @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', 
      @level1type = 'TABLE', @level1name = 'consistent_batch_manifests_internal';
END;
GO

----------------------------------------------------------------------------
-- Table [sysutility_ucp_dac_collected_execution_statistics_internal]
--    Stores the output of sp_sysutility_instance_retrieve_dac_execution_statistics, 
--    which is executed by the Utility Data Collector collection set to retrieve DAC 
--    info, including execution statistics like the amount of CPU time being consumed 
--    by each DAC.  The execution statistics (%CPU) returned by this query are 
--    actually collected by a different SQL Agent job that runs every 15 seconds on 
--    the managed instance. 
----------------------------------------------------------------------------
IF (OBJECT_ID(N'[snapshots].[sysutility_ucp_dac_collected_execution_statistics_internal]', 'U') IS NULL)
BEGIN
   RAISERROR('Creating [snapshots].[sysutility_ucp_dac_collected_execution_statistics_internal] table', 0, 1)  WITH NOWAIT;
   CREATE TABLE [snapshots].[sysutility_ucp_dac_collected_execution_statistics_internal]
   (
      [physical_server_name] SYSNAME,
      [server_instance_name] SYSNAME,
      [dac_db] SYSNAME,
      [dac_deploy_date] DATETIME,
      [dac_description] NVARCHAR(4000) NULL,
      [dac_name] SYSNAME,
      [interval_start_time] DATETIMEOFFSET NULL,-- The first every-15-second DAC CPU collection time included in this row's time interval
      [interval_end_time] DATETIMEOFFSET NULL,  -- The last every-15-second DAC CPU collection time included in this row's time interval (typically ~15 min after interval_start_time)
      [interval_cpu_time_ms] BIGINT NULL,       -- The total amount of CPU time consumed by this DAC in the time interval
      [interval_cpu_pct] REAL NULL,             -- The average %CPU utilization for this DAC in the time interval (% is of all available CPU cycles, not just a single CPU)
      [lifetime_cpu_time_ms] BIGINT NULL,       -- The cumulative total CPU time consumed by this DAC since we started monitoring it
      [batch_time] datetimeoffset(7),           -- Start time for one execution of the Utility data collection job
      [collection_time] [datetimeoffset](7),    -- The execution time of the DC query that gathered this data from the managed instance
      [snapshot_id] [int], 
      
      -- This index is used by the caching job to copy the latest consistent batch from live to cache table
      CONSTRAINT PK_sysutility_ucp_dac_collected_execution_statistics_internal PRIMARY KEY CLUSTERED 
         (server_instance_name, batch_time, dac_name) 
   );      

    -- This index is used by the DC purge job to seek against snapshot_id
   CREATE NONCLUSTERED INDEX NCI_sysutility_ucp_dac_collected_execution_statistics_internal 
        ON [snapshots].[sysutility_ucp_dac_collected_execution_statistics_internal](snapshot_id)    

   -- Create a foreign key referencing snapshots_internal so that rows in this table will be deleted by the DC purge job
   ALTER TABLE [snapshots].[sysutility_ucp_dac_collected_execution_statistics_internal] WITH CHECK 
   ADD CONSTRAINT [fk_dac_collected_execution_statistics_internal_snapshots_internal] 
   FOREIGN KEY([snapshot_id])
   REFERENCES [core].[snapshots_internal] ([snapshot_id])
   ON DELETE CASCADE;
   
   EXEC sp_addextendedproperty 
      @name = 'MS_UtilityObjectType', @value = 'LIVE', 
      @level0type = 'SCHEMA', @level0name = 'snapshots', 
      @level1type = 'TABLE', @level1name = 'sysutility_ucp_dac_collected_execution_statistics_internal';
END;
GO

----------------------------------------------------------------------------------
-- View latest_dac_cpu_utilization
--    Gets the latest DAC-related information and CPU utilization for each DAC on a
--    managed instanced
----------------------------------------------------------------------------------
IF  EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_staging].[latest_dac_cpu_utilization]'))
BEGIN
   RAISERROR('Dropping [sysutility_ucp_staging].[latest_dac_cpu_utilization] view', 0, 1) WITH NOWAIT;
   DROP VIEW [sysutility_ucp_staging].[latest_dac_cpu_utilization]
END
GO

RAISERROR('Creating [sysutility_ucp_staging].[latest_dac_cpu_utilization] view', 0, 1) WITH NOWAIT;
GO
CREATE VIEW [sysutility_ucp_staging].[latest_dac_cpu_utilization]
AS
  SELECT physical_server_name, ds.server_instance_name, dac_db, dac_deploy_date, dac_description, dac_name, 
         lifetime_cpu_time_ms, interval_cpu_pct AS latest_cpu_pct, interval_cpu_time_ms AS latest_interval_cpu_time_ms, 
         interval_start_time AS latest_interval_start_time, interval_end_time AS latest_interval_end_time, 
         ds.batch_time,
         N'Utility[@Name=''' + CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')) + N''']/DeployedDac[@Name=''' + ds.dac_name + N''' and @ServerInstanceName=''' + ds.server_instance_name + N''']' AS urn,
         N'SQLSERVER:\Utility\'+CASE WHEN 0 = CHARINDEX(N'\', CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')), 1) THEN CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')) + N'\DEFAULT' ELSE CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')) END 
        +N'\DeployedDacs\'+msdb.dbo.fn_encode_sqlname_for_powershell(ds.dac_name+'.'+ds.server_instance_name) AS powershell_path
  FROM [snapshots].[sysutility_ucp_dac_collected_execution_statistics_internal] ds
        INNER JOIN [sysutility_ucp_staging].[consistent_batch_manifests_internal] cb
        ON ds.server_instance_name = cb.server_instance_name AND ds.batch_time = cb.batch_time
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'STAGING', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', 
   @level1type = 'VIEW', @level1name = 'latest_dac_cpu_utilization';
GO


----------------------------------------------------------------------------
--- MDW Table for storing SMO server collection set information.
----------------------------------------------------------------------------

IF NOT EXISTS (SELECT name FROM .[sys].[objects] WHERE object_id = OBJECT_ID(N'[snapshots].[sysutility_ucp_smo_properties_internal]'))
BEGIN
   RAISERROR('Creating [snapshots].[sysutility_ucp_smo_properties_internal] table', 0, 1) WITH NOWAIT;
   CREATE TABLE [snapshots].[sysutility_ucp_smo_properties_internal]
   (
      [physical_server_name]  SYSNAME,
      [server_instance_name]  SYSNAME,
      [object_type]           INT,
      [urn]                   NVARCHAR(4000),
      [property_name]         NVARCHAR(128),
      [property_value]        SQL_VARIANT,
      [batch_time]            DATETIMEOFFSET(7),  -- Start time for one execution of the Utility data collection job
      [collection_time]       DATETIMEOFFSET(7) NULL,
      [snapshot_id]           INT NULL,
      -- NOTE: compression is enabled on this table at runtime (during Create UCP) in sp_initialize_mdw_internal              
   )

    -- This index is used by the caching job to copy the latest consistent batch from live to cache table
   CREATE CLUSTERED INDEX CI_sysutility_ucp_smo_properties_internal 
        ON [snapshots].[sysutility_ucp_smo_properties_internal](server_instance_name, batch_time); 

    -- This index is used by the DC purge job to seek against snapshot_id
    CREATE NONCLUSTERED INDEX NCI_sysutility_ucp_smo_properties_internal 
        ON [snapshots].[sysutility_ucp_smo_properties_internal](snapshot_id)       

   -- Create a foreign key referencing snapshots_internal so that rows in this table will be deleted by the DC purge job
   ALTER TABLE [snapshots].[sysutility_ucp_smo_properties_internal] WITH CHECK 
   ADD CONSTRAINT [FK_sysutility_ucp_smo_properties_internal_snapshots_internal] 
   FOREIGN KEY([snapshot_id])
   REFERENCES [core].[snapshots_internal] ([snapshot_id])
   ON DELETE CASCADE;

    EXEC sp_addextendedproperty 
        @name = 'MS_UtilityObjectType', @value = 'LIVE', 
        @level0type = 'SCHEMA', @level0name = 'snapshots', 
        @level1type = 'TABLE', @level1name = 'sysutility_ucp_smo_properties_internal'; 
END
GO

------------------------------------------------------------------------------
--  SQL Server View to read latest snapshot data for SMO properties
------------------------------------------------------------------------------
IF  EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_staging].[latest_smo_properties]'))
BEGIN
   RAISERROR('Dropping [sysutility_ucp_staging].[latest_smo_properties] view', 0, 1) WITH NOWAIT;
   DROP VIEW [sysutility_ucp_staging].[latest_smo_properties]
END
GO

RAISERROR('Creating [sysutility_ucp_staging].[latest_smo_properties] view', 0, 1) WITH NOWAIT;
GO
CREATE VIEW [sysutility_ucp_staging].[latest_smo_properties]
AS
   SELECT s.physical_server_name, s.server_instance_name, s.urn, s.object_type, 
          s.property_name,s.property_value, s.snapshot_id, s.batch_time
   FROM [snapshots].[sysutility_ucp_smo_properties_internal] AS s
         INNER JOIN [sysutility_ucp_staging].[consistent_batch_manifests_internal] cb
         ON s.server_instance_name = cb.server_instance_name AND s.batch_time = cb.batch_time     
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'STAGING', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', 
   @level1type = 'VIEW', @level1name = 'latest_smo_properties';
GO


-----------------------------------------------------------------------
--  Created [snapshots].[sysutility_ucp_cpu_memory_configurations_internal] and [snapshots].[sysutility_ucp_cpu_affinity_internal] tables if they are not existing.
-----------------------------------------------------------------------
IF NOT EXISTS (SELECT name FROM sys.objects WHERE object_id = OBJECT_ID(N'[snapshots].[sysutility_ucp_cpu_memory_configurations_internal]'))
BEGIN
    RAISERROR('Creating table [snapshots].[sysutility_ucp_cpu_memory_configurations_internal]', 0, 1)  WITH NOWAIT;   
    CREATE TABLE [snapshots].[sysutility_ucp_cpu_memory_configurations_internal](
      [server_instance_name]     SYSNAME NOT NULL,
      [is_clustered_server]      SMALLINT NULL,
      [virtual_server_name]      SYSNAME,
      [physical_server_name]     SYSNAME NOT NULL,
      [num_processors]           INT NULL,
      [server_processor_usage]   REAL,
      [instance_processor_usage] REAL,
      [cpu_name]                 NVARCHAR(128) NULL,
      [cpu_caption]              NVARCHAR(128) NULL,
      [cpu_family]               NVARCHAR(128) NULL,
      [cpu_architecture]         NVARCHAR(64) NULL,
      [cpu_max_clock_speed]      DECIMAL(10,0) NULL,    -- this is in MHz (Mega Hertz)
      [cpu_clock_speed]          DECIMAL(10,0) NULL,
      [l2_cache_size]            DECIMAL(10,0) NULL,
      [l3_cache_size]            DECIMAL(10,0) NULL,
      [batch_time]               [datetimeoffset](7),  -- Start time for one execution of the Utility data collection job
      [collection_time]          [datetimeoffset](7) NULL,
      [snapshot_id]              [int] NULL
      
    -- This index is used by the caching job to copy the latest consistent batch from live to cache table
    CONSTRAINT PK_sysutility_cpu_memory_related_info_internal_clustered PRIMARY KEY CLUSTERED 
        ([server_instance_name], [batch_time], [physical_server_name])

    ) ON [PRIMARY];

    -- This index is used by the DC purge job to seek against snapshot_id
    CREATE NONCLUSTERED INDEX NCI_sysutility_ucp_cpu_memory_configurations_internal 
        ON [snapshots].[sysutility_ucp_cpu_memory_configurations_internal](snapshot_id)       

    ALTER TABLE [snapshots].[sysutility_ucp_cpu_memory_configurations_internal]  WITH CHECK ADD  CONSTRAINT [FK_sysutility_cpu_memory_related_info_snapshots_internal_snapshots_internal] FOREIGN KEY([snapshot_id])
    REFERENCES [core].[snapshots_internal] ([snapshot_id])
    ON DELETE CASCADE;

    ALTER TABLE [snapshots].[sysutility_ucp_cpu_memory_configurations_internal] CHECK CONSTRAINT [FK_sysutility_cpu_memory_related_info_snapshots_internal_snapshots_internal];

    ALTER TABLE [snapshots].[sysutility_ucp_cpu_memory_configurations_internal]  WITH CHECK ADD  CONSTRAINT [CHK_check_operator_9DAA9ACC-F1E1-44F8-8B74-D081734E5F39] CHECK  (([core].[fn_check_operator]([snapshot_id])=(1)));

    ALTER TABLE [snapshots].[sysutility_ucp_cpu_memory_configurations_internal] CHECK CONSTRAINT [CHK_check_operator_9DAA9ACC-F1E1-44F8-8B74-D081734E5F39];
    
    EXEC sp_addextendedproperty 
        @name = 'MS_UtilityObjectType', @value = 'LIVE', 
        @level0type = 'SCHEMA', @level0name = 'snapshots', 
        @level1type = 'TABLE', @level1name = 'sysutility_ucp_cpu_memory_configurations_internal';
END
GO

-----------------------------------------------------------------------
--  Gets the latest CPU/memory configurations for each computer - along 
--  with the current CPU utilization for the computer.
-- 
-- NOTE: If there are multiple SQL instances (MIs) on a computer, each of 
--    them uploads its snapshot of information about the computer. We 
--    only want one entry for the computer - so we pick the entry that 
--    was uploaded last
-----------------------------------------------------------------------
IF  EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_staging].[latest_computer_cpu_memory_configuration]'))
BEGIN
   RAISERROR('Dropping [sysutility_ucp_staging].[latest_computer_cpu_memory_configuration] view', 0, 1) WITH NOWAIT;
   DROP VIEW [sysutility_ucp_staging].[latest_computer_cpu_memory_configuration]
END
GO

RAISERROR('Creating view [sysutility_ucp_staging].[latest_computer_cpu_memory_configuration]', 0, 1)  WITH NOWAIT;   
GO
CREATE VIEW [sysutility_ucp_staging].[latest_computer_cpu_memory_configuration]
AS
   SELECT * 
   FROM
      (
        SELECT  
           ROW_NUMBER() OVER (PARTITION BY t.physical_server_name ORDER BY t.batch_time DESC) AS Rank,
           [virtual_server_name], [is_clustered_server],[physical_server_name], 
           [num_processors], [cpu_name], [cpu_caption], [cpu_family], [cpu_architecture], [cpu_max_clock_speed], [cpu_clock_speed], 
           [l2_cache_size], [l3_cache_size], 
           [server_processor_usage],
           t.[batch_time],
           N'Utility[@Name=''' + CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')) + N''']/Computer[@Name=''' + t.physical_server_name + N''']' AS urn,
           N'SQLSERVER:\Utility\'+CASE WHEN 0 = CHARINDEX(N'\', CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')), 1) THEN CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')) + N'\DEFAULT' ELSE @@SERVERNAME END 
          +N'\Computers\'+msdb.dbo.fn_encode_sqlname_for_powershell(t.physical_server_name) AS powershell_path
        FROM [snapshots].[sysutility_ucp_cpu_memory_configurations_internal] AS t
             INNER JOIN [sysutility_ucp_staging].[consistent_batch_manifests_internal] cb
             ON t.server_instance_name = cb.server_instance_name AND t.batch_time = cb.batch_time
      ) AS S
   WHERE S.Rank = 1
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'STAGING', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', 
   @level1type = 'VIEW', @level1name = 'latest_computer_cpu_memory_configuration';
GO


-----------------------------------------------------------------------

-----------------------------------------------------------------------
IF  EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_staging].[latest_instance_cpu_utilization]'))
BEGIN
   RAISERROR('Dropping [sysutility_ucp_staging].[latest_instance_cpu_utilization] view', 0, 1) WITH NOWAIT;
   DROP VIEW [sysutility_ucp_staging].[latest_instance_cpu_utilization]
END
GO
RAISERROR('Creating view [sysutility_ucp_staging].[latest_instance_cpu_utilization]', 0, 1)  WITH NOWAIT;   
GO
CREATE VIEW [sysutility_ucp_staging].[latest_instance_cpu_utilization]
AS
   SELECT t.[server_instance_name],  
          [instance_processor_usage],
          t.[batch_time]
   FROM [snapshots].[sysutility_ucp_cpu_memory_configurations_internal] AS t
        INNER JOIN [sysutility_ucp_staging].[consistent_batch_manifests_internal] cb
        ON t.server_instance_name = cb.server_instance_name AND t.batch_time = cb.batch_time
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'STAGING', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', 
   @level1type = 'VIEW', @level1name = 'latest_instance_cpu_utilization';
GO


-----------------------------------------------------------------------
--  Created [snapshots].[sysutility_ucp_volumes_internal] tables to collect volume-related
--  information from the MIs
-----------------------------------------------------------------------

IF NOT EXISTS (SELECT name FROM sys.objects WHERE object_id = OBJECT_ID(N'[snapshots].[sysutility_ucp_volumes_internal]') )
BEGIN
    RAISERROR('Creating table [snapshots].[sysutility_ucp_volumes_internal]', 0, 1)  WITH NOWAIT;
    CREATE TABLE [snapshots].[sysutility_ucp_volumes_internal](
    [server_instance_name]  SYSNAME,
    [virtual_server_name]   SYSNAME,
    [physical_server_name]  SYSNAME,
    
    [volume_device_id]      SYSNAME NOT NULL,
    [volume_name]           [nvarchar](260) NOT NULL,

    [total_space_available] [real] NULL, -- in MB
    [free_space]            [real] NULL, -- in MB
    [batch_time]            datetimeoffset(7),  -- Start time for one execution of the Utility data collection job
    [collection_time]       datetimeoffset(7) NULL,
    [snapshot_id]           [int] NULL
    
    -- This index is used by the caching job to copy the latest consistent batch from live to cache table
    CONSTRAINT PK_sysutility_volumes_info_internal PRIMARY KEY CLUSTERED
        (server_instance_name, batch_time, volume_device_id)
    
    ) ON [PRIMARY]
    
    -- This index is used by the DC purge job to seek against snapshot_id
    CREATE NONCLUSTERED INDEX NCI_sysutility_ucp_volumes_internal
        ON [snapshots].[sysutility_ucp_volumes_internal](snapshot_id) 
            
    ALTER TABLE [snapshots].[sysutility_ucp_volumes_internal]  WITH CHECK ADD  CONSTRAINT [FK_volumes_info_snapshots_internal] FOREIGN KEY([snapshot_id])
    REFERENCES [core].[snapshots_internal] ([snapshot_id])
    ON DELETE CASCADE
    ALTER TABLE [snapshots].[sysutility_ucp_volumes_internal] CHECK CONSTRAINT [FK_volumes_info_snapshots_internal]

    ALTER TABLE [snapshots].[sysutility_ucp_volumes_internal]  WITH CHECK ADD  CONSTRAINT [CHK_check_operator_D79F8519-D243-4176-8291-6F3BA8EF776D] CHECK  (([core].[fn_check_operator]([snapshot_id])=(1)))
    ALTER TABLE [snapshots].[sysutility_ucp_volumes_internal] CHECK CONSTRAINT [CHK_check_operator_D79F8519-D243-4176-8291-6F3BA8EF776D] 
    
    EXEC sp_addextendedproperty 
        @name = 'MS_UtilityObjectType', @value = 'LIVE', 
        @level0type = 'SCHEMA', @level0name = 'snapshots', 
        @level1type = 'TABLE', @level1name = 'sysutility_ucp_volumes_internal';   
END
GO


-----------------------------------------------------------------------
--  View to get latest volumes information. 
--  NOTE: If you change the output of this view in any way, be sure to also update the 
--  corresponding "stub" object in instmsdb.sql. 
--  
--  NOTE: When you have multiple SQL instances on a machine, each of them is uploading
--     volume information (as it sees it). However, we only want one entry for each
--     volume on the computer. What we do here is to simply pick the entry from the 
--     instance that uploaded last - hence the partition-by (physical_server_name,volume_device_id)
--     The "latest_computers" view exhibits very similar behavior.
-----------------------------------------------------------------------
IF  EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_staging].[latest_volumes]'))
BEGIN
   RAISERROR('Dropping view [sysutility_ucp_staging].[latest_volumes]', 0, 1)  WITH NOWAIT;
   DROP VIEW [sysutility_ucp_staging].[latest_volumes]
END
GO

RAISERROR('Creating view [sysutility_ucp_staging].[latest_volumes]', 0, 1)  WITH NOWAIT;
GO
CREATE VIEW [sysutility_ucp_staging].[latest_volumes] AS
   SELECT [virtual_server_name],
          [physical_server_name],
          [volume_device_id],
          [volume_name],
          [total_space_available],  -- in MB
          [free_space], -- in MB
          N'SQLSERVER:\Utility\'+ CASE WHEN 0 = CHARINDEX(N'\', CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')), 1) 
                                        THEN CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')) + N'\DEFAULT' 
                                        ELSE CONVERT(SYSNAME, SERVERPROPERTY(N'ServerName')) 
                                    END 
          +N'\Computers\'+msdb.dbo.fn_encode_sqlname_for_powershell(physical_server_name) 
          +N'\Volumes\'+msdb.dbo.fn_encode_sqlname_for_powershell(volume_name) AS powershell_path,
          [batch_time],
          [snapshot_id]
      FROM
      (
      SELECT 
         [virtual_server_name],
         [physical_server_name],
         [volume_device_id],
         [volume_name],
         [total_space_available],
         [free_space],
         V.[batch_time],
         [snapshot_id], 
         ROW_NUMBER() OVER (PARTITION BY physical_server_name,volume_device_id ORDER BY V.batch_time DESC) rk
      FROM [snapshots].[sysutility_ucp_volumes_internal] AS V
           INNER JOIN [sysutility_ucp_staging].[consistent_batch_manifests_internal] cb
           ON V.server_instance_name = cb.server_instance_name AND V.batch_time = cb.batch_time
      ) AS T
      WHERE T.rk = 1
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'STAGING', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', 
   @level1type = 'VIEW', @level1name = 'latest_volumes';
GO


--*********************************************************************
-- Create procedure: sp_get_consistent_batches
-- This procedure gets the consistent batch information for the enrolled MI's
-- and stores it into the consistent_batch_manifests_internal table.
-- This SP is executed by the caching job to identify the consistent batches 
-- whose data needs to be copied from the live to cache tables for every run.
--
-- A batch is marked consistent if the number of records in each table uploaded 
-- match to the respective row count in the batch manifest. The evaluation of 
-- last batch (a) and second-last batch (b) for a given MI is based on following rules: 
-- . Both a and b exists and are consistent: 
--      Most recent batch (a) is returned    
-- . Either a or b exists
--      Returns a or b only if it is consistent. Possiblity of delayed upload from MI side.
-- . Both a and b exists and are inconsistent: 
--      No batch info is returned for that MI. Possiblity of failure in collection/upload on MI side.    
-- . Both a and b does not exist
--      No batch info is not returned for that MI. Aged-out data is currently not considered, VSTS #319498
--*********************************************************************
IF OBJECT_ID ('[sysutility_ucp_staging].[sp_get_consistent_batches]') IS NOT NULL 
BEGIN
    RAISERROR ('Dropping procedure [sysutility_ucp_staging].[sp_get_consistent_batches]', 0, 1) WITH NOWAIT;
    DROP PROCEDURE [sysutility_ucp_staging].[sp_get_consistent_batches]
END;
GO

RAISERROR ('Creating procedure [sysutility_ucp_staging].[sp_get_consistent_batches]', 0, 1) WITH NOWAIT;
GO

CREATE PROCEDURE [sysutility_ucp_staging].[sp_get_consistent_batches] 
AS
BEGIN
    SET NOCOUNT ON;   

    -- Note: As we are not currently caching the aged-out data, this SP
    -- clears the existing records and inserts the new data. However, as a fix for 
    -- VSTS #319498 (display aged-out data) this behavior needs to be changed 
    -- to UPSERT for any existing or new data and delete the entries whose 
    -- data is purged (> 7 days). 

    -- Get the manifest information for the latest uploaded batches.  The "manifest" info includes 
    -- the expected number of rows that should have been uploaded into each live table for the 
    -- batch.  This query captures the manifest for most recent unprocessed batch (T) and the 
    -- immediately prior (T-1) unprocessed batch from each managed instance since the last execution 
    -- of the sp_copy_live_table_data_into_cache_tables stored proc. 
    -- 
    -- This rowset is staged in a temp table b/c the query optimizer cannot accurately predict the 
    -- number of rows that qualify for "WHERE bm.snapshot_id > sp.latest_consistent_snapshot_id". 
    -- 
    -- Note: This view may fetch the last 2 batch manifest rows for a given MI. The reason for 
    -- considering two batches is to use the latest one that is consistent. If the latest one is 
    -- missing (delayed upload) or inconsistent (failed or still-in-progress upload), we will use 
    -- the second-to-last batch, assuming that it is consistent. This makes the caching job 
    -- resilient to occasional delays in the MI upload job. 
    SELECT server_instance_name
        , batch_time
        , CONVERT(INT, dac_packages_row_count) AS dac_packages_row_count
        , CONVERT(INT, cpu_memory_configurations_row_count) AS cpu_memory_configurations_row_count
        , CONVERT(INT, volumes_row_count) AS volumes_row_count
        , CONVERT(INT, smo_properties_row_count) AS smo_properties_row_count        
    INTO #batch_manifests_latest
    FROM  (SELECT bm.server_instance_name, bm.batch_time, bm.parameter_name, bm.parameter_value 
           FROM snapshots.sysutility_ucp_batch_manifests_internal bm
              , msdb.dbo.sysutility_ucp_snapshot_partitions_internal AS sp
           WHERE bm.snapshot_id > sp.latest_consistent_snapshot_id 
             -- The [time_id] = 1 partition gives us the max snapshot_id the last time that the 
             -- sp_copy_live_table_data_into_cache_tables proc was executed (previous high water 
             -- mark).  We will consider for processing any snapshots that have been uploaded 
             -- since then. 
             AND sp.time_id = 1) AS lbm
    PIVOT (MAX(parameter_value) FOR parameter_name IN (dac_packages_row_count
                                                     , cpu_memory_configurations_row_count
                                                     , volumes_row_count
                                                     , smo_properties_row_count)) pvt;
    
    -- Truncate the table
    TRUNCATE TABLE [sysutility_ucp_staging].[consistent_batch_manifests_internal];
    
    -- Get the set of latest batches that are consistent with respect to the data uploaded to 
    -- each live table.  A check is made to verify that the number of rows uploaded matches the 
    -- expected row count in that batch's manifest. 
    -- 
    -- These rowsets are staged in temp tables b/c the query optimizer cannot accurately predict 
    -- the number of rows that qualify for "HAVING COUNT(*) = bm.cpu_memory_configurations_row_count". 

    SELECT bm.server_instance_name, bm.batch_time
    INTO #dac_statistics_consistent_batches
    FROM #batch_manifests_latest bm
    -- Note: No records in DAC table doesn't mean issue with upload -- a MI with no DACs is 
    -- perfectly valid; use an outer join so that we tolerate the no-DACs case.  
    LEFT JOIN [snapshots].[sysutility_ucp_dac_collected_execution_statistics_internal] ds 
        ON bm.server_instance_name = ds.server_instance_name AND bm.batch_time = ds.batch_time    
    GROUP BY bm.server_instance_name, bm.batch_time, bm.dac_packages_row_count, ds.batch_time    
    HAVING SUM(CASE WHEN ds.batch_time IS NULL THEN 0 ELSE 1 END) = bm.dac_packages_row_count

    SELECT bm.server_instance_name, bm.batch_time 
    INTO #cpu_memory_configurations_consistent_batches
    FROM #batch_manifests_latest bm
    INNER JOIN [snapshots].[sysutility_ucp_cpu_memory_configurations_internal] cm 
        ON bm.server_instance_name = cm.server_instance_name AND bm.batch_time = cm.batch_time   
    GROUP BY bm.server_instance_name, bm.batch_time, bm.cpu_memory_configurations_row_count
    HAVING COUNT(*) = bm.cpu_memory_configurations_row_count

    SELECT bm.server_instance_name, bm.batch_time 
    INTO #volumes_consistent_batches
    FROM #batch_manifests_latest bm
    INNER JOIN [snapshots].[sysutility_ucp_volumes_internal] vo 
        ON bm.server_instance_name = vo.server_instance_name AND bm.batch_time = vo.batch_time        
    GROUP BY bm.server_instance_name, bm.batch_time, bm.volumes_row_count
    HAVING COUNT(*) = bm.volumes_row_count

    SELECT bm.server_instance_name, bm.batch_time
    INTO #smo_properties_consistent_batches
    FROM #batch_manifests_latest bm
    INNER JOIN [snapshots].[sysutility_ucp_smo_properties_internal] sp 
        ON bm.server_instance_name = sp.server_instance_name AND bm.batch_time = sp.batch_time   
    GROUP BY bm.server_instance_name, bm.batch_time, bm.smo_properties_row_count
    HAVING COUNT(*) = bm.smo_properties_row_count


    -- Insert the new consistent batch information.  A consistent batch is a batch where all of 
    -- the live tables have the expected number of rows. 
    INSERT INTO [sysutility_ucp_staging].[consistent_batch_manifests_internal]
    SELECT bm.server_instance_name
        , bm.batch_time 
    FROM
    (
        -- Fetch the latest (order by DESC) consistent batches uploaded by the MI's
        SELECT ROW_NUMBER() OVER (PARTITION BY bm.server_instance_name ORDER BY bm.batch_time DESC) AS [rank]
            , bm.server_instance_name
            , bm.batch_time
        FROM #batch_manifests_latest AS bm
        INNER JOIN #dac_statistics_consistent_batches AS ds  
            ON bm.server_instance_name = ds.server_instance_name AND bm.batch_time = ds.batch_time    
        INNER JOIN #cpu_memory_configurations_consistent_batches AS cm  
            ON bm.server_instance_name = cm.server_instance_name AND bm.batch_time = cm.batch_time   
        INNER JOIN #volumes_consistent_batches AS vo 
            ON bm.server_instance_name = vo.server_instance_name AND bm.batch_time = vo.batch_time   
        INNER JOIN #smo_properties_consistent_batches AS sp 
            ON bm.server_instance_name = sp.server_instance_name AND bm.batch_time = sp.batch_time
    ) bm        
    WHERE bm.[rank] = 1;

END
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'STAGING', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', 
   @level1type = 'PROCEDURE', @level1name = 'sp_get_consistent_batches';
GO

-- Note: There is another stored procedure in the sysutility_ucp_staging schema 
-- (sp_copy_live_table_data_into_cache_tables) that is created later in this script, 
-- because it depends on the schema of some objects in the sysutility_ucp_core schema. 



/***********************************************************************/
/* Utility SCHEMA: (sysutility_ucp_core)                               */
/* Dimension Tables and Views                                          */
/*                                                                     */
/*   We currently handle the following dimensions (and hence dimension */
/* tables).                                                            */
/*  Computers  (Table: computers_internal; view: latest_computers)     */
/*  Volumes    (Table: volumes_internal; view: latest_volumes)         */
/*  Instances  (Table: smo_servers_internal; view: latest_smo_servers) */
/*  Databases  (Table: databases_internal; view: latest_databases)     */
/*  FileGroups (Table: filegroups_internal; view: latest_filegroups)   */
/*  DataFiles  (Table: datafiles_internal; view: latest_datafiles)     */
/*  LogFiles   (Table: logfiles_internal; view: latest_logfiles)       */   
/*  Dacs       (Table: dacs_internal; view: latest_dacs)               */
/*                                                                     */
/* Every dimension table is clustered on its primary key. Each table   */
/* has processing_time as the prefix of its primary. This allows for   */
/* efficient queue-like behavior - inserts at the end, purges at the   */
/* beginning, and queries typically at the end.                        */
/*                                                                     */
/* Each of these dimension tables has a corresponding view that picks  */
/* the latest consistent information for that dimension. The latest    */
/* consistent information is determined by the value of the            */
/* "latest_processing_time" column in sysutility_ucp_processing_state   */
/* table in MSDB                                                       */
/***********************************************************************/

------------------------------------------------------------------------------------------------------------
-- Table dacs_internal
--    This is technically a dimension table for DACs. 
--      (For various logisical reasons, this also contains CPU utilization information for DACs.)
--    The key of this table is (processing_time, server_instance_name, dac_name). 
--       Machine_name is a regular column, but does not need to be part of the key (server_instance_name already
--       includes the appropriate information)
------------------------------------------------------------------------------------------------------------
IF (OBJECT_ID(N'[sysutility_ucp_core].[dacs_internal]', 'U') IS NULL)
BEGIN
   RAISERROR('Creating [sysutility_ucp_core].[dacs_internal] table', 0, 1)  WITH NOWAIT;
   CREATE TABLE [sysutility_ucp_core].[dacs_internal]
   (
      -- todo (VSTS #345036): This column will be removed
      [dac_id] INT IDENTITY,
      
      [server_instance_name] SYSNAME,  -- the server-qualified instance name
      [dac_name] SYSNAME,
      [urn] NVARCHAR(4000),
      [powershell_path] NVARCHAR(4000),

      [physical_server_name] SYSNAME,
      [dac_deploy_date] DATETIME,
      [dac_description] NVARCHAR(4000) NULL,

      -- todo (VSTS #345040)
      -- This is technically a "measure" column and should not be part of this dimension table   
      [dac_percent_total_cpu_utilization] REAL,
      
      [processing_time] DATETIMEOFFSET(7), 
      [batch_time] DATETIMEOFFSET(7),

      CONSTRAINT [PK_dacs_internal] 
        PRIMARY KEY CLUSTERED (processing_time, server_instance_name, dac_name)
   )

   EXEC sp_addextendedproperty 
      @name = 'MS_UtilityObjectType', @value = 'DIMENSION', 
      @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
      @level1type = 'TABLE', @level1name = 'dacs_internal';
END
GO

------------------------------------------------------------------------------
--  SQL Server View to read latest data for all DACs from cache table
--  NOTE: If you change the output of this view in any way, be sure to also update the 
--  corresponding "stub" object in instmsdb.sql. 
------------------------------------------------------------------------------
IF  EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_dacs]'))
BEGIN
   RAISERROR('Dropping [sysutility_ucp_core].[latest_dacs] view', 0, 1)  WITH NOWAIT; 
   DROP VIEW [sysutility_ucp_core].[latest_dacs]
END
GO
RAISERROR('Creating [sysutility_ucp_core].[latest_dacs] view', 0, 1)  WITH NOWAIT;
GO
CREATE VIEW [sysutility_ucp_core].[latest_dacs]
AS
   SELECT 
      dac_id, 
      server_instance_name, 
      dac_name, 
      physical_server_name, 
      dac_deploy_date, 
      dac_description, 
      urn,
      powershell_path,
      processing_time,
      batch_time,
      dac_percent_total_cpu_utilization
      FROM [sysutility_ucp_core].[dacs_internal] S
      WHERE S.processing_time =  (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'CORE', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
   @level1type = 'VIEW', @level1name = 'latest_dacs';
GO


-----------------------------------------------------------------------
--  
-- Dimension table: computers_internal. 
--    (For logistical reasons, also includes percent_total_cpu_consumption)
--   Key: (processing_time, physical_server_name)
--
-- NOTE: We use physical_server_name as part of the key rather than the 
--    (logical) virtual_server_name. This is to allow for scenarios with clustering
--    where we have two or more servers clustered together but with potentially
--    different configurations.
--
-----------------------------------------------------------------------
IF NOT EXISTS (SELECT name FROM sys.objects WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[computers_internal]'))
BEGIN
   RAISERROR('Creating table [sysutility_ucp_core].[computers_internal]', 0, 1)  WITH NOWAIT;
   CREATE TABLE [sysutility_ucp_core].[computers_internal]
   (
      -- todo (VSTS #345036): This column will be removed
      [id] INT IDENTITY, 
      
      virtual_server_name SYSNAME,
      physical_server_name SYSNAME,    -- differs from virtual_server_name for clustered servers
      is_clustered_server INT,
      
      num_processors INT,
      cpu_name NVARCHAR(128),
      cpu_caption NVARCHAR(128),
      cpu_family NVARCHAR(128),
      cpu_architecture NVARCHAR(64),
      cpu_max_clock_speed DECIMAL(10),
      cpu_clock_speed DECIMAL(10),
      l2_cache_size DECIMAL(10),
      l3_cache_size DECIMAL(10),
     
      -- todo (VSTS #345040)
      -- This is technically a "measure" column and should not be part of this dimension table   
      percent_total_cpu_utilization REAL,
      
      urn NVARCHAR(4000),
      powershell_path NVARCHAR(4000),
      
      processing_time DATETIMEOFFSET(7),
      batch_time DATETIMEOFFSET(7),
      
      CONSTRAINT [PK_computers_internal] 
        PRIMARY KEY CLUSTERED (processing_time, physical_server_name)
   )

   EXEC sp_addextendedproperty 
      @name = 'MS_UtilityObjectType', @value = 'DIMENSION', 
      @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
      @level1type = 'TABLE', @level1name = 'computers_internal';
END
GO

-----------------------------------------------------------------------
-- View to select latest data from [computers_internal] dimension table.
--  NOTE: If you change the shape of this view in any way, be sure to also 
--  update the corresponding "stub" object in instmsdb.sql. 
-----------------------------------------------------------------------
IF  EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_computers]'))
BEGIN
   RAISERROR('Dropping [sysutility_ucp_core].[latest_computers] view', 0, 1)  WITH NOWAIT; 
   DROP VIEW [sysutility_ucp_core].[latest_computers]
END
GO
RAISERROR('Creating view [sysutility_ucp_core].[latest_computers]', 0, 1)  WITH NOWAIT;
GO
CREATE VIEW [sysutility_ucp_core].[latest_computers] AS 
  SELECT [id], 
         virtual_server_name, 
         physical_server_name, 
         is_clustered_server,
         num_processors, 
         cpu_name, 
         cpu_caption, 
         cpu_family, 
         cpu_architecture, 
         cpu_max_clock_speed, 
         cpu_clock_speed, 
         l2_cache_size, 
         l3_cache_size, 
         urn,
         powershell_path,
         processing_time,
         batch_time,
         percent_total_cpu_utilization
  FROM [sysutility_ucp_core].[computers_internal]
  WHERE processing_time = (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal]);
GO 

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'CORE', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
   @level1type = 'VIEW', @level1name = 'latest_computers';
GO


-----------------------------------------------------------------------
-- Dimension table: volumes_internal
--   - PK (processing_time, physical_server_name, volume_name)
--   - also includes physical_server_name 
--
--   - Includes "measure" columns (total_space_available, free_space)
-- Created cache table [sysutility_ucp_core].[volumes_internal] for storage view. 
-----------------------------------------------------------------------
IF NOT EXISTS (SELECT name FROM sys.objects WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[volumes_internal]') )
BEGIN
  RAISERROR('Creating table [sysutility_ucp_core].[volumes_internal]', 0, 1)  WITH NOWAIT;
  CREATE TABLE [sysutility_ucp_core].[volumes_internal]
  (
     -- todo (VSTS #345036): This column will be removed
     [ID] INT IDENTITY, 
   
     virtual_server_name SYSNAME,
     physical_server_name SYSNAME,
   
     volume_device_id SYSNAME,
     volume_name NVARCHAR(260),

     -- todo (VSTS #345040)
     -- These are technically "measure" columns and should not be part of this dimension table
     total_space_available real,  -- in MB
     free_space  real,  -- in MB

     processing_time DATETIMEOFFSET(7),
     batch_time DATETIMEOFFSET(7),
     powershell_path NVARCHAR(4000) NULL,
   
     CONSTRAINT pk_volumes_internal 
       PRIMARY KEY CLUSTERED(processing_time, physical_server_name, volume_device_id)
  )

   EXEC sp_addextendedproperty 
      @name = 'MS_UtilityObjectType', @value = 'DIMENSION', 
      @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
      @level1type = 'TABLE', @level1name = 'volumes_internal';
END
GO

-- If we are upgrading a SQL 2008 R2 MDW database, add the powershell_path column to volumes_internal for 
-- performance reasons (this column was not in the table in SQL Server 2008 R2).  See changelists 1844247 
-- and 1797832 for background. 
IF NOT EXISTS (SELECT * FROM sys.columns WHERE name = 'powershell_path' AND object_id = OBJECT_ID('sysutility_ucp_core.volumes_internal'))
BEGIN
    RAISERROR('Adding powershell_path column to [sysutility_ucp_core].[volumes_internal]', 0, 1) WITH NOWAIT;
    ALTER TABLE sysutility_ucp_core.volumes_internal ADD powershell_path NVARCHAR(4000) NULL;
END;
GO

------------------------------------------------------------------------------
--  SQL Server View to read latest information for volumes
--  NOTE: If you change the output of this view in any way, be sure to also update the 
--  corresponding "stub" object in instmsdb.sql. 
------------------------------------------------------------------------------
IF EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_volumes]'))
BEGIN
   RAISERROR('Dropping view [sysutility_ucp_core].[latest_volumes]', 0, 1)  WITH NOWAIT;
   DROP VIEW [sysutility_ucp_core].[latest_volumes]
END
GO
 
RAISERROR('Creating view [sysutility_ucp_core].[latest_volumes]', 0, 1)  WITH NOWAIT;
GO
CREATE VIEW [sysutility_ucp_core].[latest_volumes]
   AS
   SELECT [ID], 
          [virtual_server_name], 
          [physical_server_name], 
          [volume_device_id], 
          [volume_name], 
          [powershell_path],
          [processing_time],
          [batch_time], 
          [total_space_available], 
          (total_space_available - free_space) AS [total_space_utilized], 
          (CASE WHEN total_space_available = 0 THEN 0 ELSE (100 * (total_space_available - free_space))/total_space_available END) AS  [percent_total_space_utilization]
   FROM [sysutility_ucp_core].[volumes_internal]
   WHERE processing_time = (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'CORE', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
   @level1type = 'VIEW', @level1name = 'latest_volumes';
GO

         
-- =============================================
-- Dimension Table for SQL Server Instances
-- Table: smo_servers_internal
-- Key: processing_time, server_instance_name
-- =============================================
IF(OBJECT_ID(N'[sysutility_ucp_core].[smo_servers_internal]', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table [sysutility_ucp_core].[smo_servers_internal]', 0, 1) WITH NOWAIT;
    CREATE TABLE [sysutility_ucp_core].[smo_servers_internal]
    (
        [urn] NVARCHAR(320) 
        , [powershell_path]  NVARCHAR(4000)
        , [processing_time] DATETIMEOFFSET(7)
        , [batch_time] DATETIMEOFFSET(7)

        -- SMO properties
        , [AuditLevel] SMALLINT 
        , [BackupDirectory] NVARCHAR(260) 
        , [BrowserServiceAccount] NVARCHAR(128) 
        , [BrowserStartMode] SMALLINT 
        , [BuildClrVersionString] NVARCHAR(20) 
        , [BuildNumber] INT 
        , [Collation] NVARCHAR(128) 
        , [CollationID] INT 
        , [ComparisonStyle] INT 
        , [ComputerNamePhysicalNetBIOS] NVARCHAR(128) 
        , [DefaultFile] NVARCHAR(260) 
        , [DefaultLog] NVARCHAR(260) 
        , [Edition] NVARCHAR(64) 
        , [EngineEdition] SMALLINT 
        , [ErrorLogPath] NVARCHAR(260) 
        , [FilestreamShareName] NVARCHAR(260) 
        , [InstallDataDirectory] NVARCHAR(260) 
        , [InstallSharedDirectory] NVARCHAR(260) 
        , [InstanceName] NVARCHAR(128) 
        , [IsCaseSensitive] BIT 
        , [IsClustered] BIT 
        , [IsFullTextInstalled] BIT 
        , [IsSingleUser] BIT 
        , [Language] NVARCHAR(64) 
        , [MailProfile] NVARCHAR(128) 
        , [MasterDBLogPath] NVARCHAR(260) 
        , [MasterDBPath] NVARCHAR(260) 
        , [MaxPrecision] TINYINT 
        , [Name] NVARCHAR(128)        -- This is SERVERPROPERTY('ServerName')
        , [NamedPipesEnabled] BIT 
        , [NetName] NVARCHAR(128)     -- This is SERVERPROPERTY('MachineName')
        , [NumberOfLogFiles] INT 
        , [OSVersion] NVARCHAR(32) 
        , [PerfMonMode] SMALLINT 
        , [PhysicalMemory] INT 
        , [Platform] NVARCHAR(32) 
        , [Processors] SMALLINT 
        , [ProcessorUsage] INT 
        , [Product] NVARCHAR(48) 
        , [ProductLevel] NVARCHAR(32) 
        , [ResourceVersionString] NVARCHAR(32) 
        , [RootDirectory] NVARCHAR(260) 
        , [ServerType] SMALLINT 
        , [ServiceAccount] NVARCHAR(128) 
        , [ServiceInstanceId] NVARCHAR(64) 
        , [ServiceName] NVARCHAR(64) 
        , [ServiceStartMode] SMALLINT 
        , [SqlCharSet] SMALLINT 
        , [SqlCharSetName] NVARCHAR(32) 
        , [SqlDomainGroup] NVARCHAR(260) 
        , [SqlSortOrder] SMALLINT 
        , [SqlSortOrderName] NVARCHAR(64) 
        , [Status] SMALLINT 
        , [TapeLoadWaitTime] INT 
        , [TcpEnabled] BIT 
        , [VersionMajor] INT 
        , [VersionMinor] INT 
        , [VersionString] NVARCHAR(32)
       CONSTRAINT [PK_smo_servers_internal] 
         PRIMARY KEY CLUSTERED (processing_time, [Name])
         -- NOTE: compression is enabled on this table at runtime (during Create UCP) in sp_initialize_mdw_internal
    );
    
    EXEC sp_addextendedproperty 
        @name = 'MS_UtilityObjectType', @value = 'DIMENSION', 
        @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
        @level1type = 'TABLE', @level1name = 'smo_servers_internal';
END
GO

-- =====================================================================
-- Dimension Table for databases in a SQL Instance
-- Table: databases_internal
-- Key: processing_time, server_instance_name, name
-- =====================================================================

IF(OBJECT_ID(N'[sysutility_ucp_core].[databases_internal]', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table [sysutility_ucp_core].[databases_internal]', 0, 1) WITH NOWAIT;
    CREATE TABLE [sysutility_ucp_core].[databases_internal]
    (
        [urn] NVARCHAR(512)
        , [powershell_path]  NVARCHAR(MAX)
        , [processing_time] DATETIMEOFFSET(7)
        , [batch_time] DATETIMEOFFSET(7)
        , [server_instance_name]    SYSNAME
        , [parent_urn] NVARCHAR(320)
        , [Collation] NVARCHAR(128)
        , [CompatibilityLevel] SMALLINT
        , [CreateDate] DATETIME
        , [EncryptionEnabled] BIT
        , [Name] SYSNAME
        , [RecoveryModel] SMALLINT
        , [Trustworthy]    BIT       
        , [state] TINYINT NULL
       CONSTRAINT [PK_databases_internal] 
          PRIMARY KEY CLUSTERED (processing_time, server_instance_name, [Name]) 
          -- Note: compression is enabled on this table at runtime (during Create UCP) in sp_initialize_mdw_internal
    );
    
    EXEC sp_addextendedproperty 
        @name = 'MS_UtilityObjectType', @value = 'DIMENSION', 
        @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
        @level1type = 'TABLE', @level1name = 'databases_internal';
END
GO

-- If we are upgrading a SQL 2008 R2 MDW database, add the state column to databases_internal. This column allows 
-- better handling of unavailable databases (offline, emergency mode, recovering, etc).  See changelist 1856917
-- and VSTS 624995 for background. 
IF NOT EXISTS (
    SELECT * 
    FROM sys.columns 
    WHERE name = 'state' AND object_id = OBJECT_ID('sysutility_ucp_core.databases_internal'))
BEGIN
    RAISERROR('Adding state column to [sysutility_ucp_core].[databases_internal]', 0, 1) WITH NOWAIT;
    ALTER TABLE sysutility_ucp_core.databases_internal ADD state TINYINT NULL;
END;
GO

-- Constrain the values allowed in the state column. 
IF NOT EXISTS (
    SELECT * 
    FROM sys.check_constraints 
    WHERE name = 'chk_databases_internal_state' AND parent_object_id = OBJECT_ID('sysutility_ucp_core.databases_internal'))
BEGIN
    -- Current defined states are 0 (available) and 1 (not available -- emergency mode, offline, etc)
    ALTER TABLE sysutility_ucp_core.databases_internal
    ADD CONSTRAINT chk_databases_internal_state CHECK ([state] BETWEEN 0 AND 1);
END;
GO


-- =====================================================================
-- Dimension Table for filegroups in a database (in a SQL Instance)
-- Table: filegroups_internal
-- Key: processing_time, server_instance_name, database_name, name
-- =====================================================================
IF(OBJECT_ID(N'[sysutility_ucp_core].[filegroups_internal]', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table [sysutility_ucp_core].[filegroups_internal]', 0, 1) WITH NOWAIT;
    CREATE TABLE [sysutility_ucp_core].[filegroups_internal]
    (
        [urn] NVARCHAR(780)
        , [powershell_path]  NVARCHAR(MAX)
        , [processing_time] DATETIMEOFFSET(7)
        , [batch_time] DATETIMEOFFSET(7)
        , [server_instance_name]    SYSNAME
        , [database_name] SYSNAME
        , [parent_urn] NVARCHAR(512)
                
        -- SMO Properties
        , [Name] SYSNAME
        
        , CONSTRAINT PK_filegroups_internal
            PRIMARY KEY CLUSTERED(processing_time, server_instance_name, database_name, [Name])
        -- Note: compression is enabled on this table at runtime (during Create UCP) in sp_initialize_mdw_internal
    );    
    
    EXEC sp_addextendedproperty 
        @name = 'MS_UtilityObjectType', @value = 'DIMENSION', 
        @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
        @level1type = 'TABLE', @level1name = 'filegroups_internal';
END
GO

-- =====================================================================
-- Dimension Table for datafiles in a database (in a SQL Instance)
-- Table: datafiles_internal
-- Key: processing_time, server_instance_name, database_name, filegroup_name, name
--
-- VSTS #345570: The key length of the clustered index may be larger than 900 bytes.
-- 
-- ===================================================================== 
IF(OBJECT_ID(N'[sysutility_ucp_core].[datafiles_internal]', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table [sysutility_ucp_core].[datafiles_internal]', 0, 1) WITH NOWAIT;
    CREATE TABLE [sysutility_ucp_core].[datafiles_internal]
    (
        [urn] NVARCHAR(1500)
        , [powershell_path]  NVARCHAR(MAX)
        , [processing_time] DATETIMEOFFSET(7)
        , [batch_time] DATETIMEOFFSET(7)
        , [server_instance_name]    SYSNAME
        , [database_name] SYSNAME
        , [filegroup_name] SYSNAME
        , [parent_urn] NVARCHAR(780)
        , [physical_server_name] SYSNAME

        , [volume_name] NVARCHAR(260)
        , [volume_device_id] SYSNAME
        
        -- SMO Properties  
        , [Growth]  REAL
        , [GrowthType] SMALLINT
        , [MaxSize]  REAL
        , [Name] SYSNAME
        , [Size] REAL
        , [UsedSpace]  REAL
        , [FileName] NVARCHAR(260)
        , [VolumeFreeSpace] BIGINT
        
        , CONSTRAINT PK_datafiles_internal
            PRIMARY KEY CLUSTERED (processing_time, server_instance_name, database_name, [filegroup_name], [Name])
        -- Note: compression is enabled on this table at runtime (during Create UCP) in sp_initialize_mdw_internal
     );
     
    EXEC sp_addextendedproperty 
        @name = 'MS_UtilityObjectType', @value = 'DIMENSION', 
        @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
        @level1type = 'TABLE', @level1name = 'datafiles_internal';
END
GO

-- =====================================================================
-- Dimension Table for logfiles in a database (in a SQL Instance)
-- Table: logfiles_internal
-- Key: processing_time, server_instance_name, database_name, name
-- ===================================================================== 
IF(OBJECT_ID(N'[sysutility_ucp_core].[logfiles_internal]', 'U') IS NULL)
BEGIN
    RAISERROR ('Creating table [sysutility_ucp_core].[logfiles_internal]', 0, 1) WITH NOWAIT;
    CREATE TABLE [sysutility_ucp_core].[logfiles_internal]
    (
        [urn] NVARCHAR(1500)
        , [powershell_path]  NVARCHAR(MAX)
        , [processing_time] DATETIMEOFFSET(7)
        , [batch_time] DATETIMEOFFSET(7)
        , [server_instance_name]    SYSNAME
        , [database_name]    SYSNAME
        , [parent_urn] NVARCHAR(780)
        , [physical_server_name] SYSNAME

        , [volume_name] NVARCHAR(260) 
        , [volume_device_id] SYSNAME

      -- SMO Properties
        , [Growth]  REAL
        , [GrowthType] SMALLINT
        , [MaxSize]  REAL
        , [Name] SYSNAME
        , [Size] REAL
        , [UsedSpace]  REAL
        , [FileName] NVARCHAR(260)
        , [VolumeFreeSpace] BIGINT
        
        
        , CONSTRAINT PK_logfiles_internal
            PRIMARY KEY CLUSTERED (processing_time, server_instance_name, database_name, [Name])
        -- Note: compression is enabled on this table at runtime (during Create UCP) in sp_initialize_mdw_internal
    );
    
    EXEC sp_addextendedproperty 
        @name = 'MS_UtilityObjectType', @value = 'DIMENSION', 
        @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
        @level1type = 'TABLE', @level1name = 'logfiles_internal';
END
GO

-----------------------------------------------------------------------------
--  The view which returns server properties
--  NOTE: If you change the output of this view in any way, be sure to also update the 
--  corresponding "stub" object in instmsdb.sql. 
-----------------------------------------------------------------------------
IF  EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_smo_servers]'))
BEGIN
   RAISERROR('Dropping [sysutility_ucp_core].[latest_smo_servers] view', 0, 1) WITH NOWAIT;
   DROP VIEW [sysutility_ucp_core].[latest_smo_servers]
END
GO

RAISERROR('Creating [sysutility_ucp_core].[latest_smo_servers] view', 0, 1) WITH NOWAIT;
GO
CREATE VIEW [sysutility_ucp_core].[latest_smo_servers]
AS

SELECT urn
, [powershell_path]
, [processing_time]
, [batch_time]
, [AuditLevel]
, [BackupDirectory]
, [BrowserServiceAccount]
, [BrowserStartMode]
, [BuildClrVersionString]
, [BuildNumber]
, [Collation]
, [CollationID]
, [ComparisonStyle]
, [ComputerNamePhysicalNetBIOS]
, [DefaultFile]
, [DefaultLog]
, [Edition]
, [EngineEdition]
, [ErrorLogPath]
, [FilestreamShareName]
, [InstallDataDirectory]
, [InstallSharedDirectory]
, [InstanceName]
, [IsCaseSensitive]
, [IsClustered]
, [IsFullTextInstalled]
, [IsSingleUser]
, [Language]
, [MailProfile]
, [MasterDBLogPath]
, [MasterDBPath]
, [MaxPrecision]
, [Name]
, [NamedPipesEnabled]
, [NetName]
, [NumberOfLogFiles]
, [OSVersion]
, [PerfMonMode]
, [PhysicalMemory]
, [Platform]
, [Processors]
, [ProcessorUsage]
, [Product]
, [ProductLevel]
, [ResourceVersionString]
, [RootDirectory]
, [ServerType]
, [ServiceAccount]
, [ServiceInstanceId]
, [ServiceName]
, [ServiceStartMode]
, [SqlCharSet]
, [SqlCharSetName]
, [SqlDomainGroup]
, [SqlSortOrder]
, [SqlSortOrderName]
, [Status]
, [TapeLoadWaitTime]
, [TcpEnabled]
, [VersionMajor]
, [VersionMinor]
, [VersionString]
FROM [sysutility_ucp_core].[smo_servers_internal] AS SI
WHERE SI.processing_time =  (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'CORE', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
   @level1type = 'VIEW', @level1name = 'latest_smo_servers';
GO


------------------------------------------------------------------------------
--  SQL Server View to read latest snapshot data for SMO Database object
--  NOTE: If you change the output of this view in any way, be sure to also update the 
--  corresponding "stub" object in instmsdb.sql. 
------------------------------------------------------------------------------
IF  EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_databases]'))
BEGIN
   RAISERROR('Dropping [sysutility_ucp_core].[latest_databases] view', 0, 1) WITH NOWAIT;
   DROP VIEW [sysutility_ucp_core].[latest_databases]
END
GO

RAISERROR('Creating [sysutility_ucp_core].[latest_databases] view', 0, 1) WITH NOWAIT;
GO
CREATE VIEW [sysutility_ucp_core].[latest_databases]
AS
    SELECT [urn]
        , [powershell_path]
        , [processing_time]
        , [batch_time]
        , [server_instance_name]
        , [parent_urn]
        , [Collation]
        , [CompatibilityLevel]
        , [CreateDate]
        , [EncryptionEnabled]
        , [Name]
        , [RecoveryModel]
        , [Trustworthy]  
        , [state]
        FROM [sysutility_ucp_core].[databases_internal] AS SI
        WHERE SI.processing_time =  (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'CORE', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
   @level1type = 'VIEW', @level1name = 'latest_databases';
GO


------------------------------------------------------------------------------
--  SQL Server View to read latest snapshot data for SMO FileGroup object
--  NOTE: If you change the output of this view in any way, be sure to also update the 
--  corresponding "stub" object in instmsdb.sql. 
------------------------------------------------------------------------------
IF  EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_filegroups]'))
BEGIN
   RAISERROR('Dropping [sysutility_ucp_core].[latest_filegroups] view', 0, 1) WITH NOWAIT;
   DROP VIEW [sysutility_ucp_core].[latest_filegroups]
END
GO

RAISERROR('Creating [sysutility_ucp_core].[latest_filegroups] view', 0, 1) WITH NOWAIT;
GO
CREATE VIEW [sysutility_ucp_core].[latest_filegroups]
AS
    SELECT [urn]
        , [powershell_path]
        , [processing_time]
        , [batch_time]
        , [server_instance_name]
        , [database_name]
        , [parent_urn]
        , [Name]
        FROM [sysutility_ucp_core].[filegroups_internal] AS SI
        WHERE SI.processing_time =  (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])
            -- Suppress for "not available" databases (state=1). We lack full filegroup/file hierarchy metadata for these databases. 
            AND EXISTS (
                SELECT * FROM sysutility_ucp_core.latest_databases AS db 
                WHERE db.server_instance_name = SI.server_instance_name AND db.Name = SI.database_name AND db.state = 0)
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'CORE', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
   @level1type = 'VIEW', @level1name = 'latest_filegroups';
GO


------------------------------------------------------------------------------
--  SQL Server View to read latest snapshot data for SMO DataFile object
--  NOTE: If you change the output of this view in any way, be sure to also update the 
--  corresponding "stub" object in instmsdb.sql. 
------------------------------------------------------------------------------
IF  EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_datafiles]'))
BEGIN
   RAISERROR('Dropping [sysutility_ucp_core].[latest_datafiles] view', 0, 1) WITH NOWAIT;
   DROP VIEW [sysutility_ucp_core].[latest_datafiles]
END
GO

RAISERROR('Creating [sysutility_ucp_core].[latest_datafiles] view', 0, 1) WITH NOWAIT;
GO
CREATE VIEW [sysutility_ucp_core].[latest_datafiles]
AS
 SELECT [urn]
        , [powershell_path]
        , [processing_time]
        , [batch_time]
        , [server_instance_name]
        , [database_name] 
        , [filegroup_name]
        , [parent_urn]
        , [physical_server_name]
        , [volume_name]
        , [volume_device_id]
        , [Growth] 
        , [GrowthType]
        , [MaxSize] 
        , [Name]
        , [Size]
        , [UsedSpace] 
        , [FileName]
        , [VolumeFreeSpace]
        , [sysutility_ucp_misc].[fn_get_max_size_available]([Size], [MaxSize], [Growth], [GrowthType], [VolumeFreeSpace]) AS [available_space]
        FROM [sysutility_ucp_core].[datafiles_internal] AS SI
        WHERE SI.processing_time =  (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])
            -- Suppress for "not available" databases (state=1). We lack full filegroup/file hierarchy metadata for these databases. 
            AND EXISTS (
                SELECT * FROM sysutility_ucp_core.latest_databases AS db 
                WHERE db.server_instance_name = SI.server_instance_name AND db.Name = SI.database_name AND db.state = 0)
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'CORE', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
   @level1type = 'VIEW', @level1name = 'latest_datafiles';
GO


------------------------------------------------------------------------------
--  SQL Server View to read latest snapshot data for SMO DataFile object
--  NOTE: If you change the output of this view in any way, be sure to also update the 
--  corresponding "stub" object in instmsdb.sql. 
------------------------------------------------------------------------------
IF  EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[latest_logfiles]'))
BEGIN
   RAISERROR('Dropping [sysutility_ucp_core].[latest_logfiles] view', 0, 1) WITH NOWAIT;
   DROP VIEW [sysutility_ucp_core].[latest_logfiles]
END
GO

RAISERROR('Creating [sysutility_ucp_core].[latest_logfiles] view', 0, 1) WITH NOWAIT;
GO
CREATE VIEW [sysutility_ucp_core].[latest_logfiles]
AS
 SELECT [urn]
        , [powershell_path]
        , [processing_time]
        , [batch_time]
        , [server_instance_name]
        , [database_name] 
        , [parent_urn]
        , [physical_server_name]
        , [volume_name]
        , [volume_device_id]
        , [Growth] 
        , [GrowthType]
        , [MaxSize] 
        , [Name]
        , [Size]
        , [UsedSpace] 
        , [FileName]
        , [VolumeFreeSpace]
        , [sysutility_ucp_misc].[fn_get_max_size_available]([Size], [MaxSize], [Growth], [GrowthType], [VolumeFreeSpace]) AS [available_space]
        FROM [sysutility_ucp_core].[logfiles_internal] AS SI
        WHERE SI.processing_time =  (SELECT latest_processing_time FROM [msdb].[dbo].[sysutility_ucp_processing_state_internal])
            -- Suppress for "not available" databases (state=1). We lack full filegroup/file hierarchy metadata for these databases. 
            AND EXISTS (
                SELECT * FROM sysutility_ucp_core.latest_databases AS db 
                WHERE db.server_instance_name = SI.server_instance_name AND db.Name = SI.database_name AND db.state = 0)
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'CORE', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
   @level1type = 'VIEW', @level1name = 'latest_logfiles';
GO


/***********************************************************************/
/* Utility SCHEMA: (sysutility_ucp_core)                               */
/* Measure Tables and Views                                            */
/*                                                                     */
/*   We currently handle the following measures (and hence measure     */
/* tables).                                                            */
/*   CPU           (Table: cpu_utilization_internal)                   */
/*   Storage Space (Table: space_utilization_internal)                 */
/*                                                                     */
/* CPU information is stored for the following dimensions              */
/*   - Computers                                                       */
/*   - Instances                                                       */
/*   - Dacs                                                            */
/*                                                                     */
/* Storage space information is stored for the following dimensions    */
/*   - Computers                                                       */
/*   - Volumes                                                         */
/*   - Instances                                                       */
/*   - Databases                                                       */
/*   - FileGroups                                                      */
/*   - DataFiles                                                       */
/*   - LogFiles                                                        */
/*                                                                     */
/* Each measure table stores information at different levels of        */
/* aggregation. Currently, we support 3 levels of aggregation          */
/*   - No aggregation (i.e.) latest "detail" information               */
/*   - Hourly                                                          */
/*   - Daily                                                           */
/* We expect to add additional aggregation levels in the future        */
/*                                                                     */
/* Information for each aggregation level is stored in a different     */
/* partition of the measure table. This allows us to leverage          */
/* partition pruning (for queries), and different maintenance operations */
/* for each partition (especially wrt purging of data.                 */
/*                                                                     */
/* Within each partition, (processing_time, object_type) */
/* is the prefix of the key. This allows for inserts at the end, and   */
/* purges at the front for new data. It also ensures that information  */
/* about each object type is collocated within a given collection time */
/*                                                                     */
/***********************************************************************/
---
-- Describes the aggregation level
-- 0 = Non-aggregated; 1 = Hourly, 2 = Daily, ...
--
IF NOT EXISTS(SELECT 0 FROM sys.types t 
              WHERE t.name = N'AggregationType' AND t.schema_id = SCHEMA_ID(N'sysutility_ucp_core'))
BEGIN
  RAISERROR ('Creating type [sysutility_ucp_core].[AggregationType]', 0, 1) WITH NOWAIT;
  CREATE TYPE [sysutility_ucp_core].[AggregationType] FROM TINYINT
END
GO

-- 0 = utility
-- 1 = computer
-- 2 = volume
-- 3 = instance
-- 4 = database (also dac)
-- 5 = filegroup
-- 6 = datafile
-- 7 = logfile
IF NOT EXISTS(SELECT 0 FROM sys.types t 
              WHERE t.name = N'ObjectType' AND t.[schema_id] = SCHEMA_ID(N'sysutility_ucp_core')) 
BEGIN
  RAISERROR ('Creating type [sysutility_ucp_core].[ObjectType]', 0, 1) WITH NOWAIT;
  CREATE TYPE [sysutility_ucp_core].[ObjectType] FROM TINYINT
END
GO


-- ============================================================================
-- Measure Table: CPU Utilization information
--   Supported dimensions: Computers, Instances, Databases (DACs)
--   Supported aggregation-levels: none, hourly, daily
--
-- The object_type field describes the dimension that the current entry (row) 
-- defines. 
-- The following conditions must hold.
--   If object_type = 1, server_instance_name = NULL and database_name = NULL and physical_server_name <> NULL
--   If object type = 3, physical_server_name = NULL, database_name = NULL, server_instance_name <> NULL
--   If object_type = 4, physical_server_name = NULL, server_instance_name <> NULL, database_name <> NULL
--   No other legal combinations
-- ============================================================================
IF(OBJECT_ID(N'[sysutility_ucp_core].[cpu_utilization_internal]', N'U') IS NULL)
BEGIN
    RAISERROR ('Creating table [sysutility_ucp_core].[cpu_utilization_internal]', 0, 1) WITH NOWAIT;
    
    CREATE TABLE [sysutility_ucp_core].[cpu_utilization_internal]
    (
       [processing_time]  DATETIMEOFFSET(7) NOT NULL,
       [aggregation_type] [sysutility_ucp_core].AggregationType NOT NULL,
       [object_type]      [sysutility_ucp_core].ObjectType NOT NULL, 
       
       -- Dimension keys
       [physical_server_name] SYSNAME DEFAULT N'',
       [server_instance_name] SYSNAME DEFAULT N'',
       [database_name]        SYSNAME DEFAULT N'',
      
       -- The actual measure columns.
       percent_total_cpu_utilization REAL,
        
       -- NOTE: This index is redefined at runtime (during Create UCP) in sp_initialize_mdw_internal              
       CONSTRAINT pk_cpu_utilization_internal 
         PRIMARY KEY CLUSTERED(aggregation_type, processing_time, object_type, 
                               physical_server_name, server_instance_name, database_name) 
    )
    
    EXEC sp_addextendedproperty 
        @name = 'MS_UtilityObjectType', @value = 'MEASURE', 
        @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
        @level1type = 'TABLE', @level1name = 'cpu_utilization_internal';
END
GO

---------------------------------------------------------------------------
-- View to select information from the [cpu_utilization_internal] measure
-- table.
--  NOTE: If you change the shape of this view in any way, be sure to also 
--  update the corresponding "stub" object in instmsdb.sql. 
-----------------------------------------------------------------------
IF  EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[cpu_utilization]'))
BEGIN
   RAISERROR('Dropping [sysutility_ucp_core].[cpu_utilization] view', 0, 1)  WITH NOWAIT; 
   DROP VIEW [sysutility_ucp_core].[cpu_utilization]
END
GO
RAISERROR('Creating [sysutility_ucp_core].[cpu_utilization] view', 0, 1) WITH NOWAIT;
GO
CREATE VIEW [sysutility_ucp_core].[cpu_utilization]
AS
  SELECT aggregation_type, processing_time, object_type,
         physical_server_name, server_instance_name, database_name,
         percent_total_cpu_utilization
  FROM [sysutility_ucp_core].[cpu_utilization_internal]
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'CORE', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
   @level1type = 'VIEW', @level1name = 'cpu_utilization';
GO


-- ============================================================================
-- Measure Table: Space Utilization information
--   Supported dimensions: Computers, Volumes, Instances, Databases, 
--                         Filegroups, DataFiles, LogFiles
--   Supported aggregation-levels: none, hourly, daily
--
-- The object_type field describes the dimension that the current entry (row) 
-- defines. 
-- The following conditions must hold.
--    Utility  (type=0), all dimension columns must be ''
--    Computer (type=1), virtual_server_name must be non-null; the rest must be ''
--    Volume   (type=2), virtual_server_name, volume_device_id must be non-null; rest ''
--    Instance (type=3), server_instance_name must be non-NULL, everything else must be ''
--    Database (type=4), server_instance_name, database_name must be non-null
--                          other keys must be ''
--    FileGroup(type=5), server_instance_name, datbase_name, filegroup_name must be non-NULL
--                          other keys must be ''
--    DataFile (type=6), server_instance_name, database_name, filegroup_name, dbfile_name 
--                       must be non-null. Other keys must be ''
--    LogFile  (type=7), server_instance_name, database_name, dbfile_name must be non-null
--                          other keys must be ''
--
-- IMPORTANT: Unlike the cpu_utilization measure table, the space_utilization measure
--   table uses virtual_server_name to represent a computer (and volume). This is
--   because storage is shared (potentially) across a failover-cluster-instance,
--   and we want to track history of the space usage regardless of the specific 
--   current "owner" of the storage
--
-- The space_utilization_internal table stores information about two distinct hierarchies.
-- The Utility->Computer->Volume hierarchy and the Instance->Database->FileGroup->File 
-- hiererachy. A row in the table is part of only one of these hierarchies.
--
-- There are 4 distinct measure columns we maintain in this table. Not all of them are
-- really need. See VSTS #345039
--   total_space_bytes    : the maximum amount of storage available
--   allocated_space_bytes: the currently allocated amount of storage. Typically, the
--                          same as total_space_bytes, except for files
--   used_space_bytes     : the current used up space
--   available_space_bytes: the amount of space that's available for further use. 
--                          Typically this is total_space_bytes - used_space_bytes. 
--                          Except for files, where this is more complicated
--  
-- We should be able to live with just two of these columns (total_space and used_space)
--
-- For the instance-database-... hierarchy, only used_space_bytes is rolled up. All
-- the other values are rolled up to NULL
-- For the Utility-computer-volume hierarchy, all values are rolled up.
--  
--                          
-- 
-- VSTS #345570: The key length of the clustered index may be larger than 900 bytes.
--
-- ============================================================================
IF(OBJECT_ID(N'[sysutility_ucp_core].[space_utilization_internal]', N'U') IS NULL)
BEGIN
    RAISERROR ('Creating table [sysutility_ucp_core].[space_utilization_internal]', 0, 1) WITH NOWAIT;
    CREATE TABLE [sysutility_ucp_core].[space_utilization_internal]
    (
       [processing_time] DATETIMEOFFSET(7) NOT NULL,
       [aggregation_type] [sysutility_ucp_core].AggregationType NOT NULL,
       [object_type]      [sysutility_ucp_core].ObjectType NOT NULL, 
          
       -- The dimension columns
       [virtual_server_name]  SYSNAME DEFAULT N'',
       [server_instance_name] SYSNAME DEFAULT N'',
       [volume_device_id]     SYSNAME DEFAULT N'',
       [database_name]        SYSNAME DEFAULT N'',
       [filegroup_name]       SYSNAME DEFAULT N'',
       [dbfile_name]          SYSNAME DEFAULT N'',
       
       -- todo (VSTS #345039)
       -- we don't need all 4 of the columns below. We only need used_space and available_space
       [used_space_bytes]        REAL,
       [allocated_space_bytes]   REAL,
       [total_space_bytes]       REAL,
       [available_space_bytes]   REAL,

       -- NOTE: This index is redefined at runtime (during Create UCP) in sp_initialize_mdw_internal              
       CONSTRAINT pk_storage_utilization 
         PRIMARY KEY CLUSTERED(
            aggregation_type,
            processing_time, 
            object_type, 
            virtual_server_name, 
            volume_device_id, 
            server_instance_name, 
            database_name, 
            [filegroup_name], 
            dbfile_name) 
    )
    
    EXEC sp_addextendedproperty 
        @name = 'MS_UtilityObjectType', @value = 'MEASURE', 
        @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
        @level1type = 'TABLE', @level1name = 'space_utilization_internal';
END
GO

---------------------------------------------------------------------------
-- View to select information from the [space_utilization_internal] measure
-- table.
--  NOTE: If you change the shape of this view in any way, be sure to also 
--  update the corresponding "stub" object in instmsdb.sql. 
-----------------------------------------------------------------------
IF  EXISTS (SELECT name FROM sys.views WHERE object_id = OBJECT_ID(N'[sysutility_ucp_core].[space_utilization]'))
BEGIN
   RAISERROR('Dropping [sysutility_ucp_core].[space_utilization] view', 0, 1)  WITH NOWAIT; 
   DROP VIEW [sysutility_ucp_core].[space_utilization]
END
GO

RAISERROR('Creating [sysutility_ucp_core].[space_utilization] view', 0, 1) WITH NOWAIT;
GO
CREATE VIEW [sysutility_ucp_core].[space_utilization]
AS
  SELECT aggregation_type, 
         processing_time, 
         object_type,
         virtual_server_name, 
         volume_device_id, 
         server_instance_name, 
         database_name, 
         [filegroup_name], 
         dbfile_name,
         total_space_bytes, 
         allocated_space_bytes, 
         used_space_bytes, 
         available_space_bytes
  FROM [sysutility_ucp_core].[space_utilization_internal]
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'CORE', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
   @level1type = 'VIEW', @level1name = 'space_utilization';
GO


-----------------------------------------------------------------------------------------
-- Procedure sp_copy_live_table_data_into_cache_tables
--   Copies the latest snapshot of data from the "live" tables into the "cache" tables 
-----------------------------------------------------------------------------------------
IF OBJECT_ID ('sysutility_ucp_staging.sp_copy_live_table_data_into_cache_tables') IS NOT NULL
BEGIN
   RAISERROR('Dropping procedure sysutility_ucp_staging.sp_copy_live_table_data_into_cache_tables procedure', 0, 1) WITH NOWAIT;
   DROP PROCEDURE sysutility_ucp_staging.sp_copy_live_table_data_into_cache_tables;
END
GO
RAISERROR('Creating procedure sysutility_ucp_staging.sp_copy_live_table_data_into_cache_tables', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE sysutility_ucp_staging.sp_copy_live_table_data_into_cache_tables
AS
BEGIN
      SET NOCOUNT ON;
      -- Snapshot isolation prevents the nightly purge jobs that delete much older data from blocking us. 
      SET TRANSACTION ISOLATION LEVEL SNAPSHOT; 

      DECLARE @max_snapshot_id INT, @num_snapshots_partitions INT
      
      SELECT @max_snapshot_id = ISNULL(MAX(snapshot_id),0) FROM [core].[snapshots]
      SELECT @num_snapshots_partitions = COUNT(*) FROM [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal]
      DECLARE @task_start_time DATETIME = GETUTCDATE();
      DECLARE @task_elapsed_ms INT;
      DECLARE @row_count INT;
      
        -- Initialize the snapshot partitions to default (0)
        IF(@num_snapshots_partitions = 0)
        BEGIN
            INSERT INTO [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] VALUES (2, 0)
            INSERT INTO [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] VALUES (1, 0)
            INSERT INTO [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] VALUES (0, 0)
        END   
                
        DECLARE @processing_time_current DATETIMEOFFSET(7) = SYSDATETIMEOFFSET();

         --
         -- Stage 0:
         --  Identify the batches that were recently uploaded and are consistent 
         --  Data belonging to these batches will be copied from live to cache table.	  
         EXEC [sysutility_ucp_staging].[sp_get_consistent_batches]
         SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
         RAISERROR ('sp_get_consistent_batches: %d ms', 0, 1, @task_elapsed_ms);
         SET @task_start_time = GETUTCDATE();

         ----- 
         ----- Stage 1: Insert into all the dimension tables 
         -----          computers_internal, dacs_internal, volumes_internal,
         -----          smo_servers_internal, databases_internal, filegroups_internal, 
         -----          datafiles_internal, logfiles_internal
         -----   (Then move to Stage 2: the "measure" tables)
         -----

         -- A note about the expression used for [batch_time] in the INSERT queries, below: 
         -- 
         -- We want to expose batch_time w/a UCP-local time zone so it can be exposed as a UCP-local datetime 
         -- in the GUI (the GUI consumes directly from the enumerator).  The batch_time values in each of the 
         -- queries below have their time zone offset switched to produce a datetimeoffset with the local UCP 
         -- server's time zone offset.  
         -- 
         -- This works well except when the server's time zone offset has changed since the [batch_time] was 
         -- generated due to a Daylight Saving Time change.  (Unfortunately, there is no way in T-SQL to 
         -- determine what the server's time zone offset was at some arbitrary point in the past.)  The risk 
         -- of this is low since we generally do this processing within 15 minutes of timestamp generation.  
         -- This doesn't actually result in truly incorrect batch_times that would affect data processing 
         -- since the UTC time value that underlies every datetimeoffset is unchanged when you switch the 
         -- value's time zone offset. 
         
         --
         -- Insert into the "computers" dimension table
         --
         INSERT INTO [sysutility_ucp_core].[computers_internal] (
               virtual_server_name, 
               is_clustered_server,
               physical_server_name,
               num_processors, 
               cpu_name, cpu_caption, cpu_family, cpu_architecture, cpu_max_clock_speed, cpu_clock_speed, 
               l2_cache_size, l3_cache_size,
               percent_total_cpu_utilization,
               batch_time, processing_time,
               urn, powershell_path)
            SELECT virtual_server_name, is_clustered_server, physical_server_name, 
                   num_processors, 
                   cpu_name, cpu_caption, cpu_family, cpu_architecture, cpu_max_clock_speed, cpu_clock_speed,
                   l2_cache_size, l3_cache_size, 
                   server_processor_usage,
                   SWITCHOFFSET (batch_time, DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS batch_time, 
                   @processing_time_current AS processing_time, 
                   urn, powershell_path
            FROM [sysutility_ucp_staging].[latest_computer_cpu_memory_configuration]
          SET @row_count = @@ROWCOUNT;
          SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
          RAISERROR ('Insert into [computers_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms);
          SET @task_start_time = GETUTCDATE();
           
          --
          -- Insert into the "dacs_internal" dimension-table
          --
          INSERT INTO [sysutility_ucp_core].[dacs_internal] (
                 server_instance_name, dac_name, 
                 physical_server_name, dac_deploy_date, dac_description,
                 dac_percent_total_cpu_utilization, 
                 batch_time, processing_time,
                 urn, powershell_path)
           SELECT server_instance_name, dac_name,
                  physical_server_name, dac_deploy_date, dac_description,
                  latest_cpu_pct, 
                  SWITCHOFFSET (batch_time, DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS batch_time, 
                  @processing_time_current AS processing_time, 
                  urn, powershell_path
           FROM [sysutility_ucp_staging].[latest_dac_cpu_utilization] 
          SET @row_count = @@ROWCOUNT;
          SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
          RAISERROR ('Insert into [dacs_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms);
          SET @task_start_time = GETUTCDATE();
           
         --- Insert into the volumes_internal dimension table  
         INSERT INTO [sysutility_ucp_core].[volumes_internal] (
                virtual_server_name, physical_server_name, volume_device_id, volume_name,
                total_space_available, free_space, powershell_path,
                batch_time, processing_time)
           SELECT virtual_server_name, physical_server_name, volume_device_id, volume_name,
                  total_space_available, free_space, powershell_path,
                  SWITCHOFFSET (batch_time, DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS batch_time, 
                  @processing_time_current AS processing_time
           FROM [sysutility_ucp_staging].[latest_volumes]
         SET @row_count = @@ROWCOUNT;
         SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
         RAISERROR ('Insert into [volumes_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms);
         SET @task_start_time = GETUTCDATE();
           
         INSERT INTO [sysutility_ucp_core].[smo_servers_internal]
         (
            [urn]  
            , [powershell_path]
            , [processing_time] 
            , [batch_time]
            , [AuditLevel]  
            , [BackupDirectory]  
            , [BrowserServiceAccount]  
            , [BrowserStartMode]  
            , [BuildClrVersionString]  
            , [BuildNumber]  
            , [Collation]  
            , [CollationID]  
            , [ComparisonStyle]  
            , [ComputerNamePhysicalNetBIOS]  
            , [DefaultFile]  
            , [DefaultLog]  
            , [Edition]  
            , [EngineEdition]  
            , [ErrorLogPath]  
            , [FilestreamShareName]  
            , [InstallDataDirectory]  
            , [InstallSharedDirectory]  
            , [InstanceName]  
            , [IsCaseSensitive]  
            , [IsClustered]  
            , [IsFullTextInstalled]  
            , [IsSingleUser]  
            , [Language]  
            , [MailProfile]  
            , [MasterDBLogPath]  
            , [MasterDBPath]  
            , [MaxPrecision]  
            , [Name]  
            , [NamedPipesEnabled]  
            , [NetName]  
            , [NumberOfLogFiles]  
            , [OSVersion]  
            , [PerfMonMode]  
            , [PhysicalMemory]  
            , [Platform]  
            , [Processors]  
            , [ProcessorUsage]  
            , [Product]  
            , [ProductLevel]  
            , [ResourceVersionString]  
            , [RootDirectory]  
            , [ServerType]  
            , [ServiceAccount]  
            , [ServiceInstanceId]  
            , [ServiceName]  
            , [ServiceStartMode]  
            , [SqlCharSet]  
            , [SqlCharSetName]  
            , [SqlDomainGroup]  
            , [SqlSortOrder]  
            , [SqlSortOrderName]  
            , [Status]  
            , [TapeLoadWaitTime]  
            , [TcpEnabled]  
            , [VersionMajor]  
            , [VersionMinor]  
            , [VersionString]  
        )

        SELECT  urn
              , CONVERT(NVARCHAR(MAX), [powershell_path]) AS [powershell_path]
                , @processing_time_current AS [processing_time]  -- $FIXED: SQLBUVSTS-316258
                , SWITCHOFFSET (batch_time, DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS [batch_time]  
                , CONVERT(SMALLINT,[AuditLevel]) AS [AuditLevel]
                , CONVERT(NVARCHAR(260) ,[BackupDirectory]) AS [BackupDirectory]
                , CONVERT(NVARCHAR(128) ,[BrowserServiceAccount]) AS [BrowserServiceAccount]
                , CONVERT(SMALLINT,[BrowserStartMode]) AS [BrowserStartMode]
                , CONVERT(NVARCHAR(20) ,[BuildClrVersionString]) AS [BuildClrVersionString]
                , CONVERT(INT,[BuildNumber]) AS [BuildNumber]
                , CONVERT(NVARCHAR(128),[Collation]) AS [Collation]
                , CONVERT(INT,[CollationID]) AS [CollationID]
                , CONVERT(INT,[ComparisonStyle]) AS [ComparisonStyle]
                , CONVERT(NVARCHAR(128),[ComputerNamePhysicalNetBIOS]) AS [ComputerNamePhysicalNetBIOS]
                , CONVERT(NVARCHAR(260),[DefaultFile]) AS [DefaultFile]
                , CONVERT(NVARCHAR(260),[DefaultLog]) AS [DefaultLog]
                , CONVERT(NVARCHAR(64),[Edition]) AS [Edition]
                , CONVERT(SMALLINT,[EngineEdition]) AS [EngineEdition]
                , CONVERT(NVARCHAR(260) ,[ErrorLogPath]) AS [ErrorLogPath]
                , CONVERT(NVARCHAR(260) ,[FilestreamShareName]) AS [FilestreamShareName]
                , CONVERT(NVARCHAR(260) ,[InstallDataDirectory]) AS [InstallDataDirectory]
                , CONVERT(NVARCHAR(260) ,[InstallSharedDirectory]) AS [InstallSharedDirectory]
                , CONVERT(NVARCHAR(128) ,[InstanceName]) AS [InstanceName]
                , CONVERT(BIT,[IsCaseSensitive]) AS [IsCaseSensitive]
                , CONVERT(BIT,[IsClustered]) AS [IsClustered]
                , CONVERT(BIT,[IsFullTextInstalled]) AS [IsFullTextInstalled]
                , CONVERT(BIT,[IsSingleUser]) AS [IsSingleUser]
                , CONVERT(NVARCHAR(64) ,[Language]) AS [Language]
                , CONVERT(NVARCHAR(128),[MailProfile]) AS [MailProfile]
                , CONVERT(NVARCHAR(260),[MasterDBLogPath]) AS [MasterDBLogPath]
                , CONVERT(NVARCHAR(260),[MasterDBPath]) AS [MasterDBPath]
                , CONVERT(TINYINT,[MaxPrecision]) AS [MaxPrecision]
                , CONVERT(NVARCHAR(128) ,[Name]) AS [Name]
                , CONVERT(BIT,[NamedPipesEnabled]) AS [NamedPipesEnabled]
                , CONVERT(NVARCHAR(128) ,[NetName]) AS [NetName]
                , CONVERT(INT,[NumberOfLogFiles]) AS [NumberOfLogFiles]
                , CONVERT(NVARCHAR(32) ,[OSVersion]) AS [OSVersion]
                , CONVERT(SMALLINT,[PerfMonMode]) AS [PerfMonMode]
                , CONVERT(INT,[PhysicalMemory]) AS [PhysicalMemory]
                , CONVERT(NVARCHAR(32) ,[Platform]) AS [Platform]
                , CONVERT(SMALLINT,[Processors]) AS [Processors]
                , CONVERT(INT,[ProcessorUsage]) AS [ProcessorUsage]
                , CONVERT(NVARCHAR(48) ,[Product]) AS [Product]
                , CONVERT(NVARCHAR(32) ,[ProductLevel]) AS [ProductLevel]
                , CONVERT(NVARCHAR(32) ,[ResourceVersionString]) AS [ResourceVersionString]
                , CONVERT(NVARCHAR(260) ,[RootDirectory]) AS [RootDirectory]
                , CONVERT(SMALLINT,[ServerType]) AS [ServerType]
                , CONVERT(NVARCHAR(128),[ServiceAccount]) AS [ServiceAccount]
                , CONVERT(NVARCHAR(64),[ServiceInstanceId]) AS [ServiceInstanceId]
                , CONVERT(NVARCHAR(64),[ServiceName]) AS [ServiceName]
                , CONVERT(SMALLINT,[ServiceStartMode]) AS [ServiceStartMode]
                , CONVERT(SMALLINT,[SqlCharSet]) AS [SqlCharSet]
                , CONVERT(NVARCHAR(32),[SqlCharSetName]) AS [SqlCharSetName]
                , CONVERT(NVARCHAR(128),[SqlDomainGroup]) AS [SqlDomainGroup]
                , CONVERT(SMALLINT,[SqlSortOrder]) AS [SqlSortOrder]
                , CONVERT(NVARCHAR(64),[SqlSortOrderName]) AS [SqlSortOrderName]
                , CONVERT(SMALLINT,[Status]) AS [Status]
                , CONVERT(INT,[TapeLoadWaitTime]) AS [TapeLoadWaitTime]
                , CONVERT(BIT,[TcpEnabled]) AS [TcpEnabled]
                , CONVERT(INT,[VersionMajor]) AS [VersionMajor]
                , CONVERT(INT,[VersionMinor]) AS [VersionMinor]
                , CONVERT(NVARCHAR(32),[VersionString]) AS [VersionString]
                
                FROM 
                    (SELECT urn, property_name, property_value, batch_time 
                    FROM [sysutility_ucp_staging].[latest_smo_properties]
                    WHERE object_type = 1) props     -- object_type = 1 is Server
                PIVOT
                (
                    MAX(property_value)
                    FOR property_name IN (    
                                   [powershell_path]
                                 , [AuditLevel]
                            , [BackupDirectory]
                            , [BrowserServiceAccount]
                            , [BrowserStartMode]
                            , [BuildClrVersionString]
                            , [BuildNumber]
                            , [Collation]
                            , [CollationID]
                            , [ComparisonStyle]
                            , [ComputerNamePhysicalNetBIOS]
                            , [DefaultFile]
                            , [DefaultLog]
                            , [Edition]
                            , [EngineEdition]
                            , [ErrorLogPath]
                            , [FilestreamShareName]
                            , [InstallDataDirectory]
                            , [InstallSharedDirectory]
                            , [InstanceName]
                            , [IsCaseSensitive]
                            , [IsClustered]
                            , [IsFullTextInstalled]
                            , [IsSingleUser]
                            , [Language]
                            , [MailProfile]
                            , [MasterDBLogPath]
                            , [MasterDBPath]
                            , [MaxPrecision]
                            , [Name]
                            , [NamedPipesEnabled]
                            , [NetName]
                            , [NumberOfLogFiles]
                            , [OSVersion]
                            , [PerfMonMode]
                            , [PhysicalMemory]
                            , [Platform]
                            , [Processors]
                            , [ProcessorUsage]
                            , [Product]
                            , [ProductLevel]
                            , [ResourceVersionString]
                            , [RootDirectory]
                            , [ServerType]
                            , [ServiceAccount]
                            , [ServiceInstanceId]
                            , [ServiceName]
                            , [ServiceStartMode]
                            , [SqlCharSet]
                            , [SqlCharSetName]
                            , [SqlDomainGroup]
                            , [SqlSortOrder]
                            , [SqlSortOrderName]
                            , [Status]
                            , [TapeLoadWaitTime]
                            , [TcpEnabled]
                            , [VersionMajor]
                            , [VersionMinor]
                            , [VersionString] )
                ) AS pvt         
         SET @row_count = @@ROWCOUNT;
         SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
         RAISERROR ('Insert into [smo_servers_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms);
         SET @task_start_time = GETUTCDATE();
                  
         INSERT INTO [sysutility_ucp_core].[databases_internal]
                ([urn]
                , [powershell_path]
                , [processing_time]
                , [batch_time]
                , [server_instance_name]
                , [parent_urn]
                , [Collation]
                , [CompatibilityLevel]
                , [CreateDate]
                , [EncryptionEnabled]
                , [Name]
                , [RecoveryModel]
                , [Trustworthy]
                , [state])
            SELECT  [urn]
                  , CONVERT(NVARCHAR(MAX), [powershell_path]) AS [powershell_path]
                    , @processing_time_current AS processing_time  -- $FIXED: SQLBUVSTS-316258
                    , SWITCHOFFSET ([batch_time], DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS [batch_time] 
                    , [server_instance_name]
                    , Left(urn, CHARINDEX('/Database[', urn, 1)-1) AS parent_urn
                    , CONVERT(NVARCHAR(128),[Collation]) AS Collation
                    , CONVERT(SMALLINT,[CompatibilityLevel]) AS CompatibilityLevel
                    -- DC (SSIS) doesn't support sql_variant, so all properties are uploaded as nvarchar(4000).  To successfully round-trip 
                    -- the property values through nvarchar, we use the same language-independent conversion style on MI and UCP. The shared 
                    -- fn_sysutility_get_culture_invariant_conversion_style_internal function gives us a consistent conversion style for each 
                    -- property data type that is language-independent and that won't cause data loss.  We also use this function on the MI 
                    -- when converting to nvarchar so that the two conversions are symmetrical.  (Ref: VSTS 361531, 359504, 12967)
                    , CONVERT(DATETIME, [CreateDate], msdb.dbo.fn_sysutility_get_culture_invariant_conversion_style_internal('datetime')) AS CreateDate
                    , CONVERT(BIT,[EncryptionEnabled]) AS EncryptionEnabled
                    , CONVERT(SYSNAME,[Name])AS [Name]
                    , CONVERT(SMALLINT,[RecoveryModel]) AS RecoveryModel
                    , CONVERT(BIT,[Trustworthy]) AS Trustworthy
                    -- Default to 0 (available) state. We'll update this to 1 (not available) for emergency/offline/etc databases later (we 
                    -- need to examine file and filegroup properties to infer whether a database should be available or not available). 
                    , 0 AS state
             FROM 
             (SELECT urn, server_instance_name, property_name, property_value, batch_time 
             FROM [sysutility_ucp_staging].[latest_smo_properties]
             WHERE object_type = 2) props -- object_type = 1 is Database
                PIVOT
                (
                    MAX(property_value)
                    FOR property_name IN ( 
                                       [powershell_path]
                                      , [ID]
                                      , [Collation]
                                            , [CompatibilityLevel]
                                            , [CreateDate]
                                            , [EncryptionEnabled]
                                            , [Name]
                                            , [RecoveryModel]
                                            , [Trustworthy] )
                ) AS pvt    
         SET @row_count = @@ROWCOUNT;
         SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
         RAISERROR ('Insert into [databases_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms);
         SET @task_start_time = GETUTCDATE();
         
         
         INSERT INTO [sysutility_ucp_core].[filegroups_internal]
                ([urn]
                , [powershell_path]
                , [processing_time]
                , [batch_time]
                , [server_instance_name]
                , [database_name]
                , [parent_urn]
                , [Name])
            SELECT  [urn]
                  , CONVERT(NVARCHAR(MAX), [powershell_path]) AS [powershell_path]
                    , @processing_time_current AS processing_time  -- $FIXED: SQLBUVSTS-316258
                    , SWITCHOFFSET ([batch_time], DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS [batch_time] 
                    , [server_instance_name]
                    , CONVERT(SYSNAME,[parent_name]) AS [database_name]
                    , Left(urn, CHARINDEX('/FileGroup[', urn, 1)-1) AS parent_urn
                    , CONVERT(SYSNAME,[Name]) AS Name
             FROM 
             (SELECT urn, server_instance_name, property_name, property_value, batch_time 
             FROM [sysutility_ucp_staging].[latest_smo_properties]
             WHERE object_type = 4) props -- object_type = 4 is FileGroup
                PIVOT
                (
                    MAX(property_value)
                    FOR property_name IN ( 
                                        [powershell_path]
                                      , [parent_name]
                                      , [ID]
                                            , [Name])
                ) AS pvt    
         SET @row_count = @@ROWCOUNT;
         SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
         RAISERROR ('Insert into [filegroups_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms);
         SET @task_start_time = GETUTCDATE();
         
                  
         
         INSERT INTO [sysutility_ucp_core].[datafiles_internal]
                ([urn]
                , [powershell_path]
                , [processing_time]
                , [batch_time]
                , [server_instance_name]
                , [database_name]
                , [filegroup_name]
                , [parent_urn]
                , [Growth]
                , [GrowthType]
                , [MaxSize]
                , [Name]
                , [Size]
                , [UsedSpace]
                , [FileName]
                , [VolumeFreeSpace]
                , [volume_name]
                , [volume_device_id]
                , [physical_server_name])
            SELECT  [urn]
                  , CONVERT(NVARCHAR(MAX), pvt.[powershell_path]) AS [powershell_path]
                    , @processing_time_current AS processing_time  -- $FIXED: SQLBUVSTS-316258
                    , SWITCHOFFSET (pvt.[batch_time], DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS [batch_time] 
                    , pvt.[server_instance_name]
                    , CONVERT(SYSNAME,[grandparent_name]) AS [database_name]
                    , CONVERT(SYSNAME,[parent_name]) AS [filegroup_name]
                    , Left(urn, CHARINDEX('/File[', urn, 1)-1) AS parent_urn
                    , CONVERT(REAL,[Growth]) AS Growth
                    , CONVERT(SMALLINT,[GrowthType]) AS GrowthType
                    , CONVERT(REAL,[MaxSize]) AS MaxSize
                    , CONVERT(SYSNAME,[Name]) AS Name
                    , CONVERT(REAL,[Size]) AS Size
                    , CONVERT(REAL,[UsedSpace]) AS UsedSpace
                    , CONVERT(NVARCHAR(260),[FileName]) AS FileName
                    , ISNULL(v.free_space, 0.0) * 1024 AS VolumeFreeSpace -- volumes_internal.free_space is MB, and VolumeFreeSpace is expected to be KB.
                    , ISNULL(v.volume_name, N'') AS volume_name
                    , v.[volume_device_id] AS [volume_device_id]
                    , pvt.[physical_server_name]
             FROM 
             (SELECT urn, physical_server_name, server_instance_name, property_name, property_value, batch_time 
             FROM [sysutility_ucp_staging].[latest_smo_properties]
             WHERE object_type = 5) props -- object_type = 5 is DataFile
                PIVOT
                (
                    MAX(property_value)
                    FOR property_name IN ( 
                                        [powershell_path]
                                      , [parent_name]
                                      , [grandparent_name]
                                      , [ID]
                                      , [Growth]
                                      , [GrowthType]
                                      , [MaxSize]
                                      , [Name]
                                      , [Size]
                                      , [UsedSpace]
                                      , [FileName] 
                                      , [volume_device_id])
                ) AS pvt
            LEFT OUTER JOIN
            [sysutility_ucp_core].[volumes_internal] v
            ON 
            v.physical_server_name = pvt.physical_server_name AND
            CONVERT(SYSNAME, pvt.[volume_device_id]) = v.volume_device_id
            WHERE v.processing_time = @processing_time_current
         SET @row_count = @@ROWCOUNT;
         SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
         RAISERROR ('Insert into [datafiles_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms);
         SET @task_start_time = GETUTCDATE();
                             

         INSERT INTO [sysutility_ucp_core].[logfiles_internal]
                ([urn]
                , [powershell_path]
                , [processing_time]
                , [batch_time]
                , [server_instance_name]
                , [database_name]
                , [parent_urn]
                , [Growth]
                , [GrowthType]
                , [MaxSize]
                , [Name]
                , [Size]
                , [UsedSpace]
                , [FileName]
                , [VolumeFreeSpace]
                , [volume_name]
                , [volume_device_id]
                , [physical_server_name])
            SELECT[urn]
                  , CONVERT(NVARCHAR(MAX), pvt.[powershell_path]) AS [powershell_path]
                    , @processing_time_current AS processing_time  -- $FIXED: SQLBUVSTS-316258
                    , SWITCHOFFSET (pvt.[batch_time], DATENAME(TZoffset, SYSDATETIMEOFFSET())) AS [batch_time] 
                    , pvt.[server_instance_name]
                    , CONVERT(SYSNAME,[parent_name]) AS [database_name]
                    , Left(urn, CHARINDEX('/LogFile[', urn, 1)-1) AS parent_urn
                    , CONVERT(REAL,[Growth]) AS Growth
                    , CONVERT(SMALLINT,[GrowthType]) AS GrowthType
                    , CONVERT(REAL,[MaxSize]) AS MaxSize
                    , CONVERT(SYSNAME,[Name]) AS Name
                    , CONVERT(REAL,[Size]) AS Size
                    --- The collection data may not contain the UsedSpace property of the log file of a database in EMERGENCY state. 
                    , ISNULL(CONVERT(REAL,[UsedSpace]), 0.0) AS UsedSpace
                    , CONVERT(NVARCHAR(260),[FileName]) AS FileName
                    , ISNULL(v.free_space, 0.0) * 1024 AS VolumeFreeSpace -- volumes_internal.free_space is MB, and VolumeFreeSpace is expected to be KB.
                    , ISNULL(v.volume_name, N'') AS volume_name
                    , v.[volume_device_id] AS [volume_device_id]
                    , pvt.[physical_server_name]
             FROM 
             (SELECT urn, physical_server_name, server_instance_name, property_name, property_value, batch_time 
             FROM [sysutility_ucp_staging].[latest_smo_properties] 
             WHERE object_type = 3) props -- object_type = 3 is LogFile
                PIVOT
                (
                    MAX(property_value)
                    FOR property_name IN ( 
                                       [powershell_path]
                                      , [parent_name]
                                      , [ID]
                                      , [Growth]
                                      , [GrowthType]
                                      , [MaxSize]
                                      , [Name]
                                      , [Size]
                                      , [UsedSpace]
                                      , [FileName] 
                                      , [volume_device_id])
                ) AS pvt  
            LEFT OUTER JOIN
            [sysutility_ucp_core].[volumes_internal] v
            ON 
            v.physical_server_name = pvt.physical_server_name AND
            CONVERT(SYSNAME, pvt.[volume_device_id]) = v.volume_device_id
            WHERE v.processing_time = @processing_time_current
         SET @row_count = @@ROWCOUNT;
         SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
         RAISERROR ('Insert into [logfiles_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms);
         SET @task_start_time = GETUTCDATE();
         
         -- Identify all of the databases for which we do not have all file/filegroup details because of the current 
         -- database status (db is offline, recovering, emergency mode, etc).  Update the state for these databases 
         -- to 1 ("not available"). 
         UPDATE sysutility_ucp_core.databases_internal 
         SET state = 1 
         FROM sysutility_ucp_core.databases_internal AS db
         WHERE 
             -- Case #1: Emergency mode databases are considered 'not available' -- the database is not recovered, and 
             -- we can't get correct log file metadata.  We detect this by looking for databases with a log file that 
             -- has a size of 0, which is impossible in an available database. 
            EXISTS (
                SELECT * 
                FROM sysutility_ucp_core.logfiles_internal AS lf
                WHERE lf.server_instance_name = db.server_instance_name 
                    AND lf.database_name = db.Name
                    AND lf.Size = 0
                    AND lf.processing_time = @processing_time_current)
            -- Case #2: When a database is in other "not available" states (like recovering, offline, suspect), we 
            -- cannot retrieve filegroup or file-level metadata.  We detect this case by looking for databases that seem 
            -- to have no filegroups, which is an impossible state for an online & available database. 
            OR NOT EXISTS (
                SELECT * 
                FROM sysutility_ucp_core.filegroups_internal AS fg
                WHERE fg.server_instance_name = db.server_instance_name 
                    AND fg.database_name = db.Name
                    AND fg.processing_time = @processing_time_current);
         SET @row_count = @@ROWCOUNT;
         SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
         RAISERROR ('Discover unavailable databases: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms);
         SET @task_start_time = GETUTCDATE();
         
         
         ----- 
         ----- Stage 2: Insert into all the measure tables
         -----          cpu_utilization_internal (computers, instances, dacs)
         -----          space_utilization_internal (volumes, instances, databases, filegroups, datafiles, logfiles)
         ----- 
         ----- 
         INSERT INTO [sysutility_ucp_core].[cpu_utilization_internal](
              aggregation_type, object_type, processing_time,
              physical_server_name, server_instance_name, database_name,
              percent_total_cpu_utilization)
            SELECT 0,   -- No aggregation
                   1,   -- Computer Object
                   @processing_time_current,
                   physical_server_name,
                   N'', 
                   N'',
                   percent_total_cpu_utilization
            FROM [sysutility_ucp_core].[computers_internal]
            WHERE processing_time = @processing_time_current
              UNION ALL
            SELECT 0,   -- No aggregation
                   3,   -- Instance object
                   @processing_time_current, 
                   N'', 
                   server_instance_name,
                   N'',
                   instance_processor_usage
            FROM [sysutility_ucp_staging].[latest_instance_cpu_utilization]
              UNION ALL
            SELECT 0,   -- No aggregation
                   4,   -- Database/DAC object
                   @processing_time_current,
                   N'',   -- computer_name
                   server_instance_name,
                   dac_name,
                   dac_percent_total_cpu_utilization
            FROM [sysutility_ucp_core].[dacs_internal]
            WHERE processing_time = @processing_time_current
         SET @row_count = @@ROWCOUNT;
         SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
         RAISERROR ('Insert into [cpu_utilization_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms);
         SET @task_start_time = GETUTCDATE();
            
         INSERT INTO [sysutility_ucp_core].[space_utilization_internal] (
               aggregation_type, object_type, processing_time,
               virtual_server_name, volume_device_id, 
               server_instance_name, database_name, [filegroup_name], dbfile_name,
               total_space_bytes, allocated_space_bytes, used_space_bytes, available_space_bytes)
            SELECT 0 AS aggregation_type,
                   CASE WHEN group_id = 0 AND [filegroup_name] = N'' THEN 7 -- logfile
                        WHEN group_id = 0 THEN 6 -- datafile
                        WHEN group_id = 1 THEN 5 -- filegroup
                        WHEN group_id = 3 THEN 4 -- database
                        WHEN group_id = 7 THEN 3 -- instance
                        ELSE NULL -- should never get here 
                   END as [object_type], 
                   @processing_time_current AS processing_time,
                   N'' as virtual_server_name, 
                   N'' as volume_device_id, 
                   ISNULL(server_instance_name, N''), -- shouldn't ever get to be null 
                   ISNULL(database_name, N''), 
                   ISNULL([filegroup_name], N''), 
                   ISNULL([dbfile_name], N''),
                   CASE WHEN group_id = 0 THEN total_space_kb * 1024 ELSE NULL END, 
                   CASE WHEN group_id = 0 THEN allocated_space_kb * 1024 ELSE NULL END, 
                   used_space_kb * 1024, 
                   CASE WHEN group_id = 0 THEN available_space_kb * 1024 ELSE NULL END
            FROM (
                SELECT server_instance_name, database_name, [filegroup_name], dbfile_name,  
                       SUM(MaxSize) AS total_space_kb,  -- Is this right?
                       SUM([Size]) AS allocated_space_kb, 
                       SUM(UsedSpace) AS used_space_kb, 
                       SUM(available_space) AS available_space_kb,
                       GROUPING_ID(server_instance_name, database_name, [filegroup_name], dbfile_name) AS group_id
                FROM (SELECT server_instance_name, database_name, [filegroup_name], [Name] as dbfile_name, 
                             MaxSize, [Size], UsedSpace,
                             [sysutility_ucp_misc].[fn_get_max_size_available]([Size], [MaxSize], [Growth], [GrowthType], [VolumeFreeSpace]) AS available_space
                       FROM [sysutility_ucp_core].[datafiles_internal] 
                       WHERE processing_time = @processing_time_current
                     UNION ALL
                      SELECT server_instance_name, database_name, N'' AS [filegroup_name], 
                             [Name] AS dbfile_name, 
                             MaxSize, [Size], UsedSpace,
                             [sysutility_ucp_misc].[fn_get_max_size_available]([Size], [MaxSize], [Growth], [GrowthType], [VolumeFreeSpace]) AS available_space
                      FROM [sysutility_ucp_core].[logfiles_internal]
                      WHERE processing_time = @processing_time_current) as dbfiles
                GROUP BY GROUPING SETS((server_instance_name), 
                                       (server_instance_name, database_name),
                                       (server_instance_name, database_name, [filegroup_name]),
                                       (server_instance_name, database_name, [filegroup_name], [dbfile_name])
                                      )
                 ) AS instance_space_utilizations
            UNION ALL       
            SELECT 0 AS aggregation_type,
                   CASE WHEN GROUPING_ID(virtual_server_name, volume_device_id) = 3 THEN 0 -- utility 
                        WHEN GROUPING_ID(virtual_server_name, volume_device_id) = 1 THEN 1 -- computer 
                        WHEN GROUPING_ID(virtual_server_name, volume_device_id) = 0 THEN 2 -- volume
                        ELSE NULL  -- should never get here
                   END AS object_type,
                   @processing_time_current as processing_time,
                   ISNULL(virtual_server_name, N''), 
                   ISNULL(volume_device_id, N'') AS volume_device_id, 
                   N'' as server_instance_name, 
                   N'' as database_name, 
                   N'' as [filegroup_name], 
                   N'' as dbfile_name,
                   SUM(total_space_available)*1048576 AS total_space_bytes,
                   SUM(total_space_available)*1048576 AS allocated_space_bytes,
                   SUM(total_space_available - free_space)*1048576  AS used_space_bytes,
                   SUM(free_space)*1048576 AS available_space_bytes
            FROM [sysutility_ucp_core].[volumes_internal] 
            WHERE processing_time = @processing_time_current
            GROUP BY GROUPING SETS ((),
                                    (virtual_server_name),
                                    (virtual_server_name, volume_device_id)
                                   )
         SET @row_count = @@ROWCOUNT;
         SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
         RAISERROR ('Insert into [space_utilization_internal]: %d rows, %d ms', 0, 1, @row_count, @task_elapsed_ms);

        --         
        -- State changes
        --      
        UPDATE [msdb].[dbo].[sysutility_ucp_processing_state_internal] 
          SET latest_processing_time = @processing_time_current 

        -- Update the snapshot partitions table
        -- Push down the previous snapshot partition values and 
        -- store the current max snapshot in the latest (top) record           
        UPDATE [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] SET latest_consistent_snapshot_id = (SELECT TOP 1 latest_consistent_snapshot_id FROM [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] WHERE time_id = 1)  WHERE time_id = 2
        UPDATE [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] SET latest_consistent_snapshot_id = (SELECT TOP 1 latest_consistent_snapshot_id FROM [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] WHERE time_id = 0)  WHERE time_id = 1
        UPDATE [msdb].[dbo].[sysutility_ucp_snapshot_partitions_internal] SET latest_consistent_snapshot_id = @max_snapshot_id WHERE time_id = 0

        -- As we have inserted chunk of data in the cache tables, the stats on these tables
        -- get stale there by leading to performance degradation of the health state queries
        -- Force update the stats on these tables so that the QO is able to generate a more 
        -- realisitc query execution plan
        SET @task_start_time = GETUTCDATE();
        UPDATE STATISTICS [msdb].[dbo].[sysutility_ucp_processing_state_internal];

        -- Update stats on all dimension and measure cache tables
        DECLARE @schema sysname
        DECLARE @name sysname
        DECLARE @query NVARCHAR(MAX)
 
        DECLARE cache_tables CURSOR FOR
        SELECT object_schema, [object_name]
        FROM sysutility_ucp_misc.utility_objects_internal
        WHERE utility_object_type IN ('DIMENSION', 'MEASURE');
 
        OPEN cache_tables;
        FETCH NEXT FROM cache_tables INTO @schema, @name;
        WHILE (@@FETCH_STATUS <> -1)
        BEGIN
            SET @query = 'UPDATE STATISTICS ' + QUOTENAME (@schema) + '.' + QUOTENAME (@name); 
            EXEC (@query);
            FETCH NEXT FROM cache_tables INTO @schema, @name;
        END;
        CLOSE cache_tables;
        DEALLOCATE cache_tables;
 
        SET @task_elapsed_ms = DATEDIFF (ms, @task_start_time, GETUTCDATE());
        RAISERROR ('Update statistics: %d ms', 0, 1, @task_elapsed_ms);
END
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'STAGING', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_staging', 
   @level1type = 'PROCEDURE', @level1name = 'sp_copy_live_table_data_into_cache_tables';
GO


-----------------------------------------------------------------------------------------
-- Procedure sp_copy_cache_table_data_into_aggregate_tables
--   Aggregates the latest round of data from the "cache" tables into the "aggregate" tables
--   for the appropriate aggregation interval 
-----------------------------------------------------------------------------------------
IF OBJECT_ID ('sysutility_ucp_core.sp_copy_cache_table_data_into_aggregate_tables') IS NOT NULL
BEGIN
   RAISERROR('Dropping procedure sysutility_ucp_core.sp_copy_cache_table_data_into_aggregate_tables procedure', 0, 1) WITH NOWAIT;
   DROP PROCEDURE sysutility_ucp_core.sp_copy_cache_table_data_into_aggregate_tables;
END
GO
RAISERROR('Creating procedure sysutility_ucp_core.sp_copy_cache_table_data_into_aggregate_tables', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE sysutility_ucp_core.sp_copy_cache_table_data_into_aggregate_tables
    @aggregation_type INT,
    @endTime DATETIMEOFFSET(7)
AS
BEGIN       
    DECLARE @startTime DATETIMEOFFSET(7)
    DECLARE @lowerAggregationLevel [sysutility_ucp_core].AggregationType

    SELECT @lowerAggregationLevel = 0    -- compute from detail rows
    
    IF (@aggregation_type = 1)
       SELECT @startTime = DATEADD(hour, -1, @endTime)
    ELSE IF (@aggregation_type = 2)
       SELECT @startTime = DATEADD(day, -1, @endTime)
       -- todo (VSTS #345038) 
       -- Ideally, we would be using the hourly aggregation values to compute the 
       --   daily aggregation ("SELECT @lowerAggregationLevel = 1")
       --
    ELSE BEGIN
        -- todo. Raise an error instead
        RETURN(1)
    END
    
    INSERT INTO [sysutility_ucp_core].[cpu_utilization_internal] (
         aggregation_type, object_type, processing_time,
         physical_server_name, server_instance_name, database_name,
         percent_total_cpu_utilization)
       SELECT @aggregation_type, object_type, @endTime,
              physical_server_name, server_instance_name, database_name,
              AVG(percent_total_cpu_utilization)
       FROM [sysutility_ucp_core].[cpu_utilization_internal]
       WHERE (processing_time BETWEEN @startTime and @endTime) AND
             aggregation_type = @lowerAggregationLevel 
       GROUP BY object_type, physical_server_name, server_instance_name, database_name
    
    INSERT INTO [sysutility_ucp_core].[space_utilization_internal] (
         aggregation_type, object_type, processing_time,
         virtual_server_name, volume_device_id, server_instance_name, database_name, [filegroup_name], dbfile_name, 
         total_space_bytes, allocated_space_bytes, used_space_bytes, available_space_bytes)
       SELECT @aggregation_type, object_type, @endTime,
              virtual_server_name, volume_device_id, server_instance_name, database_name, [filegroup_name], dbfile_name,
              -- Is AVG the right aggregate to use - should this instead be LAST()
              AVG(total_space_bytes), AVG(allocated_space_bytes), AVG(used_space_bytes), AVG(available_space_bytes)
       FROM [sysutility_ucp_core].[space_utilization_internal]
       WHERE (processing_time BETWEEN @startTime and @endTime) AND
             aggregation_type = @lowerAggregationLevel
       GROUP BY object_type, virtual_server_name, volume_device_id, server_instance_name, database_name, [filegroup_name], dbfile_name 
           
END
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'CORE', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
   @level1type = 'PROCEDURE', @level1name = 'sp_copy_cache_table_data_into_aggregate_tables';
GO

-----------------------------------------------------------------------------------------
-- Procedure sp_purge_cache_tables
--   Deletes the data in Utility's MDW cache tables according to the data retention 
--   periods specified in msdb.dbo.sysutility_ucp_configuration_internal. 
-----------------------------------------------------------------------------------------
IF OBJECT_ID ('sysutility_ucp_core.sp_purge_cache_tables') IS NOT NULL
BEGIN
   RAISERROR('Dropping procedure sysutility_ucp_core.sp_purge_cache_tables procedure', 0, 1) WITH NOWAIT;
   DROP PROCEDURE sysutility_ucp_core.sp_purge_cache_tables;
END
GO
RAISERROR('Creating procedure sysutility_ucp_core.sp_purge_cache_tables', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE sysutility_ucp_core.sp_purge_cache_tables
AS
BEGIN
    DECLARE @rows_affected bigint;
    DECLARE @delete_batch_size varchar(30);

    SET @delete_batch_size = 500;
    SET @rows_affected = -1;

    DECLARE @days_to_retain_minute_data int;
    DECLARE @days_to_retain_hour_data int;
    DECLARE @days_to_retain_day_data int;

    SELECT @days_to_retain_minute_data = CONVERT (int,current_value) 
    FROM [msdb].[dbo].[sysutility_ucp_configuration_internal] 
    WHERE name = 'MdwRetentionLengthInDaysForMinutesHistory';

    SELECT @days_to_retain_hour_data = CONVERT (int,current_value) 
    FROM [msdb].[dbo].[sysutility_ucp_configuration_internal] 
    WHERE name = 'MdwRetentionLengthInDaysForHoursHistory';

    SELECT @days_to_retain_day_data = CONVERT (int,current_value) 
    FROM [msdb].[dbo].[sysutility_ucp_configuration_internal] 
    WHERE name = 'MdwRetentionLengthInDaysForDaysHistory';

    DECLARE @date_threshold_minute_data DATETIMEOFFSET(7) = DATEADD(day, -@days_to_retain_minute_data, SYSDATETIMEOFFSET());
    DECLARE @date_threshold_hour_data DATETIMEOFFSET(7) = DATEADD(day, -@days_to_retain_hour_data, SYSDATETIMEOFFSET());
    DECLARE @date_threshold_day_data DATETIMEOFFSET(7) = DATEADD(day, -@days_to_retain_day_data, SYSDATETIMEOFFSET());
       
    DECLARE @schema sysname
    DECLARE @name sysname
    DECLARE @query NVARCHAR(MAX)

    DECLARE dimensions_cursor CURSOR FOR
    SELECT object_schema, [object_name] 
    FROM sysutility_ucp_misc.utility_objects_internal
    WHERE utility_object_type = 'DIMENSION';

    -- Purge the dimension tables. 
    -- The number of rows that can be deleted from these tables can be very large.  If we deleted 
    -- all of these rows in a single delete statement, we would hold locks for an arbitrarily-long 
    -- time (and potentially escalate to table locks), causing long-duration blocking.  This could 
    -- also lead to transaction log growth, since log records after the oldest still-open transaction 
    -- can't be truncated.  To avoid these two problems, we delete rows in batches of 500 and loop 
    -- until we've deleted all rows that we no longer need. 
    OPEN dimensions_cursor;
    FETCH NEXT FROM dimensions_cursor INTO @schema, @name;
    WHILE (@@FETCH_STATUS <> -1)
    BEGIN
        SET @rows_affected = -1;
        WHILE (@rows_affected != 0)
        BEGIN
            -- We use dynamic SQL here because the table name is variable, but this also has the benefit of 
            -- providing the optimizer with the final value for @delete_batch_size and @date_threshold. 
            SET @query = 'DELETE TOP (' + @delete_batch_size + ') FROM ' + QUOTENAME(@schema) + '.' + QUOTENAME(@name) +
                         ' WHERE processing_time < @date_threshold';
            EXEC sp_executesql @query, N'@date_threshold datetimeoffset(7)', @date_threshold = @date_threshold_minute_data;
            SET @rows_affected = @@ROWCOUNT;
        END;

        FETCH NEXT FROM dimensions_cursor INTO @schema, @name;
    END;
    CLOSE dimensions_cursor;
    DEALLOCATE dimensions_cursor;

    DECLARE measures_cursor CURSOR FOR
    SELECT object_schema, [object_name] 
    FROM sysutility_ucp_misc.utility_objects_internal
    WHERE utility_object_type = 'MEASURE';

    -- Delete "per-minute" (15 minute) data from measure tables
    OPEN measures_cursor;
    FETCH NEXT FROM measures_cursor INTO @schema, @name;
    WHILE (@@FETCH_STATUS <> -1)
    BEGIN
        SET @rows_affected = -1;
        WHILE (@rows_affected != 0)
        BEGIN
            SET @query = 'DELETE TOP (' + @delete_batch_size + ') FROM ' + QUOTENAME(@schema) + '.' + QUOTENAME(@name) +
                         ' WHERE processing_time < @date_threshold AND aggregation_type = 0';
            EXEC sp_executesql @query, N'@date_threshold datetimeoffset(7)', @date_threshold = @date_threshold_minute_data;
            SET @rows_affected = @@ROWCOUNT;
        END;            
        
        FETCH NEXT FROM measures_cursor INTO @schema, @name;
    END;
    CLOSE measures_cursor;
    
    -- Delete "per-hour" data from our measure-tables 
    OPEN measures_cursor;
    FETCH NEXT FROM measures_cursor INTO @schema, @name;
    WHILE (@@FETCH_STATUS <> -1)
    BEGIN
        SET @rows_affected = -1;
        WHILE (@rows_affected != 0)
        BEGIN
            SET @query = 'DELETE TOP (' + @delete_batch_size + ') FROM ' + QUOTENAME(@schema) + '.' + QUOTENAME(@name) +
                         ' WHERE processing_time < @date_threshold AND aggregation_type = 1';
            EXEC sp_executesql @query, N'@date_threshold datetimeoffset(7)', @date_threshold = @date_threshold_hour_data;
            SET @rows_affected = @@ROWCOUNT;
        END;    
        
        FETCH NEXT FROM measures_cursor INTO @schema, @name;
    END;
    CLOSE measures_cursor;
    
    -- Delete "per-day" data from measure tables
    OPEN measures_cursor;
    FETCH NEXT FROM measures_cursor INTO @schema, @name;
    WHILE (@@FETCH_STATUS <> -1)
    BEGIN
        SET @rows_affected = -1;
        WHILE (@rows_affected != 0)
        BEGIN
            SET @query = 'DELETE TOP (' + @delete_batch_size + ') FROM ' + QUOTENAME(@schema) + '.' + QUOTENAME(@name) +
                         ' WHERE processing_time < @date_threshold AND aggregation_type = 2';
            EXEC sp_executesql @query, N'@date_threshold datetimeoffset(7)', @date_threshold = @date_threshold_day_data;
            SET @rows_affected = @@ROWCOUNT;
        END;
        
        FETCH NEXT FROM measures_cursor INTO @schema, @name;
    END;
    CLOSE measures_cursor;
    DEALLOCATE measures_cursor;
    
END;
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'CORE', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
   @level1type = 'PROCEDURE', @level1name = 'sp_purge_cache_tables';
GO


-----------------------------------------------------------------------------------------
-- Procedure sp_initialize_mdw_internal
--   Performs runtime schema modifications that must be deferred until Create UCP.  
--   Executed by sp_sysutility_ucp_initialize_mdw in msdb.  
--   
--   Note that this proc calls xp_qv, which requires that the 'Agent XPs' sp_configure 
--   value be enabled. During upgrade, this setting will need to be manually enabled, 
--   since upgrade scripts are executed while Agent is stopped. 
-----------------------------------------------------------------------------------------
IF OBJECT_ID ('sysutility_ucp_core.sp_initialize_mdw_internal') IS NOT NULL
BEGIN
   RAISERROR('Dropping procedure sysutility_ucp_core.sp_initialize_mdw_internal procedure', 0, 1) WITH NOWAIT;
   DROP PROCEDURE sysutility_ucp_core.sp_initialize_mdw_internal;
END
GO
RAISERROR('Creating procedure sysutility_ucp_core.sp_initialize_mdw_internal', 0, 1) WITH NOWAIT;
GO
CREATE PROCEDURE sysutility_ucp_core.sp_initialize_mdw_internal
AS
BEGIN
   IF (msdb.dbo.fn_sysutility_ucp_get_edition_is_ucp_capable_internal() = 1)
   BEGIN
      RAISERROR ('Instance is able to be used as a Utility Control Point.', 0, 1) WITH NOWAIT;
   END
   ELSE BEGIN
      DECLARE @edition nvarchar(128);
      SELECT @edition = CONVERT(nvarchar(128), SERVERPROPERTY('Edition'));
      RAISERROR(37004, -1, -1, @edition);
      RETURN(1);
   END;
   
   -- The Utility schema uses two Enterprise-only engine features: compression and partitioning.  
   -- Utility is an Enterprise-only feature, but we share instmdw.sql and the MDW database with other 
   -- features that are not Enterprise only.  Therefore we can't include the following things as part 
   -- of the CREATE TABLE statements because instmdw.sql would fail when run on a non-Enterprise SKU. 
   -- To work around this, we defer this part of the UCP schema creation until Create UCP is run. 
   IF (3 = CONVERT (int, SERVERPROPERTY('EngineEdition'))) -- Enterprise/Enterprise Eval/Developer/Data Center
   BEGIN
      -- Enable data compression on tables that benefit from it the most
      RAISERROR ('Enabling compression on MDW tables', 0, 1) WITH NOWAIT;
      ALTER TABLE [snapshots].[sysutility_ucp_smo_properties_internal] REBUILD WITH (DATA_COMPRESSION = PAGE);
      ALTER TABLE [sysutility_ucp_core].[smo_servers_internal] REBUILD WITH (DATA_COMPRESSION = PAGE);
      ALTER TABLE [sysutility_ucp_core].[databases_internal] REBUILD WITH (DATA_COMPRESSION = PAGE);
      ALTER TABLE [sysutility_ucp_core].[filegroups_internal] REBUILD WITH (DATA_COMPRESSION = PAGE);
      ALTER TABLE [sysutility_ucp_core].[datafiles_internal] REBUILD WITH (DATA_COMPRESSION = PAGE);
      ALTER TABLE [sysutility_ucp_core].[logfiles_internal] REBUILD WITH (DATA_COMPRESSION = PAGE);
      ALTER TABLE [sysutility_ucp_core].[cpu_utilization_internal] REBUILD WITH (DATA_COMPRESSION = PAGE);
      ALTER TABLE [sysutility_ucp_core].[space_utilization_internal] REBUILD WITH (DATA_COMPRESSION = PAGE);
     
      -- Partitioning (and compression) on measure tables

      -- Create a partitioning scheme based on aggregationType. In effect, we'd like
      -- each aggregation-level to get its own partition. Currently, we only support
      -- 3 aggregation levels.
      --
      -- FUTURE: It would be nice to support composite partitioning in the future. 
      --   1. We could further create a sub-partition for each object-type (computer etc.)
      --   2. If we could have sliding partitions, we could avoid deletes altogether, and 
      --      instead work with truncate/drop-partition style operations
      --
      IF NOT EXISTS (SELECT name FROM sys.partition_functions WHERE name = N'sysutility_ucp_aggregation_type_partition_function')
      BEGIN
         RAISERROR ('Creating partition function [sysutility_ucp_aggregation_type_partition_function]', 0, 1) WITH NOWAIT;
         -- Use dynamic SQL here (and in the next create stmt) b/c otherwise SQL will fail the creation of this 
         -- proc on Workgroup or Standard edition. 
         EXEC ('
            CREATE PARTITION FUNCTION [sysutility_ucp_aggregation_type_partition_function](TINYINT)
               AS RANGE LEFT 
               FOR VALUES(0, 1, 2)');
      END;

      IF NOT EXISTS (SELECT name FROM sys.partition_schemes WHERE name = N'sysutility_ucp_aggregation_type_partition_scheme')
      BEGIN
         RAISERROR ('Creating partition scheme [sysutility_ucp_aggregation_type_partition_scheme]', 0, 1) WITH NOWAIT;
         EXEC ('
            CREATE PARTITION SCHEME [sysutility_ucp_aggregation_type_partition_scheme] 
               AS PARTITION [sysutility_ucp_aggregation_type_partition_function] 
               ALL TO ([PRIMARY])');
      END;

      -- ALTER INDEX can't change partition scheme.  Instead we must drop and recreate the clustered PKs. 
      IF OBJECT_ID ('[sysutility_ucp_core].[pk_cpu_utilization_internal]') IS NOT NULL
      BEGIN
         RAISERROR ('Dropping primary key [sysutility_ucp_core].[cpu_utilization_internal].[pk_cpu_utilization_internal]', 0, 1) WITH NOWAIT;
         ALTER TABLE [sysutility_ucp_core].[cpu_utilization_internal] DROP CONSTRAINT [pk_cpu_utilization_internal]; 
      END;
      RAISERROR ('Creating partitioned primary key [sysutility_ucp_core].[cpu_utilization_internal].[pk_cpu_utilization_internal]', 0, 1) WITH NOWAIT;
      ALTER TABLE [sysutility_ucp_core].[cpu_utilization_internal] ADD 
         CONSTRAINT pk_cpu_utilization_internal 
            PRIMARY KEY CLUSTERED (aggregation_type, processing_time, object_type, physical_server_name, server_instance_name, database_name) 
            WITH (DATA_COMPRESSION = PAGE)
            ON [sysutility_ucp_aggregation_type_partition_scheme](aggregation_type);
      
      IF OBJECT_ID ('[sysutility_ucp_core].[pk_storage_utilization]') IS NOT NULL
      BEGIN
         RAISERROR ('Dropping primary key [sysutility_ucp_core].[space_utilization_internal].[pk_storage_utilization]', 0, 1) WITH NOWAIT;
         ALTER TABLE [sysutility_ucp_core].[space_utilization_internal] DROP CONSTRAINT [pk_storage_utilization]; 
      END;
      RAISERROR ('Creating partitioned primary key [sysutility_ucp_core].[space_utilization_internal].[pk_storage_utilization]', 0, 1) WITH NOWAIT;
      ALTER TABLE [sysutility_ucp_core].[space_utilization_internal] ADD 
         CONSTRAINT pk_storage_utilization 
            PRIMARY KEY CLUSTERED(
               aggregation_type,
               processing_time, 
               object_type, 
               virtual_server_name, 
               volume_device_id, 
               server_instance_name, 
               database_name, 
               [filegroup_name], 
               dbfile_name) 
            WITH (DATA_COMPRESSION = PAGE) 
            ON [sysutility_ucp_aggregation_type_partition_scheme](aggregation_type);
   END;
END;
GO

EXEC sp_addextendedproperty 
   @name = 'MS_UtilityObjectType', @value = 'CORE', 
   @level0type = 'SCHEMA', @level0name = 'sysutility_ucp_core', 
   @level1type = 'PROCEDURE', @level1name = 'sp_initialize_mdw_internal';
GO



BEGIN TRANSACTION

   -----------------------------------------------------------------------
   --  Remove the sysutility_get_views_data_into_cache_tables job if it is already existing.
   -----------------------------------------------------------------------
   DECLARE @job_id UNIQUEIDENTIFIER
   SELECT @job_id = job_id FROM msdb.dbo.sysjobs_view WHERE name = N'sysutility_get_views_data_into_cache_tables'
   IF  EXISTS (SELECT job_id FROM msdb.dbo.sysjobs_view WHERE name = N'sysutility_get_views_data_into_cache_tables')
      EXEC msdb.dbo.sp_delete_job @job_id=@job_id, @delete_unused_schedule=1
   
   DECLARE @ReturnCode INT

   -- Get the current logged in user name.
   DECLARE @CurrentLoggedIn nvarchar(128)
   Set @CurrentLoggedIn=SUSER_Name(0x1)

   SELECT @ReturnCode = 0
   -----------------------------------------------------------------------
   --  'sysutility_get_views_data_into_cache_tables job' has following steps:
   --  1. Insert [sysutility_ucp_core].[fn_get_cpu_utilizations](0) data into 
   --     [sysutility_ucp_core].[computer_cpu_utilizations_internal] cache table.
   --  2. Insert [snapshots].[dac_view] data into 
   --     [snapshots].[dac_table] 
   --  3. Insert storage live information into
   --     storage cache table
   --  4. Insert SMO live information into
   --     SMO cache table
   -----------------------------------------------------------------------
   IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1)
      BEGIN
         EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]'
         IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
      END

   DECLARE @mdwDBName NVARCHAR(256)
   SELECT  @mdwDBName = DB_NAME()
   DECLARE @jobId BINARY(16)
   EXEC @ReturnCode =  msdb.dbo.sp_add_job @job_name=N'sysutility_get_views_data_into_cache_tables', 
      @enabled=1, 
      @notify_level_eventlog=0, 
      @notify_level_email=0, 
      @notify_level_netsend=0, 
      @notify_level_page=0, 
      @delete_level=0, 
      @description=N'Gets all the views data into corresponding cache tables after every 15 minutes', 
      @category_name=N'[Uncategorized (Local)]', 
      @owner_login_name= @CurrentLoggedIn , @job_id = @jobId OUTPUT
   IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
   
      EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Insert latest data from live tables into cache tables',  
      @step_id=1, 
      @cmdexec_success_code=0, 
      @on_success_action=3, 
      @on_success_step_id=0, 
      @on_fail_action=2, 
      @on_fail_step_id=0, 
      @retry_attempts=0, 
      @retry_interval=0, 
      @os_run_priority=0, @subsystem=N'TSQL', 
      @command=N'EXEC [sysutility_ucp_staging].sp_copy_live_table_data_into_cache_tables', 
      @database_name = @mdwDBName,
      @flags=0
   
    DECLARE @execute_policy_script NVARCHAR(MAX)

    -- Powershell script to evaluate utility resource health policies
    -- Note 1: The following line uses SQL Agent tokens to set the server name
    -- ESCAPE_SQUOTE(SRVR) with a $ sign in front is a special token to SQL Agent
    -- When the job is run, SQL Agent will expand the string to the server name
    -- Use single quotes so that PS considers the string a literal and will not
    -- try to expand the $ reference and the script will not fail in a test environment
    -- Note 2: the current approach filters on policy name to identify the resource health policy
    -- this might turn out to be an issue if we start localizing the resource health policies.

    SET @execute_policy_script = N'$serverName = ''$(ESCAPE_SQUOTE(SRVR))'';
                                   $path = Convert-UrnToPath "PolicyStore[@Name=`''$serverName`'']";
                                   dir $path\Policies -FORCE | where { $_.IsSystemObject -eq $true -and $_.Name -like ''Utility*'' } | Invoke-PolicyEvaluation -TargetServerName $serverName;'
    
    EXEC msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Execute resource health policy evaluation job', 
            @step_id=2, 
            @cmdexec_success_code=0, 
            @on_success_action=3, 
            @on_success_step_id=0, 
            @on_fail_action=2, 
            @on_fail_step_id=0, 
            @retry_attempts=0, 
            @retry_interval=0, 
            @os_run_priority=0, @subsystem=N'PowerShell', 
            @command=@execute_policy_script, 
            @database_name=N'msdb', 
            @flags=0
               
    EXEC msdb.dbo.sp_add_jobstep @job_id=@jobId, @step_name=N'Compute resource health states', 
            @step_id=3, 
            @cmdexec_success_code=0, 
            @on_success_action=1, 
            @on_success_step_id=0, 
            @on_fail_action=2, 
            @on_fail_step_id=0, 
            @retry_attempts=0, 
            @retry_interval=0, 
            @os_run_priority=0, @subsystem=N'TSQL', 
            @command=N'EXEC msdb.dbo.sp_sysutility_ucp_calculate_health', 
            @database_name=N'msdb', 
            @flags=0

   EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId, @start_step_id = 1
   IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
   
   EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId, @name=N'OccursEvery15Minutes', 
      @enabled=1, 
      @freq_type=4, 
      @freq_interval=1, 
      @freq_subday_type=4, 
      @freq_subday_interval=15, 
      @freq_relative_interval=0, 
      @freq_recurrence_factor=0, 
      @schedule_uid=N'7c3e972b-6e4b-4c61-9061-715d8b9ba531'
   IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
   
   EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId, @server_name = N'(local)'
   IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
   
   -----------------------------------------------------------------------
   --  Remove the sysutility_get_cache_tables_data_into_aggregate_tables_hourly job if it is already existing.
   -----------------------------------------------------------------------
   DECLARE @job_id1 UNIQUEIDENTIFIER
   SELECT @job_id1 = job_id 
   FROM msdb.dbo.sysjobs_view 
   WHERE name = N'sysutility_get_cache_tables_data_into_aggregate_tables_hourly'

   IF EXISTS (SELECT job_id FROM msdb.dbo.sysjobs_view WHERE name = N'sysutility_get_cache_tables_data_into_aggregate_tables_hourly')
      EXEC msdb.dbo.sp_delete_job @job_id=@job_id1, @delete_unused_schedule=1
   
   -----------------------------------------------------------------------
   --  sysutility_get_cache_tables_data_into_aggregate_tables_hourly job has following steps:
   -- 1.  Aggregate current days data from [sysutility_ucp_core].[computer_cpu_utilizations_internal] 
   --     and put it into [sysutility_ucp_core].[aggregated_computer_cpu_utilizations_internal].
   -- 2.  Aggregate current days data from [sysutility_ucp_core].[dac_execution_statistics_internal] 
   --     and put it into [sysutility_ucp_core].[aggregated_dac_cpu_utilizations_internal].
   -- 3.  Aggregate current days data storage cache table into
   --     storage aggregation table
   -----------------------------------------------------------------------
   IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1)
   BEGIN
      EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]'
      IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
   END

   DECLARE @jobId1 BINARY(16)
   EXEC @ReturnCode =  msdb.dbo.sp_add_job @job_name=N'sysutility_get_cache_tables_data_into_aggregate_tables_hourly', 
         @enabled=1, 
         @notify_level_eventlog=0, 
         @notify_level_email=0, 
         @notify_level_netsend=0, 
         @notify_level_page=0, 
         @delete_level=0,
         @description=N'At every hour''s stroke, the data of the cache tables get aggregated and put into corresponding aggregate by hour tables.', 
         @category_name=N'[Uncategorized (Local)]', 
         @owner_login_name=@CurrentLoggedIn, @job_id = @jobId1 OUTPUT
   IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

   EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId1, @step_name=N'Aggregate current hours data from the cache tables into the server aggregation table', 
         @step_id=1, 
         @cmdexec_success_code=0, 
         @on_success_action=1, 
         @on_success_step_id=0, 
         @on_fail_action=2, 
         @on_fail_step_id=0, 
         @retry_attempts=0, 
         @retry_interval=0, 
         @os_run_priority=0, @subsystem=N'TSQL', 
         @command=N'DECLARE @now DATETIMEOFFSET(7); SELECT @now = SYSDATETIMEOFFSET(); EXEC [sysutility_ucp_core].sp_copy_cache_table_data_into_aggregate_tables @aggregation_type=1, @endTime=@now',
         @database_name = @mdwDBName,
         @flags=0
   IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
   
   
   EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId1, @name=N'OccursEveryOneHour', 
         @enabled=1, 
         @freq_type=4, 
         @freq_interval=1, 
         @freq_subday_type=8, 
         @freq_subday_interval=1, 
         @freq_relative_interval=0, 
         @freq_recurrence_factor=0,
         @active_start_time=100
   IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
   
   
   -----------------------------------------------------------------------
   --  Remove the sysutility_get_cache_tables_data_into_aggregate_tables_daily job if it is already existing.
   -----------------------------------------------------------------------
   SELECT @job_id1 = job_id 
   FROM msdb.dbo.sysjobs_view 
   WHERE name = N'sysutility_get_cache_tables_data_into_aggregate_tables_daily'

   IF EXISTS (SELECT job_id FROM msdb.dbo.sysjobs_view WHERE name = N'sysutility_get_cache_tables_data_into_aggregate_tables_daily')
      EXEC msdb.dbo.sp_delete_job @job_id=@job_id1, @delete_unused_schedule=1
   
   -----------------------------------------------------------------------
   --  sysutility_get_cache_tables_data_into_aggregate_tables_daily job has following steps:
   -- 1. Aggregate current days data from [sysutility_ucp_core].[computer_cpu_utilizations_internal] 
   --    and put it into [sysutility_ucp_core].[aggregated_computer_cpu_utilizations_internal].
   -- 2. Aggregate current days data from [sysutility_ucp_core].[dac_execution_statistics_internal] 
   --    and put it into [sysutility_ucp_core].[aggregated_dac_cpu_utilizations_internal].
   -- 3. Aggregate current days data from storage cache table into
   --    storage aggregation table
   -----------------------------------------------------------------------
   IF NOT EXISTS (SELECT name FROM msdb.dbo.syscategories WHERE name=N'[Uncategorized (Local)]' AND category_class=1)
   BEGIN
      EXEC @ReturnCode = msdb.dbo.sp_add_category @class=N'JOB', @type=N'LOCAL', @name=N'[Uncategorized (Local)]'
      IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
   END

   DECLARE @jobId2 BINARY(16)
   EXEC @ReturnCode =  msdb.dbo.sp_add_job @job_name=N'sysutility_get_cache_tables_data_into_aggregate_tables_daily', 
         @enabled=1, 
         @notify_level_eventlog=0, 
         @notify_level_email=0, 
         @notify_level_netsend=0, 
         @notify_level_page=0, 
         @delete_level=0, 
         @description=N'At every 12:01 AM stroke, the data of the cache tables get aggregated and put into corresponding aggregate by day tables.', 
         @category_name=N'[Uncategorized (Local)]', 
         @owner_login_name=@CurrentLoggedIn, @job_id = @jobId2 OUTPUT
   IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

   EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId2, @step_name=N'Aggregate current days data from the cache tables into the server aggregation table', 
         @step_id=1, 
         @cmdexec_success_code=0, 
         @on_success_action=3, 
         @on_success_step_id=0, 
         @on_fail_action=2, 
         @on_fail_step_id=0, 
         @retry_attempts=0, 
         @retry_interval=0, 
         @os_run_priority=0, @subsystem=N'TSQL', 
         @command=N'DECLARE @now DATETIME; SELECT @now = GETUTCDATE(); EXEC [sysutility_ucp_core].sp_copy_cache_table_data_into_aggregate_tables @aggregation_type=2, @endTime=@now',
         @database_name = @mdwDBName,
         @flags=0
   IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
   
   
   EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId2, @step_name=N'Purge cache table history data based on retention period', 
      @step_id=2, 
      @cmdexec_success_code=0, 
      @on_success_action=3, 
      @on_success_step_id=0, 
      @on_fail_action=2, 
      @on_fail_step_id=0, 
      @retry_attempts=0, 
      @retry_interval=0, 
      @os_run_priority=0, @subsystem=N'TSQL', 
      @command=N'EXEC [sysutility_ucp_core].[sp_purge_cache_tables];', 
      @database_name = @mdwDBName,
      @flags=0
   IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

   EXEC @ReturnCode = msdb.dbo.sp_add_jobstep @job_id=@jobId2, @step_name=N'Purge resource health policy evaluation history based on trailing window', 
      @step_id=3, 
      @cmdexec_success_code=0, 
      @on_success_action=1, 
      @on_success_step_id=0, 
      @on_fail_action=2, 
      @on_fail_step_id=0, 
      @retry_attempts=0, 
      @retry_interval=0, 
      @os_run_priority=0, @subsystem=N'TSQL', 
      @command=N'EXEC msdb.dbo.sp_sysutility_ucp_delete_policy_history', 
      @database_name=N'msdb', 
      @flags=0      
   IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

   EXEC @ReturnCode = msdb.dbo.sp_update_job @job_id = @jobId1, @start_step_id = 1
   IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

   EXEC @ReturnCode = msdb.dbo.sp_add_jobschedule @job_id=@jobId2, @name=N'OccursOnceADayAt12:01AM', 
         @enabled=1, 
         @freq_type=4, 
         @freq_interval=1, 
         @freq_subday_type=1, 
         @freq_subday_interval=0, 
         @freq_relative_interval=0, 
         @freq_recurrence_factor=0, 
         @active_start_date=20080218, 
         @active_end_date=99991231, 
         @active_start_time=100, 
         @active_end_time=235959, 
         @schedule_uid=N'acb4d2d5-d2ee-4d33-b82e-a296a41fc225'
   IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback

   EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId1, @server_name = N'(local)'
   IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback
   
   EXEC @ReturnCode = msdb.dbo.sp_add_jobserver @job_id = @jobId2, @server_name = N'(local)'
   IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback


-- Commit our transaction
COMMIT TRANSACTION

GOTO EndSave
QuitWithRollback:
IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION
EndSave:
GO

-- Commit our transaction
COMMIT TRANSACTION
GO


/**********************************************************************/
/* Setup Utility object permissions                                   */
/**********************************************************************/

/**********************************************************************
   Create the UtilityMDWWriter role. This role is granted to proxy 
   accounts that run the DC upload step on each MI.  It allows these 
   accounts to insert data into the Utility "live" tables in MDW. 
***********************************************************************/
RAISERROR ('Create UtilityMDWWriter role...', 0, 1) WITH NOWAIT;
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'UtilityMDWWriter' AND type = 'R'))
BEGIN
    CREATE ROLE [UtilityMDWWriter]
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'UtilityMDWWriter' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [UtilityMDWWriter]
        CREATE ROLE [UtilityMDWWriter]
    END
END
GO

-- UtilityMDWWriter is a member of the mdw_writer role; this will give it 
-- INSERT, EXEC, and SELECT permissions on anything in the [snapshots] schema. 
EXECUTE sp_addrolemember @rolename = 'mdw_writer' , 
                   @membername = 'UtilityMDWWriter' 
GO


/**********************************************************************
   Create the UtilityMDWCacheReader role. This sysutility_mdw role       
   corresponds to the UtilityCMRReader role in msdb.  A "CMRReader" 
   can select from objects in msdb that expose data in cache tables 
   in sysutility_mdw. The "CacheReader" role in sysutility_mdw secures 
   these cache tables/functions in the MDW db. A login should be a 
   member of both roles in order to run the UCP data processing job. 
***********************************************************************/
RAISERROR ('Create UtilityMDWCacheReader role...', 0, 1) WITH NOWAIT;
IF ( NOT EXISTS (SELECT * FROM sys.database_principals 
                    WHERE name = N'UtilityMDWCacheReader' AND type = 'R'))
BEGIN
    CREATE ROLE [UtilityMDWCacheReader];
END
ELSE -- if the role exists check to see if it has members
BEGIN
    IF NOT EXISTS (SELECT rm.member_principal_id
                FROM sys.database_principals dp 
                INNER JOIN sys.database_role_members rm ON rm.role_principal_id = dp.principal_id
                WHERE name = N'UtilityMDWCacheReader' AND type = 'R')
    BEGIN
        -- if the role has no members drop and recreate it
        DROP ROLE [UtilityMDWCacheReader];
        CREATE ROLE [UtilityMDWCacheReader];
    END;
END;
GO

-- Grant SELECT or EXECUTE on the cache functions/views at the boundary of msdb and 
-- sysutility_mdw.  These objects are directly referenced by objects in msdb.  
RAISERROR ('Granting permission on MDW cache objects to UtilityMDWCacheReader role', 0, 1) WITH NOWAIT;

-- Dimensions
GRANT SELECT  ON [sysutility_ucp_core].[latest_dacs]            TO [UtilityMDWCacheReader];
GRANT SELECT  ON [sysutility_ucp_core].[latest_computers]       TO [UtilityMDWCacheReader];
GRANT SELECT  ON [sysutility_ucp_core].[latest_volumes]         TO [UtilityMDWCacheReader];

GRANT SELECT  ON [sysutility_ucp_core].[latest_smo_servers]     TO [UtilityMDWCacheReader];
GRANT SELECT  ON [sysutility_ucp_core].[latest_databases]       TO [UtilityMDWCacheReader];
GRANT SELECT  ON [sysutility_ucp_core].[latest_filegroups]      TO [UtilityMDWCacheReader];
GRANT SELECT  ON [sysutility_ucp_core].[latest_datafiles]       TO [UtilityMDWCacheReader];
GRANT SELECT  ON [sysutility_ucp_core].[latest_logfiles]        TO [UtilityMDWCacheReader];

-- Measures
GRANT SELECT  ON [sysutility_ucp_core].[cpu_utilization]        TO [UtilityMDWCacheReader];
GRANT SELECT  ON [sysutility_ucp_core].[space_utilization]      TO [UtilityMDWCacheReader];

GO

-- Put the database back into multi user mode
RAISERROR('', 0, 1)  WITH NOWAIT;
RAISERROR('Restoring database to multi user mode', 0, 1)  WITH NOWAIT;
DECLARE @dbname sysname
SET @dbname = QUOTENAME(DB_NAME())

DECLARE @sql_db_multi_mode nvarchar(256)
SET @sql_db_multi_mode = 'ALTER DATABASE ' + @dbname +
                                    ' SET MULTI_USER WITH ROLLBACK IMMEDIATE'
EXEC sp_executesql @sql_db_multi_mode

-- check if sync auto stats was on
IF (EXISTS (SELECT * FROM #tmp_auto_mode))  -- if yes, turn it back on
BEGIN
    RAISERROR('', 0, 1)  WITH NOWAIT;
    RAISERROR('Re-enabling asynchronous auto statistics ...', 0, 1)  WITH NOWAIT;
    DECLARE @sql_async_autostat_on nvarchar(256)
    SET @sql_async_autostat_on = 'ALTER DATABASE ' + @dbname +
                                        ' SET AUTO_UPDATE_STATISTICS_ASYNC ON'
    EXEC sp_executesql @sql_async_autostat_on
END
DROP TABLE #tmp_auto_mode

RAISERROR('', 0, 1)  WITH NOWAIT;
RAISERROR('----------------------------------', 0, 1)  WITH NOWAIT;
RAISERROR('Execution of INSTMDW.SQL complete', 0, 1)  WITH NOWAIT;
RAISERROR('----------------------------------', 0, 1)  WITH NOWAIT;
GO


/**********************************************************************/
/* POST_UPGRADE_UCP_CMDW.SQL                                          */
/*                                                                    */
/*
** Copyright (c) Microsoft Corporation
** All Rights Reserved.
*/
/**********************************************************************/


CREATE PROCEDURE #sp_enable_component     
   @comp_name     sysname, 
   @advopt_old_value    INT OUT, 
   @comp_old_value   INT OUT 
AS
   BEGIN
   SELECT @advopt_old_value=cast(value_in_use as int) from sys.configurations where name = 'show advanced options';
   SELECT @comp_old_value=cast(value_in_use as int) from sys.configurations where name = @comp_name; 
   EXEC sp_configure 'show advanced options',1;
   RECONFIGURE WITH OVERRIDE;
   EXEC sp_configure @comp_name, 1; 
   RECONFIGURE WITH OVERRIDE;
   END
GO

CREATE PROCEDURE #sp_restore_component_state 
   @comp_name     sysname, 
   @advopt_old_value    INT, 
   @comp_old_value   INT 
AS
   BEGIN
   EXEC sp_configure @comp_name, @comp_old_value; 
   RECONFIGURE WITH OVERRIDE;
   EXEC sp_configure 'show advanced options',@advopt_old_value;
   RECONFIGURE WITH OVERRIDE;
   END
GO


-- The sp_sysutility_ucp_initialize_mdw proc needs agent XP's to be enabled to check SQL edition
DECLARE @advopt_old_value int
DECLARE @comp_old_value int
EXEC #sp_enable_component 'Agent XPs', @advopt_old_value out, @comp_old_value out

-- Get the MDW database name
DECLARE @mdw_name SYSNAME
SELECT @mdw_name=CAST(current_value as SYSNAME) FROM [msdb].[dbo].[sysutility_ucp_configuration_internal] WHERE name = 'MdwDatabaseName'

-- Now run the SProc sp_sysutility_ucp_initialize_mdw which re-creates the msdb synonyms to point to the MDW objects
EXEC [msdb].[dbo].[sp_sysutility_ucp_initialize_mdw] @mdw_database_name=@mdw_name

-- Restore Agent XPs to previous state
EXECUTE #sp_restore_component_state 'Agent XPs', @advopt_old_value, @comp_old_value
GO


PRINT '----------------------------------------'
PRINT 'Execution of POST_UPGRADE_UCP_CMDW.SQL complete'
PRINT '----------------------------------------'

GO

PRINT '------------------------------------------------------'
PRINT 'Starting execution of SSIS_DISCOVERY.SQL'
PRINT '------------------------------------------------------'
GO

DECLARE @EMPTY_SCRIPT_LEVEL INT
DECLARE @DENALI_SCRIPT_LEVEL INT
DECLARE @ID_ISSERVER_UPGRADE INT

SET @EMPTY_SCRIPT_LEVEL = 0
SET @DENALI_SCRIPT_LEVEL = 500
SET @ID_ISSERVER_UPGRADE = 17

DECLARE @run_script BIT
SET @run_script=1

DECLARE @ssis_database_name SYSNAME
SET @ssis_database_name = N'SSISDB'

-- Check whether SSISDB exists
IF(DB_ID(@ssis_database_name) IS NULL)
BEGIN
    SET @run_script=0
    RAISERROR('Database SSISDB does not exist in current SQL Server instance', 0, 1) WITH NOWAIT;
END

-- Check whether SSISDB is online
IF @run_script <> 0
BEGIN
	DECLARE @state_online SYSNAME
	SET @state_online = 'ONLINE'
	SELECT @state_online = UPPER(@state_online COLLATE SQL_Latin1_General_CP1_CI_AS)

	IF NOT EXISTS (SELECT state_desc FROM master.sys.databases WHERE name = @ssis_database_name AND
										 UPPER(state_desc COLLATE SQL_Latin1_General_CP1_CI_AS) LIKE @state_online)
	BEGIN
		SET @run_script=0    
		RAISERROR ('WARNING! The database SSISDB is not ONLINE. So skipping execution of ISServer_upgrade.sql on it. Please run the script manually after the upgrade.', 0, 1) WITH NOWAIT;
	END

	IF @run_script <> 0
	BEGIN
		IF OBJECT_ID (N'SSISDB.internal.catalog_properties', N'U') IS NULL
		BEGIN
			SET @run_script=0
			RAISERROR('Database SSISDB is missing the catalog properties table. The database may be corrupted, or it is not an SSIS Catalog.', 0, 1) WITH NOWAIT;
		END
	END

	IF @run_script <> 0
	BEGIN
		IF EXISTS (SELECT hadr.database_state FROM master.sys.dm_hadr_database_replica_states AS hadr 
				JOIN master.sys.databases AS dbs 
				ON hadr.database_id = dbs.database_id 
				WHERE dbs.name = @ssis_database_name)
		BEGIN
			SET @run_script=0
			RAISERROR('WARNING! The database SSISDB is in alwayson group. So skipping execution of ISServer_upgrade.sql on it. Please run the script manually after the upgrade.', 0, 1) WITH NOWAIT;
		END
	END

	-- Check version of SSISDB, we would upgrade the early version of SSISDB
	-- To support build-to-build upgrade, we would run upgrade if schema_version is 6. We need to change to property_value < 6 after RTM released.
	IF @run_script <> 0
	BEGIN
		IF (NOT EXISTS (SELECT property_value FROM SSISDB.internal.catalog_properties 
				WHERE property_name = 'SCHEMA_VERSION' AND property_value <= '6'))
		BEGIN
			SET @run_script=0
			RAISERROR('This version of ISServer_upgrade.sql should only be executed against earlier version of SSISDB.', 0, 1) WITH NOWAIT
		END
	END
END
-- set the script level by default to denali
EXEC sys.sp_dbscriptlevel 'master', @ID_ISSERVER_UPGRADE, @DENALI_SCRIPT_LEVEL

IF  @run_script = 0
BEGIN
	PRINT 'Database SSISDB could not be upgraded successfully.'
END
ELSE
BEGIN
	PRINT '---Enabling the ISSERVER_UPGRADE script!---'
	EXEC sys.sp_dbscriptlevel 'master', @ID_ISSERVER_UPGRADE, @EMPTY_SCRIPT_LEVEL
END


PRINT '------------------------------------------------------'
PRINT 'Execution of SSIS_DISCOVERY.SQL completed'
PRINT '------------------------------------------------------'
GO

PRINT '---------------------------------------------'
PRINT 'Starting execution of ISServer_upgrade.SQL'
PRINT '---------------------------------------------'

--disable this script for now.
-- We need find a better way to patch T-SQL code
-- to make sure everything are synced.
-- And avoid this file grows bigger and bigger.
-- RETURN

USE [master]


DECLARE @ssis_database_name SYSNAME
SET @ssis_database_name = N'SSISDB'

-- Check if the SQL version is 15.xx
IF (@@microsoftversion / 0x01000000) < 15
BEGIN
	RAISERROR('This version of ISServer_upgrade.sql should only be executed against 15.0 or later servers.', 20, 127) WITH LOG 
	RETURN
END

-- Take the database to single mode during the installation
RAISERROR('', 0, 1)  WITH NOWAIT;
RAISERROR('Taking SSISDB to single user mode', 0, 1)  WITH NOWAIT;
ALTER DATABASE [SSISDB] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO

-- Set SSISDB compatibility level to at least 130 to support JSON
IF (EXISTS (SELECT compatibility_level FROM sys.databases WHERE name = N'SSISDB' AND 130 > compatibility_level))
BEGIN
ALTER DATABASE [SSISDB] SET COMPATIBILITY_LEVEL = 130
END
GO

USE [SSISDB]
GO

-- insert Server_customized_logging_level row if not exist
IF NOT EXISTS (SELECT * FROM [internal].[catalog_properties] WHERE property_name = 'SERVER_CUSTOMIZED_LOGGING_LEVEL')
BEGIN
INSERT INTO [internal].[catalog_properties] (
                [property_name],  
                [property_value] 
                )
            VALUES (
                'SERVER_CUSTOMIZED_LOGGING_LEVEL',
                N''           
                ) 
END
GO

-- Create user defined type if not exist
IF NOT EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = 'Package_Table_Type' AND SCHEMA_ID('catalog') = schema_id)
BEGIN
CREATE TYPE [catalog].[Package_Table_Type] AS TABLE
( 
    [name] [nvarchar](260) NOT NULL,
    [package_data] [varbinary](max) NULL
)
	GRANT EXECUTE ON TYPE::[catalog].[Package_Table_Type] TO [PUBLIC]
END
GO

IF NOT EXISTS (SELECT * FROM [internal].[catalog_properties] WHERE property_name = 'DEFAULT_EXECUTION_MODE')
BEGIN
INSERT INTO [internal].[catalog_properties] (
                [property_name],  
                [property_value] 
                )
            VALUES (
                'DEFAULT_EXECUTION_MODE',
                '0'           
                ) 
END
GO

IF DATABASE_PRINCIPAL_ID('ssis_failover_monitoring_agent') IS NULL
BEGIN
	CREATE ROLE [ssis_failover_monitoring_agent] AUTHORIZATION [dbo]
END

GO

IF DATABASE_PRINCIPAL_ID('ssis_logreader') IS NULL
BEGIN
	CREATE ROLE [ssis_logreader] AUTHORIZATION [dbo]
END

GO

IF DATABASE_PRINCIPAL_ID('ssis_cluster_executor') IS NULL
BEGIN
	CREATE ROLE [ssis_cluster_executor] AUTHORIZATION [dbo]
END

GO

IF DATABASE_PRINCIPAL_ID('ssis_cluster_worker') IS NULL
BEGIN
	CREATE ROLE [ssis_cluster_worker] AUTHORIZATION [dbo]
END

GO
-- ===============================================
-- Table [internal].[alwayson_support_state]
-- state: 1 -> alwayson primary node
--        2 -> alwayson secondary node
-- If the table already exist, we need to truncate this table. 
-- Because we need to remove SSISDB from availability group to finish upgrade.
-- The corresponding supported state table is out of date.
-- ===============================================
IF OBJECT_ID('[internal].[alwayson_support_state]', 'U') IS NULL
BEGIN
	CREATE TABLE [internal].[alwayson_support_state]
	(
		[server_name] [nvarchar](256) PRIMARY KEY, 
		[state] tinyint NOT NULL
	)
END
ELSE
BEGIN
    TRUNCATE TABLE [internal].[alwayson_support_state]
END
GO

IF OBJECT_ID('[internal].[operation_messages_scaleout]', 'U') IS NULL
BEGIN
CREATE TABLE [internal].[operation_messages_scaleout]
(
    [operation_id] [bigint] NOT NULL,
    [message_time] [datetimeoffset](7) NOT NULL,
    [message_type] [smallint] NOT NULL,
    [message_source_type] [smallint] NULL,
    [message] [nvarchar](MAX) NULL,
    [extended_info_id] [bigint] NULL,
	[event_message_guid] uniqueidentifier NOT NULL
)
CREATE NONCLUSTERED INDEX [IX_OperationMessagesScaleout_event_message_guid]
ON [internal].[operation_messages_scaleout] 
(
    [event_message_guid] ASC
)
CREATE NONCLUSTERED INDEX [IX_OperationMessagesScaleout_Operation_id]
ON [internal].[operation_messages_scaleout] 
(
    [operation_id] ASC
)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
	SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, 
	ONLINE = OFF, ALLOW_ROW_LOCKS = ON, 
	ALLOW_PAGE_LOCKS = ON) 
ON [PRIMARY] 
END
GO

IF OBJECT_ID('[internal].[event_messages_scaleout]', 'U') IS NULL
BEGIN
CREATE TABLE [internal].[event_messages_scaleout]
(
    [operation_id] [bigint] NOT NULL,
    [execution_path] [nvarchar](MAX) NULL,                      
    [package_name] [nvarchar](260) NULL,
    [package_location_type] [nvarchar](128) NULL,
    [package_path_full] [nvarchar](4000) NULL,
    [event_name] [nvarchar](1024) NULL,
    [message_source_name] [nvarchar](4000) NULL,
    [message_source_id] [nvarchar](38) NULL,
    [subcomponent_name] [nvarchar](4000) NULL,
    [package_path] [nvarchar](MAX) NULL,
    [threadID] [int] NOT NULL,
    [message_code] [int] NULL,
	[event_message_guid] uniqueidentifier NULL
)
CREATE NONCLUSTERED INDEX [IX_EventMessagesScaleout_Guid]
ON [internal].[event_messages_scaleout] 
(
    [event_message_guid] 
)
CREATE NONCLUSTERED INDEX [IX_EventMessagesScaleout_Operation_id]
ON [internal].[event_messages_scaleout] 
(
    [operation_id] ASC
)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
	SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
	ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) 
ON [PRIMARY]
END
GO

IF OBJECT_ID('[internal].[event_message_context_scaleout]', 'U') IS NULL
BEGIN
CREATE TABLE [internal].[event_message_context_scaleout]
(
    [context_id] [bigint] IDENTITY(1,1) NOT NULL,
    [operation_id] [bigint] NOT NULL,
    [context_depth] [int] NULL,
    [package_path] [nvarchar](MAX) NULL,
    [context_type] [smallint] NULL,
    [context_source_name] [nvarchar](4000) NULL,
    [context_source_id] [nvarchar](38) NULL,
    [property_name] [nvarchar](4000) NULL,
    [property_value] sql_variant NULL,
    [event_message_guid] uniqueidentifier NOT NULL
)
CREATE NONCLUSTERED INDEX [IX_EventMessageContextScaleout_Operation_id]
ON [internal].[event_message_context_scaleout] 
(
    [operation_id] ASC,
    [event_message_guid] ASC
)
INCLUDE ([context_id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
	SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, 
	ONLINE = OFF, ALLOW_ROW_LOCKS = ON, 
	ALLOW_PAGE_LOCKS = ON) 
ON [PRIMARY]
END
GO

-- ===============================================
-- Table [internal].[customized_logging_levels]
-- ===============================================
IF OBJECT_ID('[internal].[customized_logging_levels]', 'U') IS NULL
BEGIN
    CREATE TABLE [internal].[customized_logging_levels]
    (
        [level_id] [bigint] IDENTITY(1,1) NOT NULL
        CONSTRAINT [PK_Levels] PRIMARY KEY,
        [name] [sysname],
        [description] [nvarchar](1024) NULL,
        [profile_value] [bigint] NOT NULL,
        [events_value] [bigint] NOT NULL,
        [created_by_sid] [varbinary](85) NOT NULL,
        [created_by_name] [nvarchar](128) NOT NULL,
        [created_time] [datetimeoffset](7) NOT NULL,
    )
    CREATE UNIQUE NONCLUSTERED INDEX [Unique_level_name]
        ON [internal].[customized_logging_levels] 
    (
        [name] ASC
    )
END

GO

-- ===============================================
-- Type [internal].[machine_property_list_type]
-- ===============================================
IF NOT EXISTS (SELECT * FROM [SSISDB].[sys].[table_types] WHERE name = 'machine_property_list_type')
BEGIN
	CREATE TYPE [internal].[machine_property_list_type] AS TABLE 
	(
		[PropertyName]  NVARCHAR(MAX)  NOT NULL,
		[PropertyValue]	NVARCHAR(MAX)
	)
END 
GO

-- ===============================================
-- Type [internal].[machine_perf_counter_list_type]
-- ===============================================
IF NOT EXISTS (SELECT * FROM [SSISDB].[sys].[table_types] WHERE name = 'machine_perf_counter_list_type')
BEGIN
	CREATE TYPE [internal].[machine_perf_counter_list_type] AS TABLE 
	(
		[PerfCounterName] NVARCHAR(MAX)	NOT NULL,
		[PerfCounterValue]	FLOAT NOT NULL,
		[TimeStamp]		DATETIMEOFFSET NOT NULL
	)
END 
GO

-- ===============================================
-- Table [internal].[jobs]
-- ===============================================
IF OBJECT_ID('[internal].[jobs]', 'U') IS NULL
BEGIN
    CREATE TABLE [internal].[jobs] 
	(
	    [JobId]						UNIQUEIDENTIFIER NOT NULL
        CONSTRAINT [PK_Jobs] PRIMARY KEY,	
		[IsCancelled]				BIT NOT NULL DEFAULT 0,
		[CreatedTime]				DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET(),
		[JobType]					INT NOT NULL,
		[InputData]					NVARCHAR(MAX) NULL DEFAULT NULL,
		[Creator]					NVARCHAR(256) NOT NULL,
		[Priority]					INT NOT NULL DEFAULT 0, 
		[LastUpdatedTime]			DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET()
	)
	CREATE NONCLUSTERED INDEX [index_job_iscancelled] ON [internal].[jobs] 
	(
		[IsCancelled] ASC
	)
END

GO

-- ===============================================
-- Table [internal].[tasks]
-- ===============================================
IF OBJECT_ID('[internal].[tasks]', 'U') IS NULL
BEGIN
    CREATE TABLE [internal].[tasks] 
	(
		[TaskId]					UNIQUEIDENTIFIER PRIMARY KEY NOT NULL,
	    [JobId]						UNIQUEIDENTIFIER NULL
        CONSTRAINT [FK_tasks_JobId]
            FOREIGN KEY REFERENCES [internal].[jobs] (JobId)
            ON DELETE CASCADE,
		[TaskType]					INT NOT NULL,
	    [InputData]					NVARCHAR(max) NULL DEFAULT NULL,
		[MaxExecutedCount]			INT NOT NULL DEFAULT 1,
		[ExecutedCount]				INT NOT NULL DEFAULT 0,
		[Status]					INT NOT NULL,
	    [IsCritical]				BIT NULL DEFAULT	0,
		[Priority]					INT NOT NULL DEFAULT 0, 
		[ReadyForDispatchTime]		DATETIMEOFFSET NULL ,
		[LastUpdatedTime]			DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET(), 
		[CreatedTime]				DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET(),
		[WorkerAgentId]				UNIQUEIDENTIFIER NULL,
	    [ExpiredTime]				DATETIMEOFFSET NULL DEFAULT NULL,
	    [CreateWorkerAgentId]		UNIQUEIDENTIFIER NULL DEFAULT NULL,	
	    [IsCancelled]				BIT NULL DEFAULT 0,
	    [LastPickupTime]			DATETIMEOFFSET	NULL,
		
		CONSTRAINT [Chk_MaxExecutedCount_GreaterOrEqualOne] CHECK
		(
			[MaxExecutedCount] > 0
		)
	)
	CREATE NONCLUSTERED INDEX [index_tasks_ReadyForDispatchTime] ON [internal].[tasks] 
	(
		[ReadyForDispatchTime] ASC
	)
END
GO

-- ===============================================
-- Table [internal].[jobs_history]
-- ===============================================
IF OBJECT_ID('[internal].[jobs_history]', 'U') IS NULL
BEGIN
	CREATE TABLE [internal].[jobs_history] 
	(
	    [JobId]						UNIQUEIDENTIFIER NOT NULL
        CONSTRAINT [PK_Jobs_History] PRIMARY KEY,	
		[IsCancelled]				BIT NOT NULL DEFAULT 0,
		[CreatedTime]				DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET(),
		[JobType]					INT NOT NULL,
		[InputData]					NVARCHAR(MAX) NULL DEFAULT NULL,
		[Creator]					NVARCHAR(256) NOT NULL,
		[Priority]					INT NOT NULL DEFAULT 0, 
		[LastUpdatedTime]			DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET()
	)
END
GO

-- ===============================================
-- Table [internal].[tasks_history]
-- ===============================================
IF OBJECT_ID('[internal].[tasks_history]', 'U') IS NULL
BEGIN
	CREATE TABLE [internal].[tasks_history] 
	(
		[TaskId]					UNIQUEIDENTIFIER PRIMARY KEY NOT NULL,
	    [JobId]						UNIQUEIDENTIFIER NULL 
        CONSTRAINT [FK_tasks_history_JobId]
            FOREIGN KEY REFERENCES [internal].[jobs_history] (JobId)
            ON DELETE CASCADE,
		[TaskType]					INT NOT NULL,
	    [InputData]					NVARCHAR(max) NULL DEFAULT NULL,
		[MaxExecutedCount]			INT NOT NULL DEFAULT 1,
		[ExecutedCount]				INT NOT NULL DEFAULT 0,
		[Status]					INT NOT NULL,
	    [IsCritical]				BIT NULL DEFAULT	0,
		[Priority]					INT NOT NULL DEFAULT 0, 
		[ReadyForDispatchTime]		DATETIMEOFFSET NULL ,
		[LastUpdatedTime]			DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET(), 
		[CreatedTime]				DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET(),
		[WorkerAgentId]				UNIQUEIDENTIFIER NULL,
	    [ExpiredTime]				DATETIMEOFFSET NULL DEFAULT NULL,
	    [CreateWorkerAgentId]		UNIQUEIDENTIFIER NULL DEFAULT NULL,	
	    [IsCancelled]				BIT NULL DEFAULT 0,
	    [LastPickupTime]			DATETIMEOFFSET	NULL
	)
	CREATE NONCLUSTERED INDEX [index_tasks_history_jobid] ON [internal].[tasks_history]
	(
		[JobId] ASC
	)
END
GO

-- ===============================================
-- Table [internal].[worker_agents]
-- ===============================================
IF OBJECT_ID('[internal].[worker_agents]', 'U') IS NULL
BEGIN
	CREATE TABLE [internal].[worker_agents]
	(
	    [WorkerAgentId] 				UNIQUEIDENTIFIER NOT NULL
        CONSTRAINT [PK_Worker_Agents] PRIMARY KEY, 
		[DisplayName] 					NVARCHAR(256) NULL,
		[Description]					NVARCHAR(MAX) NULL,
		[MachineName]					NVARCHAR(256) NULL,
		[Tags]							NVARCHAR(MAX) NULL,
		[UserAccount]					NVARCHAR(256) NULL,
		[IsEnabled] 					BIT NOT NULL DEFAULT 0, 
		[LastOnlineTime] 				DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET()
	)
END
GO

-- ===============================================
-- Table [internal].[worker_agent_perfcounter]
-- ===============================================
IF OBJECT_ID('[internal].[worker_agent_perfcounter]', 'U') IS NULL
BEGIN
	CREATE TABLE [internal].[worker_agent_perfcounter]
	(
	    [WorkerAgentId] 				UNIQUEIDENTIFIER NOT NULL
        CONSTRAINT [FK_worker_agent_perfcounter_WorkerAgentId]
            FOREIGN KEY REFERENCES [internal].[worker_agents] (WorkerAgentId)
            ON DELETE CASCADE, 
		[PerfCounterName]				NVARCHAR(MAX)	NOT NULL,
		[PerfCounterValue] 				FLOAT	NOT NULL,  
        [TimeStamp] 					DATETIMEOFFSET NOT NULL  
	)
	CREATE NONCLUSTERED INDEX [index_worker_agent_perfcounter_TimeStampAndWorkerAgentId] ON [internal].[worker_agent_perfcounter]
	(
		[WorkerAgentId],
		[TimeStamp]
	)
END
GO

-- ===============================================
-- Table [internal].[machine_properties]
-- ===============================================
IF OBJECT_ID('[internal].[machine_properties]', 'U') IS NULL
BEGIN
	CREATE TABLE [internal].[machine_properties]
	(
	    [WorkerAgentId] 				UNIQUEIDENTIFIER NOT NULL
        CONSTRAINT [FK_machine_properties_WorkerAgentId]
             FOREIGN KEY REFERENCES [internal].[worker_agents] (WorkerAgentId)
            ON DELETE CASCADE, 
		[PropertyName]					NVARCHAR(256)	NOT NULL,
	    [PropertyValue] 				NVARCHAR(MAX) NULL,
		CONSTRAINT [PK_machine_properties] PRIMARY KEY ([WorkerAgentId],[PropertyName])
	)
END
GO

-- ===============================================
-- Table [internal].[job_worker_agents]
-- ===============================================
IF OBJECT_ID('[internal].[job_worker_agents]', 'U') IS NULL
BEGIN
	CREATE TABLE [internal].[job_worker_agents]
	(
		[WorkerAgentId]		UNIQUEIDENTIFIER NOT NULL, 
	    [JobId]				UNIQUEIDENTIFIER NOT NULL
        CONSTRAINT [FK_job_worker_agents_JobId]
            FOREIGN KEY REFERENCES [internal].[jobs] (JobId)
            ON DELETE CASCADE, 
        CONSTRAINT [PK_job_worker_agents] PRIMARY KEY ([JobId],[WorkerAgentId])
	)
END
GO

-- ===============================================
-- Table [internal].[job_worker_agents_history]
-- ===============================================
IF OBJECT_ID('[internal].[job_worker_agents_history]', 'U') IS NULL
BEGIN
	CREATE TABLE [internal].[job_worker_agents_history]
	(
		[WorkerAgentId]			UNIQUEIDENTIFIER NOT NULL, 
	    [JobId]					UNIQUEIDENTIFIER NOT NULL
        CONSTRAINT [FK_job_worker_agents_history_JobId]
            FOREIGN KEY REFERENCES [internal].[jobs_history] (JobId)
            ON DELETE CASCADE, 
        CONSTRAINT [PK_job_worker_agents_history] PRIMARY KEY ([JobId],[WorkerAgentId])
	)
END
ELSE
BEGIN
	ALTER TABLE [internal].[job_worker_agents_history]
	DROP CONSTRAINT [FK_job_worker_agents_history_JobId]
	ALTER TABLE [internal].[job_worker_agents_history]
	ADD CONSTRAINT [FK_job_worker_agents_history_JobId] FOREIGN KEY (JobId)
		 REFERENCES [internal].[jobs_history] (JobId)
		ON DELETE CASCADE
END
GO

-- ===============================================
-- Table [internal].[master_properties]
-- ===============================================
IF OBJECT_ID('[internal].[master_properties]', 'U') IS NULL
BEGIN
	CREATE TABLE [internal].[master_properties]
	(
		[property_name] [nvarchar](256) NOT NULL
		CONSTRAINT [PK_Master_Property] PRIMARY KEY,
		[property_value] [nvarchar](max) NOT NULL
	)

	INSERT INTO [internal].[master_properties] VALUES 
		('MACHINE_NAME', ''),
		('MACHINE_IP', ''),
		('MASTER_SERVICE_PORT', ''),
		('SSLCERT_THUMBPRINT',''),
		('LAST_ONLINE_TIME', ''),
		('MASTER_ADDRESS', ''),	
		('MASTER_HEARTBEAT_INTERVALINMS', ''),
		('CLUSTER_LOGDB_SERVER', ''),
		('CLUSTER_LOGDB_CONNECTIONSTRING', '')
END
GO

-- ===============================================
-- Table [internal].[executions]
-- ===============================================
IF NOT EXISTS
(
    SELECT 1 FROM [sys].[columns] WHERE [name] = N'job_id' AND [object_id] = OBJECT_ID(N'[internal].[executions]')
)
BEGIN
	ALTER TABLE [internal].[executions]	ADD [job_id] UNIQUEIDENTIFIER NULL
END
GO

-- ===============================================
-- Table [internal].[validations]
-- ===============================================
IF NOT EXISTS
(
    SELECT 1 FROM [sys].[columns] WHERE [name] = N'job_id' AND [object_id] = OBJECT_ID(N'[internal].[validations]')
)
BEGIN
	ALTER TABLE [internal].[validations] ADD [job_id] UNIQUEIDENTIFIER NULL
END
GO

-- ===============================================
-- Table [internal].[operations]
-- ===============================================
IF NOT EXISTS
(
    SELECT 1 FROM [sys].[columns] WHERE [name] = N'worker_agent_id' AND [object_id] = OBJECT_ID(N'[internal].[operations]')
)
BEGIN
	ALTER TABLE [internal].[operations]	ADD [worker_agent_id] UNIQUEIDENTIFIER NULL
END
GO

-- ===============================================
-- Table [internal].[event_messages]
-- ===============================================
IF NOT EXISTS
(
    SELECT 1 FROM [sys].[columns] WHERE [name] = N'event_message_guid' AND [object_id] = OBJECT_ID(N'[internal].[event_messages]')
)
BEGIN
	ALTER TABLE [internal].[event_messages]	ADD [event_message_guid] UNIQUEIDENTIFIER NULL
END
GO


-- ===============================================
-- Table [internal].[operation_messages]
-- ===============================================
IF NOT EXISTS
(
    SELECT 1 FROM [sys].[columns] WHERE [name] = N'event_message_guid' AND [object_id] = OBJECT_ID(N'[internal].[operation_messages]')
)
BEGIN
	ALTER TABLE [internal].[operation_messages]	ADD [event_message_guid] UNIQUEIDENTIFIER NULL
END
GO


-- ===============================================
-- Table [internal].[event_message_context]
-- ===============================================
IF NOT EXISTS
(
    SELECT 1 FROM [sys].[columns] WHERE [name] = N'event_message_guid' AND [object_id] = OBJECT_ID(N'[internal].[event_message_context]')
)
BEGIN
	ALTER TABLE [internal].[event_message_context]	ADD [event_message_guid] UNIQUEIDENTIFIER NULL
END
GO

-- ===============================================
-- Table [internal].[operations]
-- ===============================================
IF NOT EXISTS
(
    SELECT 1 FROM [sys].[columns] WHERE [name] = N'executed_count' AND [object_id] = OBJECT_ID(N'[internal].[operations]')
)
BEGIN
	ALTER TABLE [internal].[operations]	ADD [executed_count] int DEFAULT(0) NULL
END
GO

IF NOT EXISTS
(
    SELECT 1 FROM [sys].[columns] WHERE [name] = N'CandidateWorkerAgentId' AND [object_id] = OBJECT_ID(N'[internal].[tasks]')
)
BEGIN
	ALTER TABLE [internal].[tasks] ADD [CandidateWorkerAgentId] UNIQUEIDENTIFIER NULL
END
GO

IF NOT EXISTS
(
    SELECT 1 FROM [sys].[columns] WHERE [name] = N'CandidateWorkerAgentId' AND [object_id] = OBJECT_ID(N'[internal].[tasks_history]')
)
BEGIN
	ALTER TABLE [internal].[tasks_history] ADD [CandidateWorkerAgentId] UNIQUEIDENTIFIER NULL
END
GO

-- ===============================================
-- Add master service login for scale out
-- ===============================================
IF EXISTS (SELECT * FROM [internal].[master_properties] WHERE [property_name]='IS_SCALEOUT_ENABLED' AND [property_value]='TRUE')
BEGIN
    DECLARE @masterAccountName AS SYSNAME
    DECLARE @strExecCmd NVARCHAR (MAX)
    SET @masterAccountName = N'NT Service\SSISScaleOutMaster150'
    IF (SUSER_SID(@masterAccountName) IS NOT NULL AND NOT EXISTS (SELECT * FROM sys.server_principals WHERE NAME = @masterAccountName))
    BEGIN
	    PRINT N'Adding login for ' + @masterAccountName

	    SET @strExecCmd = N'CREATE LOGIN ' + QUOTENAME(@masterAccountName) + ' FROM WINDOWS'
	    EXEC (@strExecCmd)

	    IF EXISTS(SELECT * FROM sys.database_principals where name = 'SSISScaleOutMasterUser150')
		    DROP USER SSISScaleOutMasterUser150

	    SET @strExecCmd = N'CREATE USER SSISScaleOutMasterUser150 for login '  + QUOTENAME(@masterAccountName) 
	    EXEC (@strExecCmd)

	    SET @strExecCmd = N'sp_addrolemember ''ssis_admin'', ''SSISScaleOutMasterUser150'''
	    EXEC (@strExecCmd)
    END
END

--##BEGIN:SSIS_Upgrade
IF EXISTS (SELECT * FROM SSISDB.sys.indexes WHERE name = 'IX_internal_object_parameters_inc'  AND object_id = OBJECT_ID('[internal].[object_parameters]'))
BEGIN
DROP INDEX IX_internal_object_parameters_inc ON [internal].[object_parameters]
END
GO

CREATE NONCLUSTERED INDEX [IX_internal_object_parameters_inc] ON [internal].[object_parameters]
(
	[project_id] ASC ,
	[project_version_lsn] ASC
)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, 
	DROP_EXISTING = OFF , ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) 
ON [PRIMARY]
GO
IF EXISTS (SELECT * FROM SSISDB.sys.indexes WHERE name = 'IX_Operations_object_id'  AND object_id = OBJECT_ID('[internal].[operations]'))
BEGIN
DROP INDEX IX_Operations_object_id ON [internal].[operations]
END
GO

CREATE NONCLUSTERED INDEX [IX_Operations_object_id]
ON [internal].[operations] 
(
    [object_id] ASC
) 
GO
IF EXISTS (SELECT * FROM SSISDB.sys.indexes WHERE name = 'IX_OperationMessages_Operation_id'  AND object_id = OBJECT_ID('[internal].[operation_messages]'))
BEGIN
DROP INDEX IX_OperationMessages_Operation_id ON [internal].[operation_messages]
END
GO

CREATE NONCLUSTERED INDEX [IX_OperationMessages_Operation_id]
ON [internal].[operation_messages] 
(
    [operation_id] ASC
)
INCLUDE ([operation_message_id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
	SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, 
	ONLINE = OFF, ALLOW_ROW_LOCKS = ON, 
	ALLOW_PAGE_LOCKS = ON) 
ON [PRIMARY] 

GO
IF EXISTS (SELECT * FROM SSISDB.sys.indexes WHERE name = 'IX_ExecutableStatistics_Execution_id'  AND object_id = OBJECT_ID('[internal].[executable_statistics]'))
BEGIN
DROP INDEX IX_ExecutableStatistics_Execution_id ON [internal].[executable_statistics]
END
GO

CREATE NONCLUSTERED INDEX [IX_ExecutableStatistics_Execution_id]
ON [internal].[executable_statistics] 
(
    [execution_id] ASC
)
INCLUDE ([statistics_id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
	SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, 
	ONLINE = OFF, ALLOW_ROW_LOCKS = ON, 
	ALLOW_PAGE_LOCKS = ON) 
ON [PRIMARY]
GO
IF EXISTS (SELECT * FROM SSISDB.sys.indexes WHERE name = 'Unique_sequence_id'  AND object_id = OBJECT_ID('[internal].[execution_component_phases]'))
BEGIN
DROP INDEX Unique_sequence_id ON [internal].[execution_component_phases]
END
GO

CREATE NONCLUSTERED INDEX [Unique_sequence_id] 
ON [internal].[execution_component_phases] 
(
	[execution_id] ASC,
	[sequence_id] ASC
)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
	SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, 
	ONLINE = OFF, ALLOW_ROW_LOCKS = ON, 
	ALLOW_PAGE_LOCKS = ON) 
ON [PRIMARY]
GO
IF EXISTS (SELECT * FROM SSISDB.sys.indexes WHERE name = 'IX_EventMessages_Operation_id'  AND object_id = OBJECT_ID('[internal].[event_messages]'))
BEGIN
DROP INDEX IX_EventMessages_Operation_id ON [internal].[event_messages]
END
GO

CREATE NONCLUSTERED INDEX [IX_EventMessages_Operation_id]
ON [internal].[event_messages] 
(
    [operation_id] ASC
)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
	SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
	ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) 
ON [PRIMARY]
GO
IF EXISTS (SELECT * FROM SSISDB.sys.indexes WHERE name = 'IX_EventMessageContext_Operation_id'  AND object_id = OBJECT_ID('[internal].[event_message_context]'))
BEGIN
DROP INDEX IX_EventMessageContext_Operation_id ON [internal].[event_message_context]
END
GO

CREATE NONCLUSTERED INDEX [IX_EventMessageContext_Operation_id]
ON [internal].[event_message_context] 
(
    [operation_id] ASC,
    [event_message_id] ASC
)
INCLUDE ([context_id])
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
	SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, 
	ONLINE = OFF, ALLOW_ROW_LOCKS = ON, 
	ALLOW_PAGE_LOCKS = ON) 
ON [PRIMARY]
GO
IF EXISTS (SELECT * FROM SSISDB.sys.indexes WHERE name = 'index_job_iscancelled'  AND object_id = OBJECT_ID('[internal].[jobs]'))
BEGIN
DROP INDEX index_job_iscancelled ON [internal].[jobs]
END
GO

CREATE NONCLUSTERED INDEX [index_job_iscancelled] ON [internal].[jobs] 
(
	[IsCancelled] ASC
)
GO
IF EXISTS (SELECT * FROM SSISDB.sys.indexes WHERE name = 'index_tasks_ReadyForDispatchTime'  AND object_id = OBJECT_ID('[internal].[tasks]'))
BEGIN
DROP INDEX index_tasks_ReadyForDispatchTime ON [internal].[tasks]
END
GO

Declare @server_sku varchar(255)
DECLARE @cmd NVARCHAR(500)
select @server_sku = CONVERT(varchar, SERVERPROPERTY ('edition'))
SET @cmd = 'CREATE NONCLUSTERED INDEX [index_tasks_ReadyForDispatchTime] ' +
	'ON [internal].[tasks] ([ReadyForDispatchTime] ASC) ' +
	'INCLUDE ([CreatedTime], [CreateWorkerAgentId], [ExecutedCount], [ExpiredTime], [InputData], [IsCritical], [JobId], [LastUpdatedTime], [MaxExecutedCount], [Priority], [Status], [TaskType]) ' +
	'WITH (ONLINE = '
IF @server_sku like '%Enterprise%' or @server_sku like '%Developer%'
	SET @cmd = @cmd + 'ON)'
ELSE
	SET @cmd = @cmd + 'OFF)'
PRINT @cmd
EXEC (@cmd)
GO
IF EXISTS (SELECT * FROM SSISDB.sys.indexes WHERE name = 'index_tasks_Status'  AND object_id = OBJECT_ID('[internal].[tasks]'))
BEGIN
DROP INDEX index_tasks_Status ON [internal].[tasks]
END
GO

Declare @server_sku varchar(255)
DECLARE @cmd NVARCHAR(500)
select @server_sku = CONVERT(varchar, SERVERPROPERTY ('edition'))
SET @cmd = 'CREATE NONCLUSTERED INDEX [index_tasks_Status] ' +
	'ON [internal].[tasks] ([Status]) ' +
	'INCLUDE ([ExecutedCount], [ExpiredTime], [IsCancelled], [MaxExecutedCount], [WorkerAgentId]) ' +
	'WITH (ONLINE = '
IF @server_sku like '%Enterprise%' or @server_sku like '%Developer%'
	SET @cmd = @cmd + 'ON)'
ELSE
	SET @cmd = @cmd + 'OFF)'
PRINT @cmd
EXEC (@cmd)
GO
IF EXISTS (SELECT * FROM SSISDB.sys.indexes WHERE name = 'index_tasks_history_jobid'  AND object_id = OBJECT_ID('[internal].[tasks_history]'))
BEGIN
DROP INDEX index_tasks_history_jobid ON [internal].[tasks_history]
END
GO

CREATE NONCLUSTERED INDEX [index_tasks_history_jobid] ON [internal].[tasks_history]
(
	[JobId] ASC
)
GO
IF EXISTS (SELECT * FROM SSISDB.sys.indexes WHERE name = 'index_worker_agent_perfcounter_TimeStampAndWorkerAgentId'  AND object_id = OBJECT_ID('[internal].[worker_agent_perfcounter]'))
BEGIN
DROP INDEX index_worker_agent_perfcounter_TimeStampAndWorkerAgentId ON [internal].[worker_agent_perfcounter]
END
GO

CREATE NONCLUSTERED INDEX [index_worker_agent_perfcounter_TimeStampAndWorkerAgentId] ON [internal].[worker_agent_perfcounter]
(
	[WorkerAgentId],
	[TimeStamp]
)
GO
IF (OBJECT_ID('[catalog].[worker_agents]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[worker_agents]
END
GO

CREATE VIEW [catalog].[worker_agents]
AS
	SELECT [WorkerAgentId], [IsEnabled], [DisplayName], [Description], [MachineName], [Tags], [UserAccount], [LastOnlineTime]
	FROM [internal].[worker_agents]
	Where ((IS_MEMBER('ssis_admin') = 1) OR (IS_SRVROLEMEMBER('sysadmin') = 1) OR (IS_MEMBER('ssis_cluster_executor') = 1))
GO

GRANT SELECT ON [catalog].[worker_agents]  TO [PUBLIC]
GO
IF (OBJECT_ID('[catalog].[worker_agent_perfcounter]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[worker_agent_perfcounter]
END
GO

CREATE VIEW [catalog].[worker_agent_perfcounter]
AS
	SELECT [WorkerAgentId], [PerfCounterName], [PerfCounterValue], [TimeStamp]
	FROM [internal].[worker_agent_perfcounter]
	Where ((IS_MEMBER('ssis_admin') = 1) OR (IS_SRVROLEMEMBER('sysadmin') = 1) OR (IS_MEMBER('ssis_cluster_executor') = 1))
GO

GRANT SELECT ON [catalog].[worker_agent_perfcounter]  TO [PUBLIC]
GO
IF (OBJECT_ID('[catalog].[master_properties]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[master_properties]
END
GO

CREATE VIEW [catalog].[master_properties]
AS
	SELECT [property_name], [property_value]
	FROM [internal].[master_properties]
	WHERE [property_name] <> 'CLUSTER_LOGDB_CONNECTIONSTRING' and [property_name] <> 'EXECUTION_PASSWORD'
GO

GRANT SELECT ON [catalog].[master_properties]  TO [PUBLIC]
GO
IF (OBJECT_ID('[internal].[get_principal_id_by_sid]', 'FN') IS NOT NULL)
BEGIN
DROP FUNCTION  [internal].[get_principal_id_by_sid]
END
GO

CREATE FUNCTION [internal].[get_principal_id_by_sid]
(
        @sid        [internal].[adt_sid] 
)
RETURNS INT
AS
BEGIN
	DECLARE @Result INT
	select @Result = [principal_id] from [sys].[database_principals] where [sid]=@sid;
	RETURN @Result
END

GO
IF (OBJECT_ID('[catalog].[alwayson_replicas]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[alwayson_replicas]
END
GO

CREATE VIEW [catalog].[alwayson_replicas] 
AS
SELECT		[server_name],
			[state]
FROM		[internal].[alwayson_support_state]
GO
IF (OBJECT_ID('[catalog].[customized_logging_levels]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[customized_logging_levels]
END
GO

CREATE VIEW [catalog].[customized_logging_levels]
AS
SELECT     [level_id],
           [name],
           [description],
           [profile_value],
           [events_value],
           [created_by_sid],
           [created_by_name],
           [created_time]
FROM       [internal].[customized_logging_levels]
GO
IF (OBJECT_ID('[catalog].[projects]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[projects]
END
GO

CREATE VIEW [catalog].[projects]
AS
SELECT     proj.[project_id],
           [internal].[folders].[folder_id],
           proj.[name],
           proj.[description],
           proj.[project_format_version], 
           proj.[deployed_by_sid], 
           proj.[deployed_by_name], 
           proj.[last_deployed_time], 
           proj.[created_time],
           proj.[object_version_lsn],  
           proj.[validation_status], 
           proj.[last_validation_time]
                             
FROM       [internal].[object_versions] ver INNER JOIN
           [internal].[projects] proj ON (ver.[object_id] = proj.[project_id]
           AND ver.[object_version_lsn] = proj.[object_version_lsn]) INNER JOIN
           [internal].[folders] ON proj.[folder_id] = [internal].[folders].[folder_id]
WHERE      (ver.[object_status] = 'C') 
           AND (ver.[object_type]= 20) 
           AND (
                  proj.[project_id] in (SELECT [id] FROM [internal].[current_user_readable_projects])
                  OR (IS_MEMBER('ssis_admin') = 1)
                  OR (IS_SRVROLEMEMBER('sysadmin') = 1)
                  OR (0 = 1)
                  OR (IS_MEMBER('ssis_logreader') = 1)
                )
GO
IF (OBJECT_ID('[catalog].[packages]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[packages]
END
GO

CREATE VIEW [catalog].[packages]
AS
SELECT     pkgs.[package_id], 
           pkgs.[name], 
           pkgs.[package_guid], 
           pkgs.[description],
           pkgs.[package_format_version], 
           pkgs.[version_major],  
           pkgs.[version_minor], 
           pkgs.[version_build], 
           pkgs.[version_comments], 
           pkgs.[version_guid], 
           pkgs.[project_id],
           pkgs.[entry_point],
           pkgs.[validation_status],
           pkgs.[last_validation_time]
FROM       [internal].[packages] pkgs INNER JOIN [internal].[projects] proj ON 
           (pkgs.[project_version_lsn] = proj.[object_version_lsn] 
           AND pkgs.[project_id] = proj.[project_id]) INNER JOIN
           [internal].[object_versions] vers ON ( vers.[object_type] =20 AND
           vers.[object_id] = proj.[project_id] 
           AND vers.[object_version_lsn] = proj.[object_version_lsn])
           
WHERE      pkgs.[project_id] in (SELECT [id] FROM [internal].[current_user_readable_projects])
           OR (IS_MEMBER('ssis_admin') = 1)
           OR (IS_SRVROLEMEMBER('sysadmin') = 1)
           OR (IS_MEMBER('ssis_logreader') = 1)
           OR (0 = 1)
GO
IF (OBJECT_ID('[catalog].[operations]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[operations]
END
GO

CREATE VIEW [catalog].[operations]
AS
SELECT     [operation_id], 
           [operation_type], 
           [created_time],
           [object_type],
           [object_id],
           [object_name],
           [status], 
           [start_time], 
           [end_time], 
           [caller_sid], 
           [caller_name], 
           [process_id],
           [stopped_by_sid],
           [stopped_by_name],
           [server_name],
           [machine_name],
           [operation_guid]
FROM       internal.[operations]
WHERE      [operation_id] in (SELECT [id] FROM [internal].[current_user_readable_operations])
           OR (IS_MEMBER('ssis_admin') = 1)
           OR (IS_SRVROLEMEMBER('sysadmin') = 1)
           OR (IS_MEMBER('ssis_logreader') = 1)
           OR (0 = 1)
GO
IF (OBJECT_ID('[catalog].[extended_operation_info]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[extended_operation_info]
END
GO

CREATE VIEW [catalog].[extended_operation_info]
AS
SELECT     [info_id], 
           [operation_id], 
           [object_name], 
           [object_type],
           [reference_id],
           [status], 
           [start_time], 
           [end_time]
FROM       [internal].[extended_operation_info]
WHERE      [operation_id] in (SELECT [id] FROM [internal].[current_user_readable_operations])
           OR (IS_MEMBER('ssis_admin') = 1)
           OR (IS_SRVROLEMEMBER('sysadmin') = 1)
           OR (IS_MEMBER('ssis_logreader') = 1)

GO
IF (OBJECT_ID('[catalog].[operation_messages]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[operation_messages]
END
GO

CREATE VIEW [catalog].[operation_messages]
AS
SELECT     [operation_message_id], 
           [operation_id], 
           [message_time],
           [message_type],  
           [message_source_type], 
           [message], 
           [extended_info_id]
FROM       [internal].[operation_messages] 
WHERE      [operation_id] in (SELECT [id] FROM [internal].[current_user_readable_operations])
           OR (IS_MEMBER('ssis_admin') = 1)
           OR (IS_SRVROLEMEMBER('sysadmin') = 1)
           OR (IS_MEMBER('ssis_logreader') = 1)
UNION ALL
SELECT     -1 as [operation_message_id], 
           [operation_id], 
           [message_time],
           [message_type],  
           [message_source_type], 
           [message], 
           [extended_info_id]
FROM       [internal].[operation_messages_scaleout] 
WHERE      [operation_id] in (SELECT [id] FROM [internal].[current_user_readable_operations])
           OR (IS_MEMBER('ssis_admin') = 1)
           OR (IS_SRVROLEMEMBER('sysadmin') = 1)
           OR (IS_MEMBER('ssis_logreader') = 1)
GO
IF (OBJECT_ID('[catalog].[event_messages]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[event_messages]
END
GO

CREATE VIEW [catalog].[event_messages]
AS
SELECT     opmsg.[operation_message_id] as [event_message_id],
           opmsg.[operation_id], 
           opmsg.[message_time],
           opmsg.[message_type],
           opmsg.[message_source_type],  
           opmsg.[message], 
           opmsg.[extended_info_id],
           eventmsg.[package_name],
           eventmsg.[event_name],
           
           message_source_name = 
                      CASE 
                        WHEN (opmsg.message_source_type = 10) THEN 'ISServerExec' 
                        WHEN (opmsg.message_source_type = 20) THEN 'Transact-SQL stored procedure'
                        ELSE eventmsg.message_source_name
                    END,
           eventmsg.[message_source_id],
           eventmsg.[subcomponent_name],
           eventmsg.[package_path],
           eventmsg.[execution_path],
           eventmsg.[threadID],
           eventmsg.[message_code],
           eventmsg.[event_message_guid]
FROM       [internal].[operation_messages] opmsg LEFT JOIN [internal].[event_messages] eventmsg
           ON opmsg.[operation_message_id] = eventmsg.[event_message_id]
WHERE      opmsg.[operation_id] in (SELECT [id] FROM [internal].[current_user_readable_operations])
           OR (IS_MEMBER('ssis_admin') = 1)
           OR (IS_SRVROLEMEMBER('sysadmin') = 1)
           OR (IS_MEMBER('ssis_logreader') = 1)
UNION ALL
    SELECT     NULL as [event_message_id],
               opmsg.[operation_id], 
               opmsg.[message_time],
               opmsg.[message_type],
               opmsg.[message_source_type],  
               opmsg.[message], 
               opmsg.[extended_info_id],
               eventmsg.[package_name],
               eventmsg.[event_name],
               
               message_source_name = 
                          CASE 
                            WHEN (opmsg.message_source_type = 10) THEN 'ISServerExec' 
                            WHEN (opmsg.message_source_type = 20) THEN 'Transact-SQL stored procedure'
                            ELSE eventmsg.message_source_name
                        END,
               eventmsg.[message_source_id],
               eventmsg.[subcomponent_name],
               eventmsg.[package_path],
               eventmsg.[execution_path],
               eventmsg.[threadID],
               eventmsg.[message_code],
               eventmsg.[event_message_guid]
    FROM       [internal].[operation_messages_scaleout] opmsg LEFT JOIN [internal].[event_messages_scaleout] eventmsg
               ON opmsg.[event_message_guid] = eventmsg.[event_message_guid]
    WHERE      opmsg.[operation_id] in (SELECT [id] FROM [internal].[current_user_readable_operations])
               OR (IS_MEMBER('ssis_admin') = 1)
               OR (IS_SRVROLEMEMBER('sysadmin') = 1)
               OR (IS_MEMBER('ssis_logreader') = 1)
GO
IF (OBJECT_ID('[catalog].[execution_data_statistics]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[execution_data_statistics]
END
GO

CREATE VIEW [catalog].[execution_data_statistics]
AS
SELECT    [data_stats_id],
          [execution_id],
          [package_name],
          [task_name],
          [dataflow_path_id_string],
          [dataflow_path_name],
          [source_component_name],
          [destination_component_name],
          [rows_sent],
          [created_time],
          [execution_path]
FROM      [internal].[execution_data_statistics]
WHERE     [execution_id] in (SELECT [id] FROM [internal].[current_user_readable_operations])
          OR (IS_MEMBER('ssis_admin') = 1)
          OR (IS_SRVROLEMEMBER('sysadmin') = 1)
          OR (IS_MEMBER('ssis_logreader') = 1)

GO
IF (OBJECT_ID('[catalog].[execution_component_phases]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[execution_component_phases]
END
GO

CREATE VIEW [catalog].[execution_component_phases]
AS
SELECT   startPhase.[phase_stats_id] as [phase_stats_id],
         startPhase.[execution_id] as [execution_id],
         startPhase.[package_name] as [package_name],
         startPhase.[task_name] as [task_name],
         startPhase.[subcomponent_name] as [subcomponent_name],
         startPhase.[phase] as [phase],
         startPhase.[phase_time] as [start_time],
         endPhase.[phase_time] as [end_time],
         startPhase.[execution_path] as [execution_path]
FROM     [internal].[execution_component_phases] startPhase LEFT JOIN [internal].[execution_component_phases] endPhase
         ON startPhase.[phase_stats_id] != endPhase.[phase_stats_id]
         AND startPhase.[execution_id] = endPhase.[execution_id]
         AND startPhase.[sequence_id] = endPhase.[sequence_id]
WHERE    startPhase.[is_start] = 'True' AND (endPhase.[is_start] = 'False' OR endPhase.[is_start] is null)
         AND (startPhase.[execution_id] in (SELECT [id] FROM [internal].[current_user_readable_operations])
         OR (IS_MEMBER('ssis_admin') = 1)
         OR (IS_SRVROLEMEMBER('sysadmin') = 1))
         OR (IS_MEMBER('ssis_logreader') = 1)

GO
IF (OBJECT_ID('[catalog].[execution_data_taps]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[execution_data_taps]
END
GO

CREATE VIEW [catalog].[execution_data_taps]
AS
SELECT    [data_tap_id],
          [execution_id],
          [package_path],
          [dataflow_path_id_string],
          [dataflow_task_guid],
          [max_rows],
          [filename]
FROM      [internal].[execution_data_taps]
WHERE     [execution_id] in (SELECT [id] FROM [internal].[current_user_readable_operations])
          OR (IS_MEMBER('ssis_admin') = 1)
          OR (IS_SRVROLEMEMBER('sysadmin') = 1)
          OR (IS_MEMBER('ssis_logreader') = 1)

GO
IF (OBJECT_ID('[catalog].[event_message_context]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[event_message_context]
END
GO

CREATE VIEW [catalog].[event_message_context]
AS
SELECT     [context_id],
           [event_message_id],
           [context_depth],
           [package_path],
           [context_type],
           [context_source_name],
           [context_source_id],
           [property_name],
           [property_value],
           [event_message_guid]
FROM       [internal].[event_message_context]
WHERE      [operation_id] in (SELECT [id] FROM [internal].[current_user_readable_operations])
           OR (IS_MEMBER('ssis_admin') = 1)
           OR (IS_SRVROLEMEMBER('sysadmin') = 1)
           OR (IS_MEMBER('ssis_logreader') = 1)
UNION ALL
SELECT     [context_id],
           NULL as [event_message_id],
           [context_depth],
           [package_path],
           [context_type],
           [context_source_name],
           [context_source_id],
           [property_name],
           [property_value],
           [event_message_guid]
FROM       [internal].[event_message_context_scaleout]
WHERE      [operation_id] in (SELECT [id] FROM [internal].[current_user_readable_operations])
           OR (IS_MEMBER('ssis_admin') = 1)
           OR (IS_SRVROLEMEMBER('sysadmin') = 1)
           OR (IS_MEMBER('ssis_logreader') = 1)

GO
IF (OBJECT_ID('[catalog].[executions]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[executions]
END
GO

CREATE VIEW [catalog].[executions]
AS
SELECT     execs.[execution_id], 
           execs.[folder_name], 
           execs.[project_name], 
           execs.[package_name],
           execs.[reference_id],
           execs.[reference_type], 
           execs.[environment_folder_name], 
           execs.[environment_name], 
           execs.[project_lsn], 
           execs.[executed_as_sid], 
           execs.[executed_as_name], 
           execs.[use32bitruntime],  
           opers.[operation_type], 
           opers.[created_time],  
           opers.[object_type], 
           opers.[object_id],
           opers.[status], 
           opers.[start_time], 
           opers.[end_time],  
           opers.[caller_sid], 
           opers.[caller_name], 
           opers.[process_id], 
           opers.[stopped_by_sid], 
           opers.[stopped_by_name],
           opers.[operation_guid] as [dump_id],
           opers.[server_name],
           opers.[machine_name],
		   opers.[worker_agent_id],
           ossysinfos.[total_physical_memory_kb],
           ossysinfos.[available_physical_memory_kb],
           ossysinfos.[total_page_file_kb],
           ossysinfos.[available_page_file_kb],
           ossysinfos.[cpu_count],
           opers.[executed_count]
FROM       [internal].[executions] execs INNER JOIN [internal].[operations] opers 
           ON execs.[execution_id]= opers.[operation_id]
           LEFT JOIN [internal].[operation_os_sys_info] ossysinfos
           ON ossysinfos.[operation_id]= execs.[execution_id]
WHERE      opers.[operation_id] in (SELECT id FROM [internal].[current_user_readable_operations])
           OR (IS_MEMBER('ssis_admin') = 1)
           OR (IS_SRVROLEMEMBER('sysadmin') = 1)
           OR (IS_MEMBER('ssis_logreader') = 1)

GO
IF (OBJECT_ID('[catalog].[executables]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[executables]
END
GO

CREATE VIEW [catalog].[executables]
AS
SELECT DISTINCT    
           execl.[executable_id], 
           execs.[execution_id], 
           execl.[executable_name], 
           execl.[executable_guid],
           execl.[package_name],
           execl.[package_path]
FROM       ([internal].[executions] execs INNER JOIN [internal].[executable_statistics] stat 
           ON execs.[execution_id] = stat.[execution_id]) INNER JOIN [internal].[executables] execl
           ON stat.[executable_id] = execl.[executable_id] 
WHERE      execs.[execution_id] in (SELECT id FROM [internal].[current_user_readable_operations])
           OR (IS_MEMBER('ssis_admin') = 1)
           OR (IS_SRVROLEMEMBER('sysadmin') = 1)
           OR (IS_MEMBER('ssis_logreader') = 1)

GO
IF (OBJECT_ID('[catalog].[executable_statistics]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[executable_statistics]
END
GO

CREATE VIEW [catalog].[executable_statistics]
AS
SELECT     [statistics_id], 
           [execution_id],
           [executable_id], 
           [execution_path], 
           [start_time],
           [end_time],
           [execution_duration], 
           [execution_result],
           [execution_value]
FROM       [internal].[executable_statistics]
WHERE      [execution_id] in (SELECT id FROM [internal].[current_user_readable_operations])
           OR (IS_MEMBER('ssis_admin') = 1)
           OR (IS_SRVROLEMEMBER('sysadmin') = 1)
           OR (IS_MEMBER('ssis_logreader') = 1)

GO
IF (OBJECT_ID('[catalog].[validations]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[validations]
END
GO

CREATE VIEW [catalog].[validations]
AS
SELECT     vals.[validation_id], 
           vals.[environment_scope], 
           vals.[validate_type],
           vals.[folder_name],
           vals.[project_name],         
           vals.[project_lsn],
           vals.[use32bitruntime],
           vals.[reference_id], 
           opers.[operation_type],
           opers.[object_name],  
           opers.[object_type], 
           opers.[object_id], 
           opers.[start_time], 
           opers.[end_time], 
           opers.[status], 
           opers.[caller_sid], 
           opers.[caller_name], 
           opers.[process_id],
           opers.[stopped_by_sid], 
           opers.[stopped_by_name],
           opers.[operation_guid] as [dump_id],
           opers.[server_name],
           opers.[machine_name]
FROM       [internal].[validations] vals INNER JOIN [internal].[operations] opers 
           ON vals.[validation_id] = opers.[operation_id]
WHERE      opers.[operation_id] in (SELECT [id] FROM [internal].[current_user_readable_operations])
           OR (IS_MEMBER('ssis_admin') = 1)
           OR (IS_SRVROLEMEMBER('sysadmin') = 1)
           OR (IS_MEMBER('ssis_logreader') = 1)
		   OR (0 = 1)
GO
IF (OBJECT_ID('[catalog].[execution_parameter_values]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[execution_parameter_values]
END
GO

CREATE VIEW [catalog].[execution_parameter_values]
AS
SELECT     [execution_parameter_id], 
           [execution_id],
           [object_type], 
           [parameter_data_type], 
           [parameter_name], 
           [parameter_value], 
           [sensitive],
           [required],
           [value_set], 
           [runtime_override]
FROM       [internal].[execution_parameter_values]
WHERE      [execution_id] in (SELECT [id] FROM [internal].[current_user_readable_operations])
           OR (IS_MEMBER('ssis_admin') = 1)
           OR (IS_SRVROLEMEMBER('sysadmin') = 1)
           OR (IS_MEMBER('ssis_logreader') = 1)

GO
IF (OBJECT_ID('[catalog].[execution_property_override_values]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[execution_property_override_values]
END
GO

CREATE VIEW [catalog].[execution_property_override_values]
AS
SELECT     [property_id], 
           [execution_id],
           [property_path], 
           [property_value], 
           [sensitive]
FROM       [internal].[execution_property_override_values]
WHERE      [execution_id] in (SELECT [id] FROM [internal].[current_user_readable_operations])
           OR (IS_MEMBER('ssis_admin') = 1)
           OR (IS_SRVROLEMEMBER('sysadmin') = 1)
           OR (IS_MEMBER('ssis_logreader') = 1)

GO
IF (OBJECT_ID('[catalog].[catalog_properties]', 'V') IS NOT NULL)
BEGIN
DROP VIEW  [catalog].[catalog_properties]
END
GO

CREATE VIEW [catalog].[catalog_properties]
AS
SELECT     [property_name], 
           [property_value]
FROM       [internal].[catalog_properties]
UNION
SELECT     [property_name], 
           [property_value]
FROM       [internal].[master_properties]
WHERE      [property_name] = 'IS_SCALEOUT_ENABLED'
GO

GRANT SELECT ON [catalog].[projects] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[packages] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[operations] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[catalog_properties] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[operation_messages] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[executions] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[execution_parameter_values] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[execution_property_override_values] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[validations] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[event_messages] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[event_message_context] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[executables] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[executable_statistics] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[execution_component_phases] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[execution_data_statistics] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[execution_data_taps] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[extended_operation_info] TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[alwayson_replicas]  TO [PUBLIC]
GO

GRANT SELECT ON [catalog].[customized_logging_levels] TO [PUBLIC]
GO
IF (OBJECT_ID('[internal].[check_permission]', 'FN') IS NOT NULL)
BEGIN
DROP FUNCTION  [internal].[check_permission]
END
GO

CREATE FUNCTION [internal].[check_permission]
(
    @object_type SMALLINT,
    @object_id BIGINT,
    @permission_type SMALLINT
)
RETURNS BIT
AS
BEGIN
    DECLARE @Result BIT
    
    IF (IS_MEMBER('ssis_admin') = 1) OR (IS_SRVROLEMEMBER('sysadmin') = 1) OR (0 = 1)
        RETURN 1
    
	IF (@object_type = 4 AND @permission_type = 1 AND IS_MEMBER('ssis_logreader') = 1)
		RETURN 1
		
    IF EXISTS 
    (
        SELECT [permission_type]
        FROM [catalog].[effective_object_permissions]
        WHERE 
            [object_type] = @object_type
        AND [object_id] = @object_id
        AND [permission_type] = @permission_type
    )
        SET @Result = 1
    ELSE
        SET @Result = 0
  
    RETURN @Result
END
GO
IF (OBJECT_ID('[internal].[check_is_role]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[check_is_role]
END
GO

CREATE PROCEDURE [internal].[check_is_role]
    @principal_id INTEGER,
    @is_role BIT OUTPUT
AS
BEGIN
    DECLARE @principal_type CHAR(1)
        
    SELECT @principal_type = [type] 
    FROM [sys].[database_principals] 
    WHERE [principal_id] = @principal_id

    IF @principal_type IS NULL
        RETURN 1
    
    IF @principal_type = 'R'
        SET @is_role = 1
    ELSE
        SET @is_role = 0
  
    RETURN 0
END
GO
IF (OBJECT_ID('[internal].[cancel_job]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[cancel_job]
END
GO

CREATE PROCEDURE [internal].[cancel_job]
	@JobId     UNIQUEIDENTIFIER

WITH EXECUTE AS 'AllSchemaOwner'
AS
BEGIN
	SET NOCOUNT ON
	SET XACT_ABORT ON
	
	
	IF @JobId IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@JobId')
		RETURN 1
	END
	
	BEGIN TRAN
	BEGIN TRY
		DECLARE @IsJobExist BIT = 0
		
		UPDATE [internal].[tasks] SET 
			[Status]=
				CASE 
					WHEN [Status] = 2 OR [Status] = 3 OR [Status]=5 THEN [Status]
					ELSE 6
				END,
			[ReadyForDispatchTime] = NULL,
			[LastUpdatedTime]=SYSDATETIMEOFFSET(),
			[IsCancelled] = 1
		WHERE [JobId] = @JobId 
	
		UPDATE [internal].[jobs] SET [LastUpdatedTime]=SYSDATETIMEOFFSET(), [IsCancelled] = 1, @IsJobExist = 1 WHERE [JobId] = @JobId
		
		IF @IsJobExist = 0
			BEGIN
				DECLARE @strJobId NVARCHAR(50)
				SET @strJobId = CONVERT(NVARCHAR(50), @JobId)
				RAISERROR(27242, 16, 1, @strJobId) WITH NOWAIT
			END
			
		COMMIT TRAN	
		RETURN 0
	END TRY
	BEGIN CATCH
		IF XACT_STATE() != 0
			ROLLBACK TRAN;
		THROW
	END CATCH
END
GO

GRANT EXECUTE ON [internal].[cancel_job] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[internal].[cleanup_completed_jobs]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[cleanup_completed_jobs]
END
GO

CREATE PROCEDURE [internal].[cleanup_completed_jobs]
	@minutesToKeep    INT = 60                                
WITH EXECUTE AS 'AllSchemaOwner'
AS
BEGIN
	SET NOCOUNT ON
	SET XACT_ABORT ON	
	DECLARE @JobRowsAffected BIGINT
	DECLARE @DeleteBatchSize BIGINT
    DECLARE @DeleteTimeBar DATETIMEOFFSET
	DECLARE @JobIdSet TABLE([JobId] UNIQUEIDENTIFIER)
	SET @DeleteBatchSize = 1000
	SET @JobRowsAffected = @DeleteBatchSize
	
    IF (@minutesToKeep is null or @minutesToKeep < 0)
        SET @minutesToKeep = 60

    SET @DeleteTimeBar = DATEADD(MINUTE, -@minutesToKeep, SYSDATETIMEOFFSET())
	
	WHILE (@JobRowsAffected = @DeleteBatchSize)
	BEGIN
		
    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
		BEGIN TRY
			DELETE FROM @JobIdSet
				
			
			INSERT INTO @JobIdSet			
			SELECT TOP (@DeleteBatchSize) [JobId] FROM [internal].[tasks] GROUP BY [JobId]
			HAVING COUNT(*) > 0 AND COUNT(CASE WHEN [Status] <= 3 or [LastUpdatedTime] > @DeleteTimeBar THEN [Status] END) = 0
	
			
			INSERT INTO [internal].[jobs_History] 
			SELECT * FROM [internal].[jobs] WHERE [JobId] IN (SELECT * from @JobIdSet)
			
			
			INSERT INTO [internal].[tasks_history] 
			SELECT * FROM [internal].[tasks] WHERE [JobId] IN (SELECT * from @JobIdSet)
			
			
			INSERT INTO [internal].[job_worker_agents_history]
			SELECT * FROM [internal].[job_worker_agents] WHERE [JobId] IN (SELECT * from @JobIdSet)
			
			DELETE FROM [internal].[jobs] WHERE [JobId] IN (SELECT * from @JobIdSet)
			
			SELECT @JobRowsAffected = Count(*) FROM @JobIdSet
				
			
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
		END TRY
		BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                                  
        THROW 
		END CATCH
	END
	RETURN 0
END
GO

GRANT EXECUTE ON [internal].[cleanup_completed_jobs] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[internal].[get_input_value]', 'FN') IS NOT NULL)
BEGIN
DROP FUNCTION  [internal].[get_input_value]
END
GO

CREATE FUNCTION [internal].[get_input_value]
(
    @input_value NVARCHAR(MAX),
	@name		 NVARCHAR(MAX)
    )
RETURNS NVARCHAR(MAX)
AS
BEGIN
	DECLARE @return_value NVARCHAR(MAX)
	SELECT @return_value = [value]
	FROM OPENJSON(@input_value) WITH (name NVARCHAR(MAX) '$.name', value NVARCHAR(MAX) '$.value')
	WHERE name = @name
	RETURN @return_value
END
GO
IF (OBJECT_ID('[internal].[dealwith_expired_tasks]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[dealwith_expired_tasks]
END
GO

CREATE PROCEDURE [internal].[dealwith_expired_tasks]

WITH EXECUTE AS 'AllSchemaOwner'
AS
BEGIN
	SET NOCOUNT ON
	SET XACT_ABORT ON
	
	
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
	BEGIN TRY
		DECLARE @updated_tasks TABLE  
		(  
			TaskId UNIQUEIDENTIFIER,  
			Status INT,
			TaskType INT,
			InputData NVARCHAR(MAX)  
		);  
  
		UPDATE [internal].[tasks]
		SET [LastUpdatedTime]=SYSDATETIMEOFFSET(),
			[Status]=
			CASE 
				WHEN [IsCancelled] = 1 THEN 6
				WHEN [IsCancelled] = 0 AND [MaxExecutedCount] > [ExecutedCount] THEN 1
				ELSE 4
			END,
			[ReadyForDispatchTime]=
			CASE 
				WHEN [IsCancelled] = 0 AND [MaxExecutedCount] > [ExecutedCount] THEN SYSDATETIMEOFFSET()
				ELSE NULL
			END,
			[ExpiredTime] = 
			CASE 
				WHEN [IsCancelled] = 0 AND [MaxExecutedCount] > [ExecutedCount] THEN NULL
				ELSE [ExpiredTime]
			END,
			[WorkerAgentId]=
			CASE 
				WHEN [IsCancelled] = 0 AND [MaxExecutedCount] > [ExecutedCount] THEN NULL
				ELSE [WorkerAgentId]
			END	
		OUTPUT INSERTED.TaskId, INSERTED.Status, INSERTED.TaskType, INSERTED.InputData INTO @updated_tasks
		FROM [internal].[tasks] 
		WHERE [Status] IN (2, 3) AND [ExpiredTime] <= SYSDATETIMEOFFSET()
		
		
		UPDATE [internal].[operations]
		SET [status] = 6, [end_time] = SYSDATETIMEOFFSET()
		WHERE [operation_id] IN (SELECT CONVERT(bigint,[internal].[get_input_value]([InputData], 'execution_id')) FROM @updated_tasks 
		WHERE [TaskType] = 0 AND [Status] = 4)
		
		
		DECLARE @stop_operations TABLE  
		(
			operation_id bigint,
			operation_to_stop_id bigint
		);

		INSERT INTO @stop_operations 
			SELECT CONVERT(bigint,[internal].[get_input_value]([InputData], 'stop_id')), CONVERT(bigint,[internal].[get_input_value]([InputData], 'execution_id')) FROM @updated_tasks 
				WHERE [TaskType] = 0 AND [Status] = 6

		
		UPDATE [internal].[operations]
		SET [status] = 3, [end_time] = SYSDATETIMEOFFSET(), [stopped_by_sid] = [caller_sid] , [stopped_by_name] = [caller_name]
		FROM @stop_operations AS STOP_OP INNER JOIN [internal].[operations] ON STOP_OP.operation_to_stop_id = [internal].[operations].[operation_id]

		
		UPDATE [internal].[operations]
		SET [status] = 7, [end_time] = SYSDATETIMEOFFSET()
		WHERE [operation_id] IN (SELECT operation_id FROM @stop_operations)

		
		
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
		RETURN 0
	END TRY
	BEGIN CATCH
		
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
		THROW
	END CATCH
END
GO

GRANT EXECUTE ON [internal].[dealwith_expired_tasks] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[internal].[create_job]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[create_job]
END
GO

CREATE PROCEDURE [internal].[create_job]
	@JobId     				UNIQUEIDENTIFIER OUTPUT,
	@JobType 				INT,
	@InputData				NVARCHAR(MAX) = NULL,
	@Creator				NVARCHAR(256),
	@UseAllWorkerAgents		BIT = 0

WITH EXECUTE AS 'AllSchemaOwner'
AS
BEGIN
	SET XACT_ABORT ON
	SET NOCOUNT ON
	
	
	IF @JobType IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@JobType')
		RETURN 1
	END
	
	IF @Creator IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@Creator')
		RETURN 1
	END
	IF LEN(@Creator) = 0
	BEGIN
		RAISERROR(27102, 16, 1, N'@Creator')
		RETURN 1
	END	
		
	DECLARE @GUID_All_Worker_Agents UNIQUEIDENTIFIER = '11111111-1111-1111-1111-111111111111'
	SET @JobId =  NEWID()
	
	BEGIN TRAN
	BEGIN TRY
		INSERT [internal].[jobs]([JobId], [JobType], [InputData], [Creator])
		VALUES (@JobId, @JobType, @InputData, @Creator)
			
		IF @UseAllWorkerAgents = 1
			INSERT INTO [internal].[job_worker_agents]([WorkerAgentId], [JobId]) VALUES (@GUID_All_Worker_Agents, @JobId)	
		
		COMMIT TRAN
		RETURN 0
	END TRY
	BEGIN CATCH
		IF XACT_STATE() != 0
			ROLLBACK TRAN;
		THROW
	END CATCH
END
GO

GRANT EXECUTE ON [internal].[create_job] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[internal].[append_job_worker_agent]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[append_job_worker_agent]
END
GO

CREATE PROCEDURE [internal].[append_job_worker_agent]
	@JobId     			UNIQUEIDENTIFIER,
	@WorkerAgentId		UNIQUEIDENTIFIER

WITH EXECUTE AS 'AllSchemaOwner'
AS
BEGIN
	SET NOCOUNT ON
	
	DECLARE @GUID_All_Worker_Agents UNIQUEIDENTIFIER = '11111111-1111-1111-1111-111111111111'
		
	
	IF @JobId IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@JobId')
		RETURN 1
	END
	
	IF @WorkerAgentId IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@WorkerAgentId')
		RETURN 1
	END
	
	IF @WorkerAgentId = '11111111-1111-1111-1111-111111111111'
	BEGIN
		RAISERROR(27101, 16, 1, '11111111-1111-1111-1111-111111111111') WITH NOWAIT
		RETURN 1
	END
	
	IF EXISTS (SELECT [WorkerAgentId] FROM [internal].[job_worker_agents] WHERE [JobId]=@JobId AND [WorkerAgentId]=@GUID_All_Worker_Agents)
	BEGIN
		DECLARE @strJobId NVARCHAR(50)
		SET @strJobId = CONVERT(NVARCHAR(50), @JobId)
		RAISERROR(27251, 16, 1, @strJobId) WITH NOWAIT
		RETURN 1
	END
	
	BEGIN TRY
		INSERT INTO [internal].[job_worker_agents]([WorkerAgentId], [JobId]) 
		SELECT @WorkerAgentId, @JobId FROM [internal].[worker_agents] WHERE [WorkerAgentId]=@WorkerAgentId
	
		
		IF @@ROWCOUNT = 0
		BEGIN
			DECLARE @strWorkerAgentId NVARCHAR(50)
			SET @strWorkerAgentId = CONVERT(NVARCHAR(50), @WorkerAgentId)
			RAISERROR(27243, 16, 1, @strWorkerAgentId)
		END
	END TRY
	BEGIN CATCH
		THROW
	END CATCH
	
	RETURN 0
END
	
GO

GRANT EXECUTE ON [internal].[append_job_worker_agent] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[internal].[update_task_status]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[update_task_status]
END
GO

CREATE PROCEDURE [internal].[update_task_status]
	@WorkerAgentId		UNIQUEIDENTIFIER,
	@TaskId				UNIQUEIDENTIFIER,
	@ExpiredTime		DATETIMEOFFSET,
	@Status				INT	OUTPUT

WITH EXECUTE AS 'AllSchemaOwner'
AS
BEGIN
	SET NOCOUNT ON
	SET XACT_ABORT ON

	
	IF @WorkerAgentId IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@WorkerAgentId')
		RETURN 1
	END
	
	IF @TaskId IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@TaskId')
		RETURN 1
	END
	
	IF @Status IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@Status')
		RETURN 1
	END
	
	DECLARE @IsWorkerAgentEnabled BIT = NULL
	DECLARE @strWorkerAgentId NVARCHAR(50)
	SELECT @IsWorkerAgentEnabled =[IsEnabled] FROM [internal].[worker_agents]
	WHERE [WorkerAgentId]=@WorkerAgentId
	IF @@ROWCOUNT = 0
	BEGIN
		RAISERROR(27243, 16, 1) WITH NOWAIT
		RETURN 1
	END
	ELSE IF @IsWorkerAgentEnabled=0
	BEGIN
		SET @strWorkerAgentId = CONVERT(NVARCHAR(50), @WorkerAgentId)
		RAISERROR(27252, 16, 1, @strWorkerAgentId) WITH NOWAIT
		RETURN 1
	END																					
	
	BEGIN TRAN
	BEGIN TRY
		
		DECLARE @JobId UNIQUEIDENTIFIER = NULL
		DECLARE @RetStatus INT = NULL
			
		UPDATE [internal].[tasks]
		SET @JobId = [JobId],
			[LastUpdatedTime]=SYSDATETIMEOFFSET(),
			[ExpiredTime]=
			CASE 
				WHEN @Status=2 OR @Status=3 THEN @ExpiredTime
				ELSE NULL
			END,
			[Status]=
			CASE 
				WHEN @Status=5 OR @Status=2 OR @Status=3 THEN @Status
				WHEN [IsCancelled]=1 AND @Status > 3 AND @Status != 5 THEN 6
				WHEN [IsCancelled]=0 AND [MaxExecutedCount] > [ExecutedCount] AND @Status > 3 AND @Status != 5 THEN 1
				ELSE @Status
			END,
			@RetStatus=
			CASE 
				WHEN [IsCancelled]=1 AND (@Status=2 OR @Status=3) THEN 6
				WHEN [IsCancelled]=0 AND @Status > 3 AND @Status != 5 AND [MaxExecutedCount] > [ExecutedCount] THEN 1
				ELSE @Status
			END,
			[ReadyForDispatchTime]=
			CASE
				WHEN [IsCancelled]=0 AND @Status > 3 AND @Status != 5 AND [MaxExecutedCount] > [ExecutedCount] THEN SYSDATETIMEOFFSET()
				ELSE NULL
			END
		FROM [internal].[tasks]
		WHERE [TaskId] = @TaskId AND [WorkerAgentId]=@WorkerAgentId AND [Status]<=3 AND [Status] >= 2 AND @Status >= [Status]
		
		COMMIT TRAN
		
		IF @RetStatus IS NULL
		BEGIN
			DECLARE @strTaskId NVARCHAR(50)
			SET @strTaskId = CONVERT(NVARCHAR(50), @TaskId)
			RAISERROR(27246, 16, 1, @strTaskId) WITH NOWAIT
		END
		
		SET @Status=@RetStatus
		RETURN 0
	END TRY
	BEGIN CATCH
		IF XACT_STATE() != 0
			ROLLBACK TRAN;
		THROW
	END CATCH
END
GO

GRANT EXECUTE ON [internal].[update_task_status] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[internal].[get_task_by_id]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[get_task_by_id]
END
GO

CREATE PROCEDURE [internal].[get_task_by_id]
	@TaskId uniqueidentifier

WITH EXECUTE AS 'AllSchemaOwner'
AS
BEGIN
	SET NOCOUNT ON
	
	IF @TaskId IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@TaskId')
		RETURN 1
	END
	SELECT * FROM [internal].[tasks] WHERE [TaskId] = @TaskId
	RETURN 0
END
GO

GRANT EXECUTE ON [internal].[get_task_by_id] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[internal].[pull_task]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[pull_task]
END
GO

CREATE PROCEDURE [internal].[pull_task]
	@WorkerAgentId 		UNIQUEIDENTIFIER,
	@ExplicitAssignOnly           BIT = 0

WITH EXECUTE AS 'AllSchemaOwner'
AS
BEGIN
	SET XACT_ABORT ON
    SET NOCOUNT ON
	
	
	IF @WorkerAgentId IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@WorkerAgentId')
		RETURN 1
	END
	
	DECLARE @IsWorkerAgentEnabled BIT = NULL
	DECLARE @strWorkerAgentId NVARCHAR(50)
	SELECT @IsWorkerAgentEnabled =[IsEnabled] FROM [internal].[worker_agents]
	WHERE [WorkerAgentId]=@WorkerAgentId
	IF @@ROWCOUNT = 0
	BEGIN
		RAISERROR(27243, 16, 1) WITH NOWAIT
		RETURN 1
	END
	ELSE IF @IsWorkerAgentEnabled=0
	BEGIN
		SET @strWorkerAgentId = CONVERT(NVARCHAR(50), @WorkerAgentId)
		RAISERROR(27252, 16, 1, @strWorkerAgentId) WITH NOWAIT
		RETURN 1
	END																					

    DECLARE @TaskId UNIQUEIDENTIFIER, 
			@JobId UNIQUEIDENTIFIER, 
			@TaskType INT, 
			@MaxExecutedCount INT, 
			@ExecutedCount INT, 
			@InputData NVARCHAR(max),
			@Status INT, 
			@ReadyForDispatchTime DATETIMEOFFSET, 
			@LastUpdatedTime DATETIMEOFFSET, 
			@Priority INT, 
			@IsCritical BIT, 
			@ExpiredTime DATETIMEOFFSET,
			@CreatedTime DATETIMEOFFSET,
			@CreateWorkerAgentId UNIQUEIDENTIFIER
			
	DECLARE @GUID_All_Worker_Agents	UNIQUEIDENTIFIER = '11111111-1111-1111-1111-111111111111'
	
    BEGIN TRAN
	BEGIN TRY
		SELECT TOP 1
			@TaskId=t.[TaskId], 
			@JobId=t.[JobId], 
			@TaskType=t.[TaskType], 
			@MaxExecutedCount=t.[MaxExecutedCount],
			@ExecutedCount=t.[ExecutedCount], 
			@InputData=t.[InputData],
			@Status=t.[Status],
			@ReadyForDispatchTime = t.[ReadyForDispatchTime],
			@LastUpdatedTime=t.[LastUpdatedTime],
			@Priority=t.[Priority],
			@IsCritical=t.[IsCritical],
			@ExpiredTime=t.[ExpiredTime],
			@CreatedTime=t.[CreatedTime],
			@CreateWorkerAgentId = t.[CreateWorkerAgentId]
		FROM [internal].[tasks] t WITH (UPDLOCK, READPAST) 
		WHERE [ReadyForDispatchTime] IS NOT NULL 
		AND ((@ExplicitAssignOnly = 0 AND ([CandidateWorkerAgentId] IS NULL OR [CandidateWorkerAgentId] = @WorkerAgentId))
			OR (@ExplicitAssignOnly = 1 AND [CandidateWorkerAgentId] = @WorkerAgentId))
		AND [JobId] IN (SELECT [JobId] FROM [internal].[job_worker_agents] WHERE [WorkerAgentId]=@GUID_All_Worker_Agents 
						UNION ALL
						SELECT [JobId] FROM [internal].[job_worker_agents] WHERE [WorkerAgentId]=@WorkerAgentId)
		ORDER BY [ReadyForDispatchTime]
			
	
		IF @TaskId IS NOT NULL
		BEGIN
			
			
			SET @LastUpdatedTime = SYSDATETIMEOFFSET()
			SET @ReadyForDispatchTime = NULL
			SET @Status = 2
			SET @ExecutedCount = @ExecutedCount + 1
			SET @ExpiredTime = DATEADD(MINUTE, 5, SYSDATETIMEOFFSET())
			UPDATE [internal].[tasks]
			SET [ExecutedCount] = @ExecutedCount, [LastUpdatedTime] = @LastUpdatedTime, [WorkerAgentId] = @WorkerAgentId, [ReadyForDispatchTime]=@ReadyForDispatchTime, [Status]=@Status, [ExpiredTime]=@ExpiredTime,
				[LastPickupTime] = SYSDATETIMEOFFSET()
			WHERE [TaskId] = @TaskId

			SELECT 
				@TaskId as [TaskId], 
				@JobId as [JobId], 
				@TaskType as [TaskType], 
				@MaxExecutedCount as [MaxExecutedCount],
				@ExecutedCount as [ExecutedCount],
				@InputData as [InputData], 
				@Status as [Status], 
				@ReadyForDispatchTime as [ReadyForDispatchTime],
				@LastUpdatedTime as [LastUpdatedTime], 
				@Priority as [Priority],
				@IsCritical as [IsCritical],
				@ExpiredTime as [ExpiredTime],
				@CreatedTime as [CreatedTime],
				@WorkerAgentId as [WorkerAgentId],
				@CreateWorkerAgentId as [CreateWorkerAgentId]
		END
        COMMIT TRAN
		RETURN 0
    END TRY
    BEGIN CATCH
		IF XACT_STATE() != 0
			ROLLBACK TRAN;
		THROW
	END CATCH
END
GO

GRANT EXECUTE ON [internal].[pull_task] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[internal].[create_task]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[create_task]
END
GO

CREATE PROCEDURE [internal].[create_task]
  @TaskId					UNIQUEIDENTIFIER	OUTPUT,
  @JobId					UNIQUEIDENTIFIER,
  @TaskType					INT,
  @MaxExecutedCount 		INT = 1, 
  @InputData				NVARCHAR(MAX) = NULL,
  @CreateWorkerAgentId		UNIQUEIDENTIFIER = NULL,
  @CandidateWorkerAgentId   UNIQUEIDENTIFIER = NULL

WITH EXECUTE AS 'AllSchemaOwner'
AS
BEGIN
	SET NOCOUNT ON
	
	
	IF @JobId IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@JobId')
		RETURN 1
	END
	
	IF @TaskType IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@TaskType')
		RETURN 1
	END
		
	IF @CreateWorkerAgentId IS NOT NULL
	BEGIN
		
	DECLARE @IsWorkerAgentEnabled BIT = NULL
	DECLARE @strWorkerAgentId NVARCHAR(50)
	SELECT @IsWorkerAgentEnabled =[IsEnabled] FROM [internal].[worker_agents]
	WHERE [WorkerAgentId]=@CreateWorkerAgentId
	IF @@ROWCOUNT = 0
	BEGIN
		RAISERROR(27243, 16, 1) WITH NOWAIT
		RETURN 1
	END
	ELSE IF @IsWorkerAgentEnabled=0
	BEGIN
		SET @strWorkerAgentId = CONVERT(NVARCHAR(50), @CreateWorkerAgentId)
		RAISERROR(27252, 16, 1, @strWorkerAgentId) WITH NOWAIT
		RETURN 1
	END																					
	END
	
	SET @TaskId = NEWID()
	INSERT [internal].[tasks] ([TaskId], [JobId], [TaskType], [MaxExecutedCount], [InputData], [ReadyForDispatchTime], [Status], [CreatedTime], [LastUpdatedTime], [CreateWorkerAgentId], [CandidateWorkerAgentId])
	SELECT @TaskId, @JobId, @TaskType, @MaxExecutedCount, @InputData, SYSDATETIMEOFFSET(), 1, SYSDATETIMEOFFSET(), SYSDATETIMEOFFSET(), @CreateWorkerAgentId, @CandidateWorkerAgentId 
	FROM [internal].[jobs] WHERE [JobId]=@JobId AND [IsCancelled]=0
	
	IF @@ROWCOUNT = 0
	BEGIN
		DECLARE @strJobId NVARCHAR(50)
		SET @strJobId = CONVERT(NVARCHAR(50), @JobId)
		RAISERROR(27242, 16, 1, @strJobId) WITH NOWAIT
		RETURN 1
	END
	RETURN 0
END
GO

GRANT EXECUTE ON [internal].[create_task] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[catalog].[delete_worker_agent]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[delete_worker_agent]
END
GO

CREATE PROCEDURE [catalog].[delete_worker_agent]
    @WorkerAgentId	UNIQUEIDENTIFIER
WITH EXECUTE AS 'AllSchemaOwner'
AS
BEGIN
	SET NOCOUNT ON

	
	IF @WorkerAgentId IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@WorkerAgentId')
		RETURN 1
	END
	
	IF @WorkerAgentId = '11111111-1111-1111-1111-111111111111'
	BEGIN
		RAISERROR(27101, 16, 1, '11111111-1111-1111-1111-111111111111') WITH NOWAIT
		RETURN 1
	END

	DELETE FROM [internal].[worker_agents] WHERE [WorkerAgentId] = @WorkerAgentId
	
	IF @@ROWCOUNT = 0
	BEGIN
		DECLARE @strWorkerAgentId NVARCHAR(50)
		SET @strWorkerAgentId = CONVERT(NVARCHAR(50), @WorkerAgentId)
		RAISERROR(27243, 16, 1, @strWorkerAgentId) WITH NOWAIT
		RETURN 1
	END

	RETURN 0
END
GO

GRANT EXECUTE ON [catalog].[delete_worker_agent] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[catalog].[disable_worker_agent]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[disable_worker_agent]
END
GO

CREATE PROCEDURE [catalog].[disable_worker_agent]
    @WorkerAgentId	UNIQUEIDENTIFIER

WITH EXECUTE AS 'AllSchemaOwner'
AS
BEGIN
	SET NOCOUNT ON
		
	
	IF @WorkerAgentId IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@WorkerAgentId')
		RETURN 1
	END
	
	IF @WorkerAgentId = '11111111-1111-1111-1111-111111111111'
	BEGIN
		RAISERROR(27101, 16, 1, '11111111-1111-1111-1111-111111111111') WITH NOWAIT
		RETURN 1
	END
	
	UPDATE [internal].[worker_agents] SET [IsEnabled]=0 WHERE WorkerAgentId=@WorkerAgentId 
	
	IF @@ROWCOUNT = 0
	BEGIN
		DECLARE @strWorkerAgentId NVARCHAR(50)
		SET @strWorkerAgentId = CONVERT(NVARCHAR(50), @WorkerAgentId)
		RAISERROR(27243, 16, 1, @strWorkerAgentId) WITH NOWAIT
		RETURN 1
	END
	
	RETURN 0
END
GO

GRANT EXECUTE ON [catalog].[disable_worker_agent] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[catalog].[enable_worker_agent]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[enable_worker_agent]
END
GO

CREATE PROCEDURE [catalog].[enable_worker_agent]
    @WorkerAgentId	UNIQUEIDENTIFIER

WITH EXECUTE AS 'AllSchemaOwner'
AS
BEGIN
	SET NOCOUNT ON
		
    
	IF @WorkerAgentId IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@WorkerAgentId')
		RETURN 1
	END
	
	IF @WorkerAgentId = '11111111-1111-1111-1111-111111111111'
	BEGIN
		RAISERROR(27101, 16, 1, '11111111-1111-1111-1111-111111111111') WITH NOWAIT
		RETURN 1
	END
	
	UPDATE [internal].[worker_agents] SET [IsEnabled]=1 WHERE WorkerAgentId=@WorkerAgentId 
	
	IF @@ROWCOUNT = 0
	BEGIN
		DECLARE @strWorkerAgentId NVARCHAR(50)
		SET @strWorkerAgentId = CONVERT(NVARCHAR(50), @WorkerAgentId)
		RAISERROR(27243, 16, 1, @strWorkerAgentId) WITH NOWAIT
		RETURN 1
	END
	
	RETURN 0
END
GO

GRANT EXECUTE ON [catalog].[enable_worker_agent] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[catalog].[set_worker_agent_property]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[set_worker_agent_property]
END
GO

CREATE PROCEDURE [catalog].[set_worker_agent_property]
    @WorkerAgentId	UNIQUEIDENTIFIER,
	@PropertyName   NVARCHAR(256),
	@PropertyValue 	NVARCHAR(MAX)

WITH EXECUTE AS 'AllSchemaOwner'
AS
BEGIN
	SET NOCOUNT ON
		
	
	IF @WorkerAgentId IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@WorkerAgentId')
		RETURN 1
	END
	
	IF @WorkerAgentId = '11111111-1111-1111-1111-111111111111'
	BEGIN
		RAISERROR(27101, 16, 1, '11111111-1111-1111-1111-111111111111') WITH NOWAIT
		RETURN 1
	END
	
	IF @PropertyName = 'Tags'
		UPDATE [internal].[worker_agents] SET [Tags] = @PropertyValue WHERE [WorkerAgentId]=@WorkerAgentId
	ELSE IF @PropertyName = 'DisplayName'
		UPDATE [internal].[worker_agents] SET [DisplayName] = @PropertyValue WHERE [WorkerAgentId]=@WorkerAgentId
	ELSE IF @PropertyName = 'Description'
		UPDATE [internal].[worker_agents] SET [Description] = @PropertyValue WHERE [WorkerAgentId]=@WorkerAgentId
	ELSE
	BEGIN
		RAISERROR(27101, 16, 1, @PropertyName) WITH NOWAIT
		RETURN 1
	END
	
	IF @@ROWCOUNT = 0
	BEGIN
		DECLARE @strWorkerAgentId NVARCHAR(50)
		SET @strWorkerAgentId = CONVERT(NVARCHAR(50), @WorkerAgentId)
		RAISERROR(27243, 16, 1, @strWorkerAgentId) WITH NOWAIT
		RETURN 1
	END
	
	RETURN 0
END
GO

GRANT EXECUTE ON [catalog].[set_worker_agent_property] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[internal].[update_worker_agent_status]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[update_worker_agent_status]
END
GO

CREATE PROCEDURE [internal].[update_worker_agent_status]
    @WorkerAgentId			UNIQUEIDENTIFIER,
	@PerfCounterList 		[internal].[machine_perf_counter_list_type]	READONLY,
	@MachinePropertyList 	[internal].[machine_property_list_type] READONLY,
	@Status 				INT = 0 OUTPUT
AS
BEGIN
	SET NOCOUNT ON
	SET @Status = 0
	
	
	IF @WorkerAgentId IS NULL
	BEGIN
		RAISERROR(27100, 16, 1, N'@WorkerAgentId')
		RETURN 1
	END
	
	IF @WorkerAgentId = '11111111-1111-1111-1111-111111111111'
	BEGIN
		RAISERROR(27101, 16, 1, '11111111-1111-1111-1111-111111111111') WITH NOWAIT
		RETURN 1
	END
	
	
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
	BEGIN TRY				
		UPDATE [internal].[worker_agents]
		SET [LastOnlineTime]=SYSDATETIMEOFFSET(),
			[MachineName]= 
				CASE 
					WHEN EXISTS (SELECT * FROM @MachinePropertyList WHERE [PropertyName]='MachineName') THEN (SELECT [PropertyValue] FROM @MachinePropertyList WHERE [PropertyName]='MachineName')
					ELSE [MachineName]
				END,
			[UserAccount]=
				CASE 
					WHEN EXISTS (SELECT * FROM @MachinePropertyList WHERE [PropertyName]='UserAccount') THEN (SELECT [PropertyValue] FROM @MachinePropertyList WHERE [PropertyName]='UserAccount')
					ELSE [UserAccount]
				END,
			@Status = [IsEnabled]
		FROM [internal].[worker_agents] 
		WHERE [WorkerAgentId]=@WorkerAgentId
		
		IF @@ROWCOUNT = 0
		BEGIN
			INSERT INTO [internal].[worker_agents]([WorkerAgentId],[LastOnlineTime],[MachineName],[UserAccount]) VALUES		
			(@WorkerAgentId, SYSDATETIMEOFFSET(), 
			(SELECT [PropertyValue] FROM @MachinePropertyList WHERE [PropertyName]='MachineName'),
			(SELECT [PropertyValue] FROM @MachinePropertyList WHERE [PropertyName]='UserAccount'))
			SET @Status = 0
		END
				
		INSERT INTO [internal].[worker_agent_perfcounter] ([WorkerAgentId],[PerfCounterName], [PerfCounterValue],[TimeStamp])
		SELECT @WorkerAgentId, [PerfCounterName], [PerfCounterValue], [TimeStamp] FROM @PerfCounterList
		
		MERGE INTO [internal].[machine_properties] AS dst
		USING @MachinePropertyList AS src
		ON @WorkerAgentId = dst.WorkerAgentId AND src.[PropertyName] = dst.[PropertyName]
		WHEN MATCHED THEN UPDATE SET dst.[PropertyValue] = src.[PropertyValue]
		WHEN NOT MATCHED THEN INSERT VALUES(@WorkerAgentId, src.[PropertyName], src.[PropertyValue]);
		
	
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                             
        THROW 
    END CATCH
	
	RETURN 0
END
GO
IF (OBJECT_ID('[internal].[master_heartbeat]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[master_heartbeat]
END
GO

CREATE PROCEDURE [internal].[master_heartbeat]
AS
BEGIN
	SET NOCOUNT ON
	
	UPDATE [SSISDB].[internal].[master_properties] SET property_value = SYSDATETIMEOFFSET()  WHERE property_name = 'LAST_ONLINE_TIME'
	
END
GO
IF (OBJECT_ID('[internal].[update_master_settings]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[update_master_settings]
END
GO

CREATE PROCEDURE [internal].[update_master_settings]
    @MachineName			NVARCHAR(256),
	@MachineIP 				NVARCHAR(256),
	@MasterServicePort 		NVARCHAR(256),
	@SSLCertThumbprint	 	NVARCHAR(256),
	@MasterHeartbeatIntervalInMs NVARCHAR(256)
AS
BEGIN
	IF @MachineName IS NULL
	BEGIN
		RAISERROR('@MachineName argument is null.', 16, 1)
		RETURN
	END
	
	IF @MachineIP IS NULL
	BEGIN
		RAISERROR('@MachineIP argument is null.', 16, 1)
		RETURN
	END
	
	IF @MasterServicePort IS NULL
	BEGIN
		RAISERROR('@MasterServicePort argument is null.', 16, 1)
		RETURN
	END
	
	IF @SSLCertThumbprint IS NULL
	BEGIN
		RAISERROR('@SSLCertThumbprint argument is null.', 16, 1)
		RETURN
	END
	
	IF @MasterHeartbeatIntervalInMs IS NULL
	BEGIN
		RAISERROR('@MasterHeartbeatIntervalInMs argument is null.', 16, 1)
		RETURN
	END
		
	SET NOCOUNT ON
	
	BEGIN TRAN
	BEGIN TRY
	UPDATE [SSISDB].[internal].[master_properties] SET property_value = @MachineName  WHERE property_name = 'MACHINE_NAME'
	UPDATE [SSISDB].[internal].[master_properties] SET property_value = @MachineIP  WHERE property_name = 'MACHINE_IP'
	UPDATE [SSISDB].[internal].[master_properties] SET property_value = @MasterServicePort  WHERE property_name = 'MASTER_SERVICE_PORT'
	UPDATE [SSISDB].[internal].[master_properties] SET property_value = @SSLCertThumbprint  WHERE property_name = 'SSLCERT_THUMBPRINT'
	UPDATE [SSISDB].[internal].[master_properties] SET property_value = @MasterHeartbeatIntervalInMs  WHERE property_name = 'MASTER_HEARTBEAT_INTERVALINMS'
	
	COMMIT TRAN	

	END TRY
    BEGIN CATCH
		IF XACT_STATE() != 0
			ROLLBACK TRAN;
		THROW
	END CATCH
END
GO
IF (OBJECT_ID('[catalog].[update_master_address]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[update_master_address]
END
GO

CREATE PROCEDURE [catalog].[update_master_address]
	@MasterAddress NVARCHAR(256)
AS
BEGIN
	IF @MasterAddress IS NULL
	BEGIN
		RAISERROR('@MasterAddress argument is null.', 16, 1)
		RETURN
	END
		
	SET NOCOUNT ON

	UPDATE [SSISDB].[internal].[master_properties] SET property_value = @MasterAddress  WHERE property_name = 'MASTER_ADDRESS'

END
GO

GRANT EXECUTE ON [catalog].[update_master_address] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[internal].[cleanup_server_retention_window]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[cleanup_server_retention_window]
END
GO

CREATE PROCEDURE [internal].[cleanup_server_retention_window]
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON
    
    DECLARE @enable_clean_operation bit
    DECLARE @retention_window_length int
    DECLARE @server_operation_encryption_level int
    
    DECLARE @caller_name nvarchar(256)
    DECLARE @caller_sid  varbinary(85)
    DECLARE @operation_id bigint
    
    EXECUTE AS CALLER
        SET @caller_name =  SUSER_NAME()
        SET @caller_sid =   SUSER_SID()
    REVERT
         
    
    BEGIN TRY
        SELECT @enable_clean_operation = CONVERT(bit, property_value) 
            FROM [catalog].[catalog_properties]
            WHERE property_name = 'OPERATION_CLEANUP_ENABLED'
        
        IF @enable_clean_operation = 1
        BEGIN
            SELECT @retention_window_length = CONVERT(int,property_value)  
                FROM [catalog].[catalog_properties]
                WHERE property_name = 'RETENTION_WINDOW'
                

            IF @retention_window_length <= 0 
            BEGIN
                RAISERROR(27163    ,16,1,'RETENTION_WINDOW')
            END
            
            SELECT @server_operation_encryption_level = CONVERT(int,property_value)  
                FROM [catalog].[catalog_properties]
                WHERE property_name = 'SERVER_OPERATION_ENCRYPTION_LEVEL'

            IF @server_operation_encryption_level NOT in (1, 2)       
            BEGIN
                RAISERROR(27163    ,16,1,'SERVER_OPERATION_ENCRYPTION_LEVEL')
            END
            INSERT INTO [internal].[operations] (
                [operation_type],  
                [created_time], 
                [object_type],
                [object_id],
                [object_name],
                [status], 
                [start_time],
                [caller_sid], 
                [caller_name]
                )
            VALUES (
                2,
                SYSDATETIMEOFFSET(),
                NULL,                     
                NULL,                     
                NULL,                     
                1,      
                SYSDATETIMEOFFSET(),
                @caller_sid,            
                @caller_name            
                ) 
            SET @operation_id = SCOPE_IDENTITY() 
            
            DECLARE @temp_date datetimeoffset
            DECLARE @rows_affected bigint
            DECLARE @delete_batch_size int

            
            SET @delete_batch_size = 1000  
            SET @rows_affected = @delete_batch_size
            
            SET @temp_date = DATEADD(day, -@retention_window_length, SYSDATETIMEOFFSET())
            
            CREATE TABLE #deleted_ops (operation_id bigint, operation_type smallint)
            DECLARE execution_cursor CURSOR LOCAL FOR SELECT operation_id FROM #deleted_ops  WHERE operation_type = 200

			DECLARE @sqlString_operation_messages_scaleout   nvarchar(1024)
            DECLARE @sqlString_event_messages_scaleout       nvarchar(1024)
            DECLARE @sqlString_event_message_context_scaleout        nvarchar(1024)

            IF @server_operation_encryption_level = 1
            BEGIN
                DECLARE @execution_id bigint
                DECLARE @sqlString              nvarchar(1024)
                DECLARE @sqlString_cert         nvarchar(1024)
                DECLARE @key_name               [internal].[adt_name]
                DECLARE @certificate_name       [internal].[adt_name]

                WHILE (@rows_affected = @delete_batch_size)
                BEGIN
                    DELETE TOP (@delete_batch_size)
                        FROM [internal].[operations] 
                            OUTPUT DELETED.operation_id, DELETED.operation_type INTO #deleted_ops
                        WHERE ( [end_time] <= @temp_date
                        
                        OR ([end_time] IS NULL AND [status] = 1 AND [created_time] <= @temp_date ))
                    
                    SET @rows_affected = @@ROWCOUNT
            
                    OPEN execution_cursor
                    FETCH NEXT FROM execution_cursor INTO @execution_id
            
                    WHILE @@FETCH_STATUS = 0
                    BEGIN
                        SET @key_name = 'MS_Enckey_Exec_'+CONVERT(varchar,@execution_id)
                        SET @certificate_name = 'MS_Cert_Exec_'+CONVERT(varchar,@execution_id)
                        SET @sqlString_operation_messages_scaleout = 'delete from [internal].[operation_messages_scaleout] where operation_id = '+CONVERT(varchar,@execution_id)
                        SET @sqlString_event_messages_scaleout = 'delete from [internal].[event_messages_scaleout] where operation_id = '+CONVERT(varchar,@execution_id)
                        SET @sqlString_event_message_context_scaleout  = 'delete from [internal].[event_message_context_scaleout] where operation_id = '+CONVERT(varchar,@execution_id)
                                SET @sqlString = 'DROP SYMMETRIC KEY '+ @key_name
                                SET @sqlString_cert = 'DROP CERTIFICATE '+ @certificate_name
                        
                                BEGIN TRY
                                EXECUTE sp_executesql @sqlString
                                    EXECUTE sp_executesql @sqlString_cert
                                    EXECUTE sp_executesql @sqlString_operation_messages_scaleout
                                    EXECUTE sp_executesql @sqlString_event_messages_scaleout
                                    EXECUTE sp_executesql @sqlString_event_message_context_scaleout
                                END TRY

                                BEGIN CATCH
                                    
                                END CATCH

                        FETCH NEXT FROM execution_cursor INTO @execution_id
                    END
                    CLOSE execution_cursor
                    TRUNCATE TABLE #deleted_ops
                END
                DROP TABLE #deleted_ops
                DEALLOCATE execution_cursor
            END
            ELSE BEGIN
                WHILE (@rows_affected = @delete_batch_size)
                BEGIN
                    DELETE TOP (@delete_batch_size)
                        FROM [internal].[operations] 
                         OUTPUT DELETED.operation_id, DELETED.operation_type INTO #deleted_ops
                        WHERE ( [end_time] <= @temp_date
                        OR ([end_time] IS NULL AND [status] = 1 AND [created_time] <= @temp_date ))
                    SET @rows_affected = @@ROWCOUNT

                    OPEN execution_cursor
                    FETCH NEXT FROM execution_cursor INTO @execution_id
                    WHILE @@FETCH_STATUS = 0
                    BEGIN
                        SET @sqlString_operation_messages_scaleout = 'delete from [internal].[operation_messages_scaleout] where operation_id = '+CONVERT(varchar,@execution_id)
                        SET @sqlString_event_messages_scaleout = 'delete from [internal].[event_messages_scaleout] where operation_id = '+CONVERT(varchar,@execution_id)
                        SET @sqlString_event_message_context_scaleout  = 'delete from [internal].[event_message_context_scaleout] where operation_id = '+CONVERT(varchar,@execution_id)
                        BEGIN TRY
                            EXECUTE sp_executesql @sqlString_operation_messages_scaleout
                            EXECUTE sp_executesql @sqlString_event_messages_scaleout
                            EXECUTE sp_executesql @sqlString_event_message_context_scaleout
                        END TRY
                        BEGIN CATCH 
                        END CATCH
                        FETCH NEXT FROM execution_cursor INTO @execution_id
                    END
                    CLOSE execution_cursor
                    TRUNCATE TABLE #deleted_ops
                END
                DEALLOCATE execution_cursor
                DROP TABLE #deleted_ops
            END
            
            UPDATE [internal].[operations]
                SET [status] = 7,
                [end_time] = SYSDATETIMEOFFSET()
                WHERE [operation_id] = @operation_id                                  
        END
    END TRY
    BEGIN CATCH
        
        
        IF @server_operation_encryption_level = 1
        BEGIN
        IF (CURSOR_STATUS('local', 'execution_cursor') = 1 
            OR CURSOR_STATUS('local', 'execution_cursor') = 0)
        BEGIN
            CLOSE execution_cursor
            DEALLOCATE execution_cursor            
        END
        END
        
        UPDATE [internal].[operations]
            SET [status] = 4,
            [end_time] = SYSDATETIMEOFFSET()
            WHERE [operation_id] = @operation_id;       
        THROW
    END CATCH
    
    RETURN 0
 GO
IF (OBJECT_ID('[internal].[cleanup_server_log]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[cleanup_server_log]
END
GO

CREATE PROCEDURE [internal].[cleanup_server_log]
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON
    
    DECLARE @enable_clean_operation bit
    
    DECLARE @caller_name nvarchar(256)
    DECLARE @caller_sid  varbinary(85)
    DECLARE @operation_id bigint
    
    EXECUTE AS CALLER
        SET @caller_name =  SUSER_NAME()
        SET @caller_sid =   SUSER_SID()
    REVERT
         
    
    BEGIN TRY
        SELECT @enable_clean_operation = CONVERT(bit, property_value) 
            FROM [catalog].[catalog_properties]
            WHERE property_name = 'OPERATION_CLEANUP_ENABLED'
        
        IF @enable_clean_operation = 1
        BEGIN
            INSERT INTO [internal].[operations] (
                [operation_type],  
                [created_time], 
                [object_type],
                [object_id],
                [object_name],
                [status], 
                [start_time],
                [caller_sid], 
                [caller_name]
                )
            VALUES (
                2,
                SYSDATETIMEOFFSET(),
                NULL,                     
                NULL,                     
                NULL,                     
                1,      
                SYSDATETIMEOFFSET(),
                @caller_sid,            
                @caller_name            
                ) 
            SET @operation_id = SCOPE_IDENTITY()
            
            
            IF EXISTS (SELECT operation_id FROM [internal].[operations]
                    WHERE [status] IN (2, 5)
                    AND   [operation_id] <> @operation_id )
            BEGIN    
                RAISERROR(27139, 16, 1) WITH NOWAIT
                RETURN 1
            END
            
            IF NOT EXISTS (SELECT [user_access] FROM sys.databases 
                WHERE name = 'SSISDB' and [user_access] = 1 )
            BEGIN
                RAISERROR(27160, 16 , 1, N'cleanup_server_log' ) WITH NOWAIT
                RETURN 1
            END 
            
            DECLARE @rows_affected bigint
            DECLARE @delete_batch_size int
            
            DECLARE @execution_id bigint
            CREATE TABLE #deleted_ops (operation_id bigint, operation_type smallint)
            DECLARE execution_cursor CURSOR LOCAL FOR SELECT operation_id FROM #deleted_ops  WHERE operation_type = 200

            DECLARE @sqlString_operation_messages_scaleout   nvarchar(1024)
            DECLARE @sqlString_event_messages_scaleout       nvarchar(1024)
            DECLARE @sqlString_event_message_context_scaleout        nvarchar(1024)

            
            SET @delete_batch_size = 1000  
            SET @rows_affected = @delete_batch_size
            
            WHILE (@rows_affected = @delete_batch_size)
            BEGIN
                DELETE TOP (@delete_batch_size)
                    FROM [internal].[operations] 
                    OUTPUT DELETED.operation_id, DELETED.operation_type INTO #deleted_ops
                    WHERE ([operation_type] = 200)
                  
                SET @rows_affected = @@ROWCOUNT
                OPEN execution_cursor
                FETCH NEXT FROM execution_cursor INTO @execution_id
                WHILE @@FETCH_STATUS = 0
                BEGIN
                    SET @sqlString_operation_messages_scaleout = 'delete from [internal].[operation_messages_scaleout] where operation_id = '+CONVERT(varchar,@execution_id)
                    SET @sqlString_event_messages_scaleout = 'delete from [internal].[event_messages_scaleout] where operation_id = '+CONVERT(varchar,@execution_id)
                    SET @sqlString_event_message_context_scaleout  = 'delete from [internal].[event_message_context_scaleout] where operation_id = '+CONVERT(varchar,@execution_id)
                    BEGIN TRY
                        EXECUTE sp_executesql @sqlString_operation_messages_scaleout
                        EXECUTE sp_executesql @sqlString_event_messages_scaleout
                        EXECUTE sp_executesql @sqlString_event_message_context_scaleout
                    END TRY
                    BEGIN CATCH 
                    END CATCH
                    FETCH NEXT FROM execution_cursor INTO @execution_id
                END
                CLOSE execution_cursor
                TRUNCATE TABLE #deleted_ops
            END
            DEALLOCATE execution_cursor
            DROP TABLE #deleted_ops
            
            UPDATE [internal].[operations]
                SET [status] = 7,
                [end_time] = SYSDATETIMEOFFSET()
                WHERE [operation_id] = @operation_id
        END
    END TRY
    BEGIN CATCH
        UPDATE [internal].[operations]
            SET [status] = 4,
            [end_time] = SYSDATETIMEOFFSET()
            WHERE [operation_id] = @operation_id;
        THROW;
    END CATCH
    
    RETURN 0
 GO
IF (OBJECT_ID('[internal].[cleanup_server_execution_keys]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[cleanup_server_execution_keys]
END
GO

CREATE PROCEDURE [internal].[cleanup_server_execution_keys]
    @cleanup_flag           int,
    @delete_batch_size      int = 1000
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON
    
    DECLARE @enable_clean_operation bit
    
    DECLARE @caller_name nvarchar(256)
    DECLARE @caller_sid  varbinary(85)
    DECLARE @operation_id bigint
    DECLARE @server_operation_encryption_level int
    
    EXECUTE AS CALLER
        SET @caller_name =  SUSER_NAME()
        SET @caller_sid =   SUSER_SID()
    REVERT
         
    
    BEGIN TRY
        SELECT @enable_clean_operation = CONVERT(bit, property_value) 
            FROM [catalog].[catalog_properties]
            WHERE property_name = 'OPERATION_CLEANUP_ENABLED'

       SELECT @server_operation_encryption_level = CONVERT(int,property_value)
        FROM [catalog].[catalog_properties]
        WHERE property_name = 'SERVER_OPERATION_ENCRYPTION_LEVEL'

        IF @enable_clean_operation <> 1 
            RETURN 0

        IF @cleanup_flag = 1 AND @server_operation_encryption_level = 2
        BEGIN
            
            INSERT INTO [internal].[operations] (
                [operation_type],  
                [created_time], 
                [object_type],
                [object_id],
                [object_name],
                [status], 
                [start_time],
                [caller_sid], 
                [caller_name]
                )
            VALUES (
                2,
                SYSDATETIMEOFFSET(),
                NULL,                     
                NULL,                     
                NULL,                     
                1,      
                SYSDATETIMEOFFSET(),
                @caller_sid,            
                @caller_name            
                ) 
            SET @operation_id = SCOPE_IDENTITY() 
            
            DECLARE @key_name               [internal].[adt_name]
            DECLARE @certificate_name       [internal].[adt_name]
            DECLARE @sqlString              nvarchar(1024)
            DECLARE @count int

            IF @delete_batch_size < 1
            BEGIN
                SET @delete_batch_size = 1000
            END

            DECLARE key_cursor CURSOR LOCAL FOR
                SELECT [name] FROM [sys].[symmetric_keys]
                WHERE [name] like 'MS_Enckey_Exec_%'

            DECLARE cert_cursor CURSOR LOCAL FOR
                SELECT [name] FROM [sys].[certificates]
                WHERE [name] like 'MS_Cert_Exec_%'
           
            SET @count = 0
            
            OPEN key_cursor
            FETCH NEXT FROM key_cursor INTO @key_name

            WHILE @@FETCH_STATUS = 0
            BEGIN
                SET @sqlString = ' DROP SYMMETRIC KEY ' + @key_name
                BEGIN TRY
                    EXECUTE sp_executesql @sqlString
                END TRY
                BEGIN CATCH
                    
                END CATCH
                SET @count = @count + 1
                IF @count = @delete_batch_size
                    BREAK

                FETCH NEXT FROM key_cursor INTO @key_name
            END
            CLOSE key_cursor
            DEALLOCATE key_cursor

            OPEN cert_cursor
            FETCH NEXT FROM cert_cursor INTO @certificate_name

            SET @count = 0

            WHILE @@FETCH_STATUS = 0
            BEGIN
                SET @sqlString = ' DROP CERTIFICATE ' + @certificate_name
                BEGIN TRY
                    EXECUTE sp_executesql @sqlString
                END TRY
                BEGIN CATCH
                    
                END CATCH
                SET @count = @count + 1
                IF @count = @delete_batch_size
                    BREAK
        
                FETCH NEXT FROM cert_cursor INTO @certificate_name
            END

            CLOSE cert_cursor
            DEALLOCATE cert_cursor

            UPDATE [internal].[operations]
                SET [status] = 7,
                [end_time] = SYSDATETIMEOFFSET()
                WHERE [operation_id] = @operation_id
        END
        ELSE IF @cleanup_flag = 2
        BEGIN
            
            INSERT INTO [internal].[operations] (
                [operation_type],  
                [created_time], 
                [object_type],
                [object_id],
                [object_name],
                [status], 
                [start_time],
                [caller_sid], 
                [caller_name]
                )
            VALUES (
                2,
                SYSDATETIMEOFFSET(),
                NULL,                     
                NULL,                     
                NULL,                     
                1,      
                SYSDATETIMEOFFSET(),
                @caller_sid,            
                @caller_name            
                ) 
            SET @operation_id = SCOPE_IDENTITY() 

            DECLARE @id                      [internal].[adt_name]

            DECLARE @project_id                    bigint

            DECLARE key_cursor CURSOR LOCAL FOR
                SELECT [name] FROM [sys].[symmetric_keys]
                WHERE [name] like 'MS_Enckey_Proj_Param_%'

            DECLARE cert_cursor CURSOR LOCAL FOR
                SELECT [name] FROM [sys].[certificates]
                WHERE [name] like 'MS_Cert_Proj_Param_%'

            OPEN key_cursor
            FETCH NEXT FROM key_cursor INTO @key_name

            WHILE @@FETCH_STATUS = 0
            BEGIN
                SET @id = SubString(@key_name,PATINDEX('%[0-9]%',@key_name),Len(@key_name))
                SET @project_id = CONVERT(int,@id) 
                SET @sqlString = ' DROP SYMMETRIC KEY ' + @key_name
                BEGIN TRY
                    IF @server_operation_encryption_level = 1
                    BEGIN
                        EXECUTE sp_executesql @sqlString
                    END
                    ELSE IF @server_operation_encryption_level = 2
                    BEGIN
                        IF (NOT EXISTS (SELECT [project_id] FROM [internal].[projects]
                            WHERE [project_id] = @project_id)) AND
                            (NOT EXISTS (SELECT [operation_id] FROM [internal].[operations] 
                            WHERE [object_id] = @project_id AND [operation_type] = 200))
                        EXECUTE sp_executesql @sqlString
                    END

                END TRY
                BEGIN CATCH
                    
                END CATCH
                
                FETCH NEXT FROM key_cursor INTO @key_name
            END
            CLOSE key_cursor
            DEALLOCATE key_cursor

            OPEN cert_cursor
            FETCH NEXT FROM cert_cursor INTO @certificate_name

            WHILE @@FETCH_STATUS = 0
            BEGIN
                SET @id = SubString(@certificate_name,PATINDEX('%[0-9]%',@certificate_name),Len(@certificate_name))
                SET @project_id = CONVERT(int,@id) 

                SET @sqlString = ' DROP CERTIFICATE ' + @certificate_name
                BEGIN TRY
                    IF @server_operation_encryption_level = 1
                    BEGIN
                        EXECUTE sp_executesql @sqlString
                    END
                    ELSE IF @server_operation_encryption_level = 2
                    BEGIN
                        IF (NOT EXISTS (SELECT [project_id] FROM [internal].[projects]
                            WHERE [project_id] = @project_id)) AND
                            (NOT EXISTS (SELECT [operation_id] FROM [internal].[operations] 
                            WHERE [object_id] = @project_id AND [operation_type] = 200))
                        EXECUTE sp_executesql @sqlString
                    END
                END TRY
                BEGIN CATCH
                    
                END CATCH
        
                FETCH NEXT FROM cert_cursor INTO @certificate_name
            END

            CLOSE cert_cursor
            DEALLOCATE cert_cursor

            UPDATE [internal].[operations]
                SET [status] = 7,
                [end_time] = SYSDATETIMEOFFSET()
                WHERE [operation_id] = @operation_id
        END
    END TRY
    BEGIN CATCH
        
        IF (CURSOR_STATUS('local', 'key_cursor') = 1
            OR CURSOR_STATUS('local', 'key_cursor') = 0)
        BEGIN
            CLOSE key_cursor
            DEALLOCATE key_cursor
        END


        IF (CURSOR_STATUS('local', 'cert_cursor') = 1
            OR CURSOR_STATUS('local', 'cert_cursor') = 0)
        BEGIN
            CLOSE cert_cursor
            DEALLOCATE cert_cursor
        END

        UPDATE [internal].[operations]
            SET [status] = 4,
            [end_time] = SYSDATETIMEOFFSET()
            WHERE [operation_id] = @operation_id;
        THROW;
    END CATCH
    
    RETURN 0
 GO
IF (OBJECT_ID('[internal].[add_replica_info]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[add_replica_info]
END
GO

CREATE PROCEDURE[internal].[add_replica_info]
	@server_name 	nvarchar(256),
	@state		tinyint
AS
BEGIN
	SET NOCOUNT ON

	IF (@server_name IS NULL OR @state IS NULL)
	BEGIN
		RAISERROR(27138, 16 , 6) WITH NOWAIT 
        RETURN 1 
	END
	
	IF (@state NOT IN (1,2))
	BEGIN
		RAISERROR(27101, 16 , 1, N'state') WITH NOWAIT 
        RETURN 1 
	END

	
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY 
		IF NOT EXISTS (SELECT * FROM [internal].[alwayson_support_state] WHERE [server_name] = @server_name)
			INSERT INTO [internal].[alwayson_support_state]([server_name],[state]) VALUES (@server_name,@state)
		ELSE
		BEGIN
			RAISERROR(27227, 16, 1, @server_name) WITH NOWAIT 
			RETURN 1
		END
			
	
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        THROW;
    END CATCH
	
	RETURN 0
END
GO
IF (OBJECT_ID('[internal].[delete_replica_info]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[delete_replica_info]
END
GO

CREATE PROCEDURE [internal].[delete_replica_info]
	@server_name		nvarchar(256)
AS
BEGIN
	SET NOCOUNT ON

	IF (@server_name IS NULL)
	BEGIN
		RAISERROR(27138, 16 , 6) WITH NOWAIT 
        RETURN 1 
	END

	
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY
		IF EXISTS (SELECT * FROM [internal].[alwayson_support_state] WHERE [server_name] = @server_name)
			DELETE FROM [internal].[alwayson_support_state] WHERE [server_name] = @server_name
		ELSE
		BEGIN
			RAISERROR(27228, 16, 1, @server_name) WITH NOWAIT 
			RETURN 1
		END
	
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        THROW;
    END CATCH
	
	RETURN 0
END
GO
IF (OBJECT_ID('[internal].[update_replica_info]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[update_replica_info]
END
GO

CREATE PROCEDURE [internal].[update_replica_info]
	@server_name		nvarchar(256)
AS
BEGIN
	SET NOCOUNT ON
  
	IF (@server_name IS NULL)
	BEGIN
		RAISERROR(27138, 16 , 6) WITH NOWAIT 
        RETURN 1 
	END
	
	
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY 
		IF EXISTS (SELECT * FROM [internal].[alwayson_support_state] WHERE [server_name] = @server_name)
		BEGIN
			MERGE INTO [internal].[alwayson_support_state] AS T
			USING (VALUES(@server_name)) AS S(new_primary_name)
			ON T.[server_name] = S.[new_primary_name]
			WHEN MATCHED 
			THEN UPDATE SET [state] = 1
			WHEN NOT MATCHED BY SOURCE
			THEN UPDATE SET [state] = 2;
		END
		ELSE
		BEGIN
			RAISERROR(27228, 16, 1, @server_name) WITH NOWAIT 
			RETURN 1
		END
	
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        THROW;
    END CATCH
	
	RETURN 0
END
GO
IF (OBJECT_ID('[internal].[refresh_replica_status]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[refresh_replica_status]
END
GO

CREATE PROCEDURE [internal].[refresh_replica_status]
	@server_name		nvarchar(256),
	@status				tinyint output
AS
BEGIN
	SET NOCOUNT ON
	
	IF (@server_name IS NULL)
	BEGIN
		RAISERROR(27138, 16 , 6) WITH NOWAIT 
        RETURN 1 
	END
	
	DECLARE @last_role tinyint
	SET @last_role = (SELECT [state]
					  FROM [internal].[alwayson_support_state]
					  WHERE [server_name] = @server_name)
	
	IF (@last_role IS NULL)
	BEGIN
		RAISERROR(27228, 16, 1, @server_name) WITH NOWAIT 
		RETURN 1
	END

	IF (@last_role = 2)
	BEGIN
		EXEC [internal].[update_replica_info] @server_name = @server_name
		SET @status = 1
	END
	ELSE
		SET @status = 0
	
	RETURN 0
END
GO
IF (OBJECT_ID('[internal].[prepare_execution]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[prepare_execution]
END
GO

CREATE PROCEDURE [internal].[prepare_execution]
        @execution_id       bigint,         
        @project_id         bigint output,  
        @version_id         bigint output,  
        @use32bitruntime    bit output      
AS 
    SET NOCOUNT ON
    DECLARE @result bit
    DECLARE @project_name nvarchar(128)
    DECLARE @folder_name nvarchar(128)

    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY
        
        DECLARE @id bigint
        EXECUTE AS CALLER
            SELECT @id = [operation_id] FROM [catalog].[operations]
                WHERE [operation_id] = @execution_id 
                  AND [object_type] = 20
                  AND [operation_type] = 200
        REVERT    
        
        IF @id IS NULL
        BEGIN
            RAISERROR(27103 , 16 , 1, @execution_id) WITH NOWAIT
        END
        
        
        
        SET @result = [internal].[check_permission] 
            (
                4,
                @execution_id,
                2
            ) 
        
        IF @result = 0
        BEGIN
            RAISERROR(27103 , 16 , 1, @execution_id) WITH NOWAIT        
        END
                   
        
        SELECT @project_id = [object_id],
               @project_name = [project_name],
               @version_id = [project_lsn], 
               @folder_name = [folder_name],
               @use32bitruntime = [use32bitruntime]
            FROM [internal].[execution_info]
            WHERE [execution_id] = @execution_id 
              AND [status] = 1
              AND [object_type] = 20
              AND [operation_type] = 200
        
        IF (@project_id IS NULL)
        BEGIN
            RAISERROR(27121 , 16 , 1) WITH NOWAIT
        END

        

        SELECT @folder_name = fd.[name]
        FROM [catalog].[projects] proj INNER JOIN
            [catalog].[folders] fd ON proj.[folder_id] = fd.[folder_id] WHERE
            fd.[name] = @folder_name AND proj.[project_id] = @project_id
        
        IF @folder_name IS NULL
        BEGIN
            RAISERROR(27109 , 16 , 1, @project_name) WITH NOWAIT        
        END

        
        SET @result = [internal].[check_permission] 
            (
                2,
                @project_id,
                3
            ) 

        
        IF @result = 0
        BEGIN
            RAISERROR(27178 , 16 , 1, @project_name) WITH NOWAIT        
        END
        
        
        DECLARE @version_current bigint
        SET @version_current = (SELECT [object_version_lsn] 
                                    FROM [internal].[projects]
                                    WHERE [project_id] = @project_id)
        
        IF (@version_current <> @version_id)
        BEGIN
            RAISERROR(27150 , 16 , 1) WITH NOWAIT
        END
        
        
        IF EXISTS (SELECT [execution_parameter_id] 
            FROM [internal].[execution_parameter_values]
            WHERE [execution_id] = @execution_id AND [required] = 1 AND [value_set] = 0)
        BEGIN
            RAISERROR(27184 , 16 , 1) WITH NOWAIT 
        END
        
        
        EXEC [internal].[set_system_informations]
              @execution_id

        
        UPDATE [internal].[operations]
        SET [status] = 5,
            [start_time] = SYSDATETIMEOFFSET(),
            [server_name] = CONVERT(sysname, SERVERPROPERTY('servername')),
            [machine_name] = 
            CASE  
                WHEN  EXISTS (SELECT * FROM [internal].[executions] WHERE [execution_id]=@execution_id AND [job_id] IS NULL) THEN  CONVERT(sysname, SERVERPROPERTY('machinename'))
                ELSE N''
            END
        WHERE [operation_id] = @execution_id
        IF @@ROWCOUNT <> 1
        BEGIN
            RAISERROR(27112, 16, 1, N'operations') WITH NOWAIT
        END       
            
        
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        THROW;
    END CATCH
    
    RETURN 0
GO
IF (OBJECT_ID('[internal].[get_execution_values]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[get_execution_values]
END
GO

CREATE PROCEDURE [internal].[get_execution_values]
        @execution_id       bigint         
WITH EXECUTE AS 'AllSchemaOwner'
AS 
    SET NOCOUNT ON
    DECLARE @result int
    
    DECLARE @sqlString              nvarchar(1024)
    DECLARE @key_name               [internal].[adt_name]
    DECLARE @certificate_name       [internal].[adt_name]
    DECLARE @encryption_algorithm   nvarchar(255)
    DECLARE @server_operation_encryption_level int
    DECLARE @project_id             bigint
    
    IF (@execution_id IS NULL)
    BEGIN
        RAISERROR(27138, 16 , 1) WITH NOWAIT 
        RETURN 1 
    END   

    IF @execution_id <= 0
    BEGIN
        RAISERROR(27101, 16 , 1, N'execution_id') WITH NOWAIT
        RETURN 1;
    END
    
    EXECUTE AS CALLER   
        SET @result = [internal].[check_permission] 
            (
                4,
                @execution_id,
                2
            ) 
    REVERT
    
    IF @result = 0
    BEGIN
        RAISERROR(27103 , 16 , 1, @execution_id) WITH NOWAIT        
        RETURN 1
    END  
    
    IF NOT EXISTS (SELECT [operation_id] FROM [internal].[operations] 
            WHERE [operation_id]= @execution_id AND [operation_type] = 200)
    BEGIN
        RAISERROR(27103 , 16 , 1, @execution_id) WITH NOWAIT        
        RETURN 1
    END  
 

    SELECT @server_operation_encryption_level = CONVERT(int,property_value)  
        FROM [catalog].[catalog_properties]
        WHERE property_name = 'SERVER_OPERATION_ENCRYPTION_LEVEL'

    IF @server_operation_encryption_level NOT in (1, 2)     
    BEGIN
        RAISERROR(27163    ,16,1,'SERVER_OPERATION_ENCRYPTION_LEVEL') WITH NOWAIT
        RETURN 1
    END
 
    SELECT @project_id = [object_id]
        FROM [internal].[operations]
        WHERE [operation_id] = @execution_id

    IF (@project_id IS NULL)
    BEGIN
        RAISERROR(27103 , 16 , 1, @execution_id) WITH NOWAIT
        RETURN 1
    END

    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY 
        IF @server_operation_encryption_level = 1 
        BEGIN
        SET @key_name = 'MS_Enckey_Exec_'+CONVERT(varchar,@execution_id)
        SET @certificate_name = 'MS_Cert_Exec_'+CONVERT(varchar,@execution_id) 
        END
        ELSE BEGIN
            SET @key_name = 'MS_Enckey_Proj_Param_'+CONVERT(varchar,@project_id)
            SET @certificate_name = 'MS_Cert_Proj_Param_'+CONVERT(varchar,@project_id)
        END
 
        SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
            + ' DECRYPTION BY CERTIFICATE ' + @certificate_name  
        EXECUTE sp_executesql @sqlString
        
        SELECT [execution_id],
               [object_type],
               [parameter_data_type],
               [parameter_name],
               [parameter_value] AS value,
               [sensitive],
               [required],
               [runtime_override]
        FROM internal.[execution_parameter_values]
        WHERE [execution_id] = @execution_id 
              AND [sensitive] = 0
              AND [value_set] = 1
        UNION
        SELECT [execution_id],
               [object_type],
               [parameter_data_type],
               [parameter_name],
               [internal].[get_value_by_data_type](DECRYPTBYKEY([sensitive_parameter_value]),[parameter_data_type]) AS value,
               [sensitive],
               [required],
               [runtime_override]
        FROM internal.[execution_parameter_values]
        WHERE [execution_id] = @execution_id 
              AND [sensitive] = 1
              AND [value_set] = 1
         
        SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString             
        
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        SET @sqlString = 'IF EXISTS (SELECT key_name FROM sys.openkeys WHERE key_name = ''' + @key_name +''') ' 
                    + 'CLOSE SYMMETRIC KEY '+ @key_name
        EXECUTE sp_executesql @sqlString;
        THROW;
    END CATCH
     
    RETURN 0       
GO
IF (OBJECT_ID('[internal].[get_execution_property_override_values]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[get_execution_property_override_values]
END
GO

CREATE PROCEDURE [internal].[get_execution_property_override_values]
        @execution_id       bigint         
WITH EXECUTE AS 'AllSchemaOwner'
AS 
    SET NOCOUNT ON
    DECLARE @result int
    
    DECLARE @sqlString              nvarchar(1024)
    DECLARE @key_name               [internal].[adt_name]
    DECLARE @certificate_name       [internal].[adt_name]
    DECLARE @encryption_algorithm   nvarchar(255)
    DECLARE @server_operation_encryption_level int
    DECLARE @project_id bigint
    
    IF (@execution_id IS NULL)
    BEGIN
        RAISERROR(27138, 16 , 1) WITH NOWAIT 
        RETURN 1 
    END   

    IF @execution_id <= 0
    BEGIN
        RAISERROR(27101, 16 , 1, N'execution_id') WITH NOWAIT
        RETURN 1;
    END
    
    EXECUTE AS CALLER   
        SET @result = [internal].[check_permission] 
            (
                4,
                @execution_id,
                2
            ) 
    REVERT
    
    IF @result = 0
    BEGIN
        RAISERROR(27103 , 16 , 1, @execution_id) WITH NOWAIT
        RETURN 1
    END  
    SELECT @server_operation_encryption_level = CONVERT(int,property_value)  
        FROM [catalog].[catalog_properties]
        WHERE property_name = 'SERVER_OPERATION_ENCRYPTION_LEVEL'

    IF @server_operation_encryption_level NOT in (1, 2)        
    BEGIN
        RAISERROR(27163    ,16,1,'SERVER_OPERATION_ENCRYPTION_LEVEL') WITH NOWAIT
        RETURN 1
    END  
    
    SELECT @project_id = [object_id] FROM [internal].[operations]
        WHERE [operation_id] = @execution_id

    
    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY 
        IF @server_operation_encryption_level = 1 
        BEGIN
        SET @key_name = 'MS_Enckey_Exec_'+CONVERT(varchar,@execution_id)
        SET @certificate_name = 'MS_Cert_Exec_'+CONVERT(varchar,@execution_id) 
        END
        ELSE BEGIN
            SET @key_name = 'MS_Enckey_Proj_Param_'+CONVERT(varchar,@project_id)
            SET @certificate_name = 'MS_Cert_Proj_Param_'+CONVERT(varchar,@project_id) 
        END

 
        SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
            + ' DECRYPTION BY CERTIFICATE ' + @certificate_name  
        EXECUTE sp_executesql @sqlString
        
        SELECT [property_path],
               [property_value]
        FROM internal.[execution_property_override_values]
        WHERE [execution_id] = @execution_id 
              AND [sensitive] = 0
        UNION
        SELECT [property_path],
               CONVERT(NVARCHAR, DECRYPTBYKEY([sensitive_property_value])) AS property_value
        FROM internal.[execution_property_override_values]
        WHERE [execution_id] = @execution_id 
              AND [sensitive] = 1
         
        SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString             
        
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        SET @sqlString = 'IF EXISTS (SELECT key_name FROM sys.openkeys WHERE key_name = ''' + @key_name +''') ' 
                    + 'CLOSE SYMMETRIC KEY '+ @key_name
        EXECUTE sp_executesql @sqlString;
        THROW;
    END CATCH
     
    RETURN 0       
GO
IF (OBJECT_ID('[catalog].[startup]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[startup]
END
GO

CREATE PROCEDURE [catalog].[startup]
WITH EXECUTE AS 'AllSchemaOwner'
AS 
    SET NOCOUNT ON
    DECLARE @operation_id  bigint
    DECLARE @caller_name   nvarchar(256)
    DECLARE @caller_sid    varbinary(85)
    DECLARE @permission_denied smallint
    
    EXECUTE AS CALLER                       
        SET @caller_name =  SUSER_NAME()
        SET @caller_sid =   SUSER_SID()
        SET @permission_denied = 0
        
        
        IF (IS_MEMBER('ssis_admin') <> 1 AND IS_MEMBER('dbo') <> 1
               AND IS_SRVROLEMEMBER('sysadmin') <> 1) AND IS_MEMBER('ssis_failover_monitoring_agent') <> 1
        BEGIN
            SET @permission_denied = 1
        END
    REVERT
    
    IF (@permission_denied = 1)
    BEGIN
        RAISERROR(27219, 16, 1) WITH NOWAIT
        RETURN 1
    END
    
    
    INSERT INTO [internal].[operations] (
        [operation_type],  
        [created_time], 
        [object_type],
        [object_id],
        [object_name],
        [status], 
        [start_time],
        [caller_sid], 
        [caller_name]
        )
    VALUES (
        1,
        SYSDATETIMEOFFSET(),
        NULL,
        NULL,                     
        NULL,                     
        1,      
        SYSDATETIMEOFFSET(),
        @caller_sid,            
        @caller_name            
        )

    SET @operation_id = SCOPE_IDENTITY();

    DECLARE @ret_val int;
    
    DECLARE @marked_ops TABLE(marked_op_id bigint, marked_op_type smallint, marked_op_status int);

    
    BEGIN TRY

         
        UPDATE [internal].[operations] 
        SET [status] = 6,
        [end_time] = SYSDATETIMEOFFSET()
        OUTPUT DELETED.operation_id, DELETED.operation_type, DELETED.status INTO @marked_ops
        WHERE ([operation_type] = 200)
            AND ([status] = 8);


        
        UPDATE ext
        SET ext.[status] = 6,
        ext.[end_time] = SYSDATETIMEOFFSET()
        FROM [internal].[operations] opers INNER JOIN [internal].[extended_operation_info] ext
            ON opers.[operation_id] = ext.[operation_id] 
            WHERE (opers.[status] IN (2, 5))
            AND opers.[operation_type] IN( 301, 300)
            AND ext.[status] NOT IN (7, 3, 6)
            AND opers.[process_id] NOT IN (SELECT process_id FROM [internal].[get_isserver_processes]())


        
        UPDATE [internal].[operations] 
        SET [status] = 6,
        [end_time] = SYSDATETIMEOFFSET()
        OUTPUT DELETED.operation_id, DELETED.operation_type, DELETED.status INTO @marked_ops
        WHERE ([operation_type] IN( 301, 200, 300))
            AND ([status] IN (2, 5))
            AND ([process_id] NOT IN (SELECT process_id FROM [internal].[get_isserver_processes]()) OR [process_id] IS NULL)

        
        UPDATE [internal].[operations] 
        SET [status] = 6,
        [end_time] = SYSDATETIMEOFFSET()
        OUTPUT DELETED.operation_id, DELETED.operation_type, DELETED.status INTO @marked_ops
        WHERE ([operation_type] IN ( 202, 106, 
            1000, 2, 3, 700))
            AND ([status] IN (2, 5))
        
        
        UPDATE [internal].[operations] 
        SET [status] = 6,
        [end_time] = SYSDATETIMEOFFSET()
        OUTPUT DELETED.operation_id, DELETED.operation_type, DELETED.status INTO @marked_ops
        WHERE ([operation_type] IN( 101))
            AND ([status] IN (2, 5))
        
        
        DECLARE @project_id bigint
        DECLARE @sqlString    nvarchar(1024)
        DECLARE @key_name               [internal].[adt_name]
        DECLARE @certificate_name     [internal].[adt_name]
        
        
        
        DECLARE temp_project_cursor CURSOR LOCAL FOR
            SELECT [project_id] FROM [internal].[projects]
            WHERE [object_version_lsn] = -1
        
        OPEN temp_project_cursor
        FETCH NEXT FROM temp_project_cursor INTO @project_id
        WHILE @@FETCH_STATUS = 0
        BEGIN
            
            
    SET @key_name = 'MS_Enckey_Proj_'+CONVERT(varchar,@project_id)
    SET @certificate_name = 'MS_Cert_Proj_'+CONVERT(varchar,@project_id)
    SET @sqlString = 'IF EXISTS (SELECT name FROM sys.symmetric_keys WHERE name = ''' + @key_name +''') '
        +'DROP SYMMETRIC KEY '+ @key_name
        EXECUTE sp_executesql @sqlString
    SET @sqlString = 'IF EXISTS (select name from sys.certificates WHERE name = ''' + @certificate_name +''') '
        +'DROP CERTIFICATE '+ @certificate_name
        EXECUTE sp_executesql @sqlString
            
            DELETE FROM [internal].[catalog_encryption_keys]
                WHERE key_name = @key_name
                
            DELETE FROM [internal].[projects]
                WHERE [object_version_lsn] = -1 
                AND [project_id] = @project_id
            FETCH NEXT FROM temp_project_cursor INTO @project_id             
        END
        CLOSE temp_project_cursor
        DEALLOCATE temp_project_cursor
        
        
        DELETE FROM [internal].[object_versions]
            WHERE [object_status] = 'D'         
        
        
        
        DECLARE @client_language nvarchar(256)
        DECLARE @server_language nvarchar(256)
        
        SET @client_language = @@LANGUAGE
        SELECT @server_language = [name] FROM sys.syslanguages WHERE [lcid] = SERVERPROPERTY('LCID')
        SET LANGUAGE @server_language

        INSERT INTO [internal].[operation_messages] (
            [operation_id],
            [message_type],
            [message_time],
            [message_source_type],
            [message]
            )
        SELECT
            @operation_id,
            110,
            SYSDATETIMEOFFSET(),
            10,
            FORMATMESSAGE (27165, marked_op_id, marked_op_type, marked_op_status)
        FROM @marked_ops
        
        SET LANGUAGE @client_language
        
        UPDATE [internal].[operations]
            SET [status] = 7,
            [end_time] = SYSDATETIMEOFFSET()
            WHERE [operation_id] = @operation_id; 
        
    END TRY
    BEGIN CATCH
        
        IF (CURSOR_STATUS('local', 'temp_project_cursor') = 1 
            OR CURSOR_STATUS('local', 'temp_project_cursor') = 0)
        BEGIN
            CLOSE temp_project_cursor
            DEALLOCATE temp_project_cursor            
        END
        
        EXEC [internal].[insert_message_caught] @operation_id;
        UPDATE [internal].[operations]
            SET [status] = 4,
            [end_time] = SYSDATETIMEOFFSET()
            WHERE [operation_id] = @operation_id; 
        RETURN 1
    END CATCH
    
    RETURN 0

GO
IF (OBJECT_ID('[internal].[update_cancelled_operation_status]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[update_cancelled_operation_status]
END
GO

CREATE PROCEDURE [internal].[update_cancelled_operation_status]
		@operation_id	bigint,		
		@stop_id		bigint		
AS
	SET NOCOUNT ON
    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY
		DECLARE @stopped_by_sid		varbinary(85)
		DECLARE @stopped_by_name	nvarchar(128)
		SELECT @stopped_by_sid = [caller_sid], @stopped_by_name = [caller_name]
		FROM [internal].[operations]
		WHERE [operation_id] = @stop_id

		
		UPDATE [internal].[operations]
		SET [stopped_by_sid] = @stopped_by_sid, [stopped_by_name] = @stopped_by_name, [status] = 3, [end_time] = SYSDATETIMEOFFSET()
		WHERE [operation_id] = @operation_id

		
		UPDATE [internal].[operations]
		SET [status] = 7, [end_time] = SYSDATETIMEOFFSET()
		WHERE [operation_id] = @stop_id

		
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY

    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           

        THROW
    END CATCH

    RETURN 0
GO
IF (OBJECT_ID('[internal].[update_operation_status_for_task]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[update_operation_status_for_task]
END
GO

CREATE PROCEDURE [internal].[update_operation_status_for_task]
	@status     int, 
    @operation_id bigint,
    @agent_id   UNIQUEIDENTIFIER = NULL
AS
BEGIN
    IF @status = 2 
    BEGIN
        UPDATE [internal].[operations]
        SET [machine_name] = w.[MachineName], [status] = @status, [worker_agent_id] = @agent_id,  [executed_count] = e.[ExecutedCount] 
        FROM (SELECT [MachineName] FROM [internal].[worker_agents]
        WHERE [WorkerAgentId] = @agent_id) w,
        (SELECT  [ExecutedCount] FROM [internal].[tasks] t INNER JOIN [internal].[executions] e ON t.JobId = e.job_id 
        WHERE execution_id = @operation_id) e
        WHERE [operation_id] = @operation_id
    END
    ELSE IF @status in (3, 4, 6, 7, 9)
    BEGIN
        UPDATE [internal].[operations] 
        SET [status] = @status, 
        [end_time] = SYSDATETIMEOFFSET()
        WHERE [operation_id] = @operation_id
    END    
END
GO

GRANT EXECUTE ON [internal].[update_operation_status_for_task] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[internal].[create_execution_job]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[create_execution_job]
END
GO

CREATE PROCEDURE [internal].[create_execution_job]
		@execution_id bigint,		 
		@caller_name  nvarchar(256), 
		@retry_count  int,	     
		@execution_agent_id uniqueidentifier = null   
WITH EXECUTE AS 'AllSchemaOwner'
AS
	SET NOCOUNT ON

	DECLARE @return_value bit
	DECLARE @input_data nvarchar(max)
	DECLARE	@job_id uniqueidentifier 
	DECLARE	@task_id uniqueidentifier 
	DECLARE @max_executed_count int 
	DECLARE @use32bitruntime bit
	DECLARE @operation_guid uniqueidentifier

	SELECT @use32bitruntime = [use32bitruntime], @job_id = [job_id] 
	FROM [internal].[executions] WHERE [execution_id]=@execution_id
	
	SELECT @operation_guid = [operation_guid]
	FROM [internal].[operations] WHERE [operation_id] = @execution_id
	
	
	
	CREATE TABLE #inputs (name nvarchar(20), value nvarchar(256))
	INSERT INTO #inputs values ('execution_id', CONVERT(nvarchar(256), @execution_id))
	INSERT INTO #inputs values ('use32bitruntime', CONVERT(nvarchar(256), @use32bitruntime))
	INSERT INTO #inputs values ('operation_guid', @operation_guid)

	SET @input_data = (select name, value FROM #inputs For JSON PATH)

	IF @job_id IS NULL
	BEGIN
		RAISERROR(27255, 16, 1) WITH NOWAIT
		RETURN 1
	END
	
	SET @max_executed_count = @retry_count + 1
	EXEC @return_value = [internal].[create_task]
			@task_id out,
			@job_id,
			0,
			@max_executed_count,
			@input_data,
			null,
			@execution_agent_id

	if(@return_value <> 0)
	BEGIN
		RAISERROR(27245, 16, 1) WITH NOWAIT
		RETURN 1
	END
		
GO	
IF (OBJECT_ID('[internal].[append_event_message]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[append_event_message]
END
GO

CREATE PROCEDURE [internal].[append_event_message]
        @operation_id       bigint,                             
        @message_type       int,                                
        @message_time         datetimeoffset,                     
        @message_source       smallint,                           
        @message              nvarchar(max),                      
        @extended_info_id     bigint = NULL,
        @package_name         nvarchar(260),
        @package_location_type nvarchar(128),
	@package_path_full    nvarchar(4000),
        @event_name           nvarchar(1024),
        @message_source_name  nvarchar(4000),
        @message_source_id    nvarchar(38),
        @subcomponent_name    nvarchar(4000),
        @package_path         nvarchar(MAX),
        @execution_path       nvarchar(MAX),
        @thread_id            int,
        @message_code         int,
        @event_message_id     bigint output,
        @event_message_guid   UniqueIdentifier output
AS
SET NOCOUNT ON

    DECLARE @operation_message_id   bigint    

    IF [internal].[check_permission] 
    (
        4,
        @operation_id,
        2
    ) = 0
    BEGIN
        RAISERROR(27143, 16, 5, @operation_id) WITH NOWAIT;
        RETURN 1;      
    END
    set @event_message_guid = NEWID()

    INSERT INTO [internal].[operation_messages] 
           ([operation_id], 
            [message_type], 
            [message_time],
            [message_source_type], 
            [message], 
            [extended_info_id],
            [event_message_guid])
        VALUES(
            @operation_id,  
            @message_type,
            @message_time,
            @message_source,
            @message,
            @extended_info_id,
            @event_message_guid)
            
    SET @operation_message_id = SCOPE_IDENTITY()

    INSERT INTO [internal].[event_messages]
           ([operation_id],
           [event_message_id],
           [package_name],
	   [package_location_type],
	   [package_path_full],
           [event_name],
           [message_source_name],
           [message_source_id],
           [subcomponent_name],
           [package_path],
           [execution_path],
           [threadID],
           [message_code],
           [event_message_guid])
     VALUES
           (
           @operation_id,
           @operation_message_id,
           @package_name,
           @package_location_type,
           @package_path_full,
           @event_name,
           @message_source_name,
           @message_source_id,
           @subcomponent_name,
           @package_path,
           @execution_path,
           @thread_id,
           @message_code,
           @event_message_guid
           )
    SET @event_message_id =  @operation_message_id
    RETURN 0
GO
IF (OBJECT_ID('[internal].[append_message_context]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[append_message_context]
END
GO

CREATE PROCEDURE [internal].[append_message_context]
        @operation_id       bigint,                             
        @event_message_id   bigint,                             
        @context_depth      int,                                
        @package_path       nvarchar(MAX),
        @context_type       smallint,
        @context_source_name nvarchar(MAX),
        @context_source_id   nvarchar(38),
        @property_name       nvarchar(4000),
        @property_value      sql_variant,
        @event_message_guid uniqueIdentifier
AS
SET NOCOUNT ON

    IF [internal].[check_permission] 
    (
        4,
        @operation_id,
        2
    ) = 0
    BEGIN
        RAISERROR(27143, 16, 5, @operation_id) WITH NOWAIT;
        RETURN 1;      
    END

    INSERT INTO [internal].[event_message_context]
           ([operation_id],
           [event_message_id],
           [context_depth],
           [package_path],
           [context_type],
           [context_source_name],
           [context_source_id],
           [property_name],
           [property_value],
           [event_message_guid])
     VALUES(
              @operation_id,
              @event_message_id,
              @context_depth,
              @package_path,
              @context_type,
              @context_source_name,
              @context_source_id,
              @property_name,
              @property_value,
              @event_message_guid 
           )
    RETURN 0
GO
IF (OBJECT_ID('[internal].[prepare_stop]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[prepare_stop]
END
GO

CREATE PROCEDURE [internal].[prepare_stop]
        @operation_id bigint,              
        @process_id   int   output,              
        @operation_guid UniqueIdentifier output,  
        @stop_id    bigint output           
AS
    SET NOCOUNT ON
    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 11) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 11) WITH NOWAIT
            RETURN 1
    END
       
    DECLARE @operation_type smallint
    DECLARE @return_value int
    DECLARE @status int
    DECLARE @object_id bigint
    DECLARE @object_name nvarchar(260)
    
    INSERT INTO [internal].[operations] (
        [operation_type],  
        [created_time], 
        [object_type],
        [object_id],
        [object_name],
        [status], 
        [start_time],
        [caller_sid], 
        [caller_name]
        )
    VALUES (
        202,
        SYSDATETIMEOFFSET(),
        20,
        NULL,                     
        NULL,                     
        2,      
        SYSDATETIMEOFFSET(),
        @caller_sid,            
        @caller_name            
        )
            
    SET @stop_id = SCOPE_IDENTITY()

    EXECUTE AS CALLER
        EXEC @return_value = [internal].[init_object_permissions] 
                4, @stop_id, @caller_id 
    REVERT            
    IF @return_value <> 0
    BEGIN
        
        RAISERROR(27153, 16, 1) WITH NOWAIT
    END      
    
    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
        
    BEGIN TRY
        SELECT @operation_guid = [operation_guid],
               @process_id = [process_id],
               @status = [status],
               @object_id = [object_id],
               @object_name = [object_name],
               @operation_type = [operation_type]
        FROM   [internal].[operations] 
        WHERE  [operation_id] = @operation_id AND ([status] = 2 OR [status] = 8)
        AND ([operation_type] = 200 OR [operation_type] = 301
            OR [operation_type] = 300) 
            
        IF @operation_guid IS NULL OR @object_id IS NULL
        BEGIN
            RAISERROR(27124, 16 , 1, @operation_id) WITH NOWAIT
        END
        
        IF @status = 8
        BEGIN
            RAISERROR(27126, 16 , 1) WITH NOWAIT
        END
        
		DECLARE @job_id uniqueIdentifier = NULL
		IF @operation_type = 200
		BEGIN
		SELECT @job_id = [job_id] FROM [internal].[executions] WHERE [execution_id]=@operation_id
		END
		ELSE IF (@operation_type = 300 OR @operation_type = 301)
		BEGIN
		SELECT @job_id = [job_id] FROM [internal].[validations] WHERE [validation_id]=@operation_id
		END

        IF @process_id IS NULL AND @job_id IS NULL
        BEGIN
            RAISERROR(27125, 16 , 1) WITH NOWAIT
        END
        
        
        DECLARE @permission_ret bit
        EXECUTE AS CALLER
           SET @permission_ret = [internal].[check_permission]
           (
              4,
              @operation_id,      
              2
           )
        REVERT
        
        IF (@permission_ret = 0)
        BEGIN
            RAISERROR(27143, 16, 6, @operation_id) WITH NOWAIT
        END
        
        
        EXECUTE AS CALLER
           SET @permission_ret = [internal].[check_permission]
           (
              4,
              @operation_id,      
              1
           )
        REVERT
        
        IF (@permission_ret = 0)
        BEGIN
            RAISERROR(27143, 16, 6, @operation_id) WITH NOWAIT
        END
        
        
        UPDATE [internal].[operations]
            SET status = 8
        WHERE operation_id = @operation_id
            
        IF @@ROWCOUNT = 0
        BEGIN
            RAISERROR(27112, 16, 7) WITH NOWAIT
        END
        
        UPDATE [internal].[operations]
            SET [object_id] = @object_id,
                [object_name] = @object_name
        WHERE operation_id = @stop_id
        
        IF @@ROWCOUNT = 0
        BEGIN
            RAISERROR(27112, 16, 7) WITH NOWAIT
        END
         
        
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        UPDATE [internal].[operations]
            SET [status] = 4,
                [end_time] = SYSDATETIMEOFFSET()
        WHERE operation_id = @stop_id;              
        THROW;
    END CATCH
    RETURN 0
GO
IF (OBJECT_ID('[internal].[get_decrypted_parameter_values]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[get_decrypted_parameter_values]
END
GO

CREATE PROCEDURE [internal].[get_decrypted_parameter_values]
        @validation_id bigint,           
        @project_id bigint,              
        @version_id bigint,              
        @package_name nvarchar(260),      
        @reference_id bigint = NULL       
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON
    DECLARE @result bit
    DECLARE @environment_id bigint
    DECLARE @environment_found bit

    DECLARE @sqlString              nvarchar(1024)
    DECLARE @encryption_algorithm   nvarchar(255)

    DECLARE @env_key_name               [internal].[adt_name]
    DECLARE @env_certificate_name       [internal].[adt_name]
    
    DECLARE @project_key_name               [internal].[adt_name]
    DECLARE @project_certificate_name       [internal].[adt_name]
    
    SET @encryption_algorithm = (SELECT [internal].[get_encryption_algorithm]())
    
    IF @encryption_algorithm IS NULL
    BEGIN
        RAISERROR(27156, 16, 1, 'ENCRYPTION_ALGORITHM') WITH NOWAIT
    END    
    
    
    IF @project_id IS NULL OR @package_name IS NULL OR @validation_id IS NULL
    BEGIN
        RAISERROR(27138, 16 , 1) WITH NOWAIT 
        RETURN 1 
    END

    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY
        
        IF NOT EXISTS (SELECT [operation_id] FROM [internal].[operations] 
            WHERE [operation_id] = @validation_id 
            AND ([operation_type] = 300 
                OR [operation_type] = 301))
        BEGIN
            RAISERROR(27185, 16, 1, @validation_id) WITH NOWAIT          
        END
        
        
        EXECUTE AS CALLER   
            SET @result =  [internal].[check_permission] 
                (
                    4,
                    @validation_id,
                    1
                 ) 
        REVERT 
        IF @result = 0
        BEGIN
            RAISERROR(27185, 16, 1, @validation_id) WITH NOWAIT
        END
        
        
        IF EXISTS (SELECT [parameter_name]
            FROM [internal].[object_parameters] 
            WHERE [project_id] = @project_id
                AND [project_version_lsn] = @version_id
                AND ([object_type] = 20 
                OR ([object_name] = @package_name 
                AND [object_type] = 30))
                AND [required] = 1 AND [value_set] = 0) 
        BEGIN
            RAISERROR(27201, 16, 1) WITH NOWAIT
        END
              
        DECLARE @environment_name nvarchar(128)
        DECLARE @environment_folder_name nvarchar(128)
        DECLARE @reference_type char(1)
        
        IF (@reference_id IS NULL)
        BEGIN
            
            IF EXISTS (SELECT [parameter_name]
                FROM [internal].[object_parameters] 
                WHERE [project_id] = @project_id
                    AND [project_version_lsn] = @version_id
                    AND ([object_type] = 20 
                    OR ([object_name] = @package_name 
                    AND [object_type] = 30))
                    AND [value_type] = 'R')
            BEGIN
                RAISERROR(27202, 16, 1) WITH NOWAIT
            END
        END
        
        
        IF(@reference_id IS NOT NULL)
        BEGIN
            
            EXECUTE AS CALLER
                SELECT @environment_name = environment_name,
                       @environment_folder_name = environment_folder_name,
                       @reference_type = reference_type
                FROM [catalog].[environment_references]
                WHERE project_id = @project_id AND reference_id = @reference_id
            REVERT
            IF (@environment_name IS NULL)
            BEGIN
                RAISERROR(27208, 16, 1, @reference_id) WITH NOWAIT
            END                                                     
            
            
            SET @environment_found = 1
            IF (@reference_type = 'A')
            BEGIN
                SELECT @environment_id = envs.[environment_id]
                FROM [internal].[folders] fds INNER JOIN [internal].[environments] envs
                ON fds.[folder_id] = envs.[folder_id]
                WHERE envs.[environment_name] = @environment_name AND fds.[name] = @environment_folder_name
            END
            ELSE IF (@reference_type = 'R')
            BEGIN
                SELECT @environment_id = envs.[environment_id]
                FROM  [internal].[projects] projs INNER JOIN [internal].[environments] envs
                ON projs.[folder_id] = envs.[folder_id]
                WHERE envs.[environment_name] = @environment_name AND projs.[project_id] = @project_id
            END
            IF (@environment_id IS NULL)
            BEGIN
                SET @environment_found = 0
            END
            
            EXECUTE AS CALLER
                SET @result =  [internal].[check_permission]
                    (
                        3,
                        @environment_id,
                        1
                     )
            REVERT
            IF @result = 0
            BEGIN
                SET @environment_found = 0
            END
                    
            IF @environment_found = 0
            BEGIN
                RAISERROR(27182 , 16 , 1, @environment_name) WITH NOWAIT
            END          
            
            
            IF EXISTS 
            (
                SELECT params.[parameter_name]
                FROM [catalog].[object_parameters] params
                WHERE params.[value_type] = 'R'   
                AND params.[project_id] = @project_id
                AND (params.[object_type] = 20
                OR (params.[object_name] = @package_name
                AND params.[object_type] = 30))            
                AND params.[referenced_variable_name] NOT IN 
                (SELECT vars.[name] 
                    FROM [internal].[environments] envs INNER JOIN [internal].[environment_variables] vars
                        ON envs.[environment_id] = vars.[environment_id]
                    WHERE envs.[environment_id] = @environment_id )
                       
            )
            BEGIN
                RAISERROR(27207, 16, 1, @environment_name) WITH NOWAIT
            END              
            
            
            
            IF EXISTS 
            (
                SELECT params.[parameter_name]
                FROM [internal].[environments] envs INNER JOIN [internal].[environment_variables] vars
                ON envs.[environment_id] = vars.[environment_id] INNER JOIN [catalog].[object_parameters] params
                ON vars.[name] = params.[referenced_variable_name] 
                WHERE params.[value_type] = 'R' 
                AND params.[data_type] <> vars.[type] 
                AND params.[project_id] = @project_id
                AND (params.[object_type] = 20
                OR (params.[object_name] = @package_name
                AND params.[object_type] = 30))
                AND envs.[environment_id] = @environment_id         
            )
            BEGIN
                RAISERROR(27148, 16, 1) WITH NOWAIT
            END 
            
            
            IF EXISTS 
            (
                SELECT params.[parameter_name]
                FROM [internal].[environment_variables] vars INNER JOIN [catalog].[object_parameters] params
                ON vars.[name] = params.[referenced_variable_name] 
                WHERE params.[value_type] = 'R' AND params.[data_type] = vars.[type] 
                AND params.[sensitive] =0 AND vars.[sensitive] = 1
                AND params.[project_id] = @project_id
                AND (params.[object_type] = 20
                OR (params.[object_name] = @package_name
                AND params.[object_type] = 30))
                AND vars.[environment_id] = @environment_id          
            )
            BEGIN
                RAISERROR(27221, 16, 1) WITH NOWAIT
            END 
        END
        
        IF @environment_id IS NOT NULL
        BEGIN
            SET @env_key_name = 'MS_Enckey_Env_'+CONVERT(varchar,@environment_id)
            SET @env_certificate_name = 'MS_Cert_Env_'+CONVERT(varchar,@environment_id)
            
            SET @sqlString = 'OPEN SYMMETRIC KEY ' + @env_key_name 
                    + ' DECRYPTION BY CERTIFICATE ' + @env_certificate_name  
                EXECUTE sp_executesql @sqlString        
        END          
        
        
        SET @project_key_name = 'MS_Enckey_Proj_'+CONVERT(varchar,@project_id)
        SET @project_certificate_name = 'MS_Cert_Proj_'+CONVERT(varchar,@project_id)

        SET @sqlString = 'OPEN SYMMETRIC KEY ' + @project_key_name 
                + ' DECRYPTION BY CERTIFICATE ' + @project_certificate_name  
        EXECUTE sp_executesql @sqlString 

        
        
        DECLARE @result_set TABLE
        (
            [object_type] smallint, 
            [parameter_data_type] nvarchar(128),
            [parameter_name] nvarchar(128),
            [parameter_value] sql_variant,
            [sensitive]  bit,
            [required]  bit
        );            
            
        
        INSERT INTO @result_set
        SELECT  [object_type], 
                [parameter_data_type], 
                [parameter_name],
                [default_value], 
                [sensitive], 
                [required] 
        FROM [internal].[object_parameters] 
        WHERE [project_id] = @project_id 
        AND ([object_type] = 20 
        OR ([object_name] = @package_name 
        AND [object_type] = 30))
        AND [sensitive] = 0 
        AND [value_type] = 'V' 
        AND [project_version_lsn] = @version_id
        AND [value_set] = 1
        
        INSERT INTO @result_set
        SELECT  [object_type], 
                [parameter_data_type], 
                [parameter_name],
                [internal].[get_value_by_data_type](DECRYPTBYKEY(sensitive_default_value),[parameter_data_type]), 
                [sensitive], 
                [required]
        FROM [internal].[object_parameters] 
        WHERE [project_id] = @project_id 
        AND ([object_type] = 20 
        OR ([object_name] = @package_name 
        AND [object_type] = 30))
        AND sensitive = 1
        AND [value_set] = 1
        AND [value_type] = 'V' 
        AND [project_version_lsn] = @version_id   
        
        
        IF @environment_id IS NOT NULL
        BEGIN
            INSERT INTO @result_set
            SELECT  params.[object_type], 
                    params.[parameter_data_type], 
                    params.[parameter_name],
                    vars.[value], 
                    params.[sensitive], 
                    params.[required]           
            FROM [internal].[object_parameters] params 
            INNER JOIN [internal].[environment_variables] vars
                ON params.[referenced_variable_name] = vars.[name] 
            WHERE params.[project_id] = @project_id 
            AND (params.[object_type] = 20
            OR (params.[object_name] = @package_name 
            AND params.[object_type] = 30))
            AND vars.[sensitive] = 0 
            AND params.[value_type] = 'R' 
            AND params.[project_version_lsn] = @version_id
            AND vars.[environment_id] = @environment_id
            AND params.[value_set] = 1
            
            INSERT INTO @result_set
            SELECT  params.[object_type], 
                    params.[parameter_data_type], 
                    params.[parameter_name],
                    [internal].[get_value_by_data_type](DECRYPTBYKEY(vars.[sensitive_value]), vars.[type]), 
                    params.[sensitive], 
                    params.[required]           
            FROM [internal].[object_parameters] params 
            INNER JOIN [internal].[environment_variables] vars
                ON params.[referenced_variable_name] = vars.[name] 
            WHERE params.[project_id] = @project_id 
            AND (params.[object_type] = 20
            OR (params.[object_name] = @package_name 
            AND params.[object_type] = 30))
            AND vars.[sensitive] = 1 
            AND params.[value_type] = 'R' 
            AND params.[project_version_lsn] = @version_id
            AND vars.[environment_id] = @environment_id
            AND params.[value_set] = 1            
                           
        END        
        
        SELECT [object_type], 
            [parameter_data_type],
            [parameter_name],
            [parameter_value],
            [sensitive],
            [required]
        FROM @result_set             
        
        IF (@env_key_name <> '')
        BEGIN
            SET @sqlString = 'IF EXISTS (SELECT key_name FROM sys.openkeys WHERE key_name = ''' + @env_key_name +''') ' 
                  + 'CLOSE SYMMETRIC KEY '+ @env_key_name
            EXECUTE sp_executesql @sqlString
        END
        
        IF (@project_key_name <> '')
        BEGIN
            SET @sqlString = 'IF EXISTS (SELECT key_name FROM sys.openkeys WHERE key_name = ''' + @project_key_name +''') ' 
                  + 'CLOSE SYMMETRIC KEY '+ @project_key_name
            EXECUTE sp_executesql @sqlString
        END;
                
        
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        IF (@env_key_name <> '')
        BEGIN
            SET @sqlString = 'IF EXISTS (SELECT key_name FROM sys.openkeys WHERE key_name = ''' + @env_key_name +''') ' 
                  + 'CLOSE SYMMETRIC KEY '+ @env_key_name
            EXECUTE sp_executesql @sqlString
        END;
        
        IF (@project_key_name <> '')
        BEGIN
            SET @sqlString = 'IF EXISTS (SELECT key_name FROM sys.openkeys WHERE key_name = ''' + @project_key_name +''') ' 
                  + 'CLOSE SYMMETRIC KEY '+ @project_key_name
            EXECUTE sp_executesql @sqlString
        END;
        
        THROW;
    END CATCH
    
    RETURN 0    
            
GO
IF (OBJECT_ID('[internal].[prepare_validate_package]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[prepare_validate_package]
END
GO

CREATE PROCEDURE [internal].[prepare_validate_package] 
        @folder_name                     nvarchar(128),
        @project_name                    nvarchar(128),     
        @package_name                    nvarchar(260),          
        @use32bitruntime                 bit =0,            
        @environment_scope              char(1) = 'D',     
        @reference_id                    bigint = NULL,
        @validation_id                   bigint OUTPUT,
        @project_id                      bigint OUTPUT,
        @package_id                      bigint OUTPUT,
        @version_id                      bigint OUTPUT
AS
    SET NOCOUNT ON

    
    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
    END

    DECLARE @created_time    DATETIMEOFFSET
    DECLARE @return_value   int
    DECLARE @operation_id  bigint
    DECLARE @result bit
    
    BEGIN TRY
        SET @created_time = SYSDATETIMEOFFSET() 
        
        EXEC @return_value = [internal].[insert_operation] 
                        301,  
                        @created_time,          
                        30,    
                        NULL,                   
                        @package_name,          
                        5,    
                        @created_time,          
                        null,                   
                        @caller_sid,            
                        @caller_name,           
                        null,                   
                        null,                   
                        null,                   
                        @operation_id OUTPUT  
        IF @return_value <> 0
            RETURN 1;
        
        SET @validation_id = @operation_id
        
        EXEC @return_value = [internal].[init_object_permissions] 4, @operation_id, @caller_id
                      
        IF @return_value <> 0
        BEGIN
            
            RAISERROR(27153, 16, 1) WITH NOWAIT
            RETURN 1
        END 
    END TRY
    BEGIN CATCH
        UPDATE [internal].[operations] SET 
            [end_time]  = SYSDATETIMEOFFSET(),
            [status]    = 4
            WHERE operation_id    = @operation_id;
        THROW           
    END CATCH

    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY
        
        

        SELECT @project_id = projs.[project_id],  
               @version_id = projs.[object_version_lsn],
               @package_id = pkgs.[package_id]
            FROM [catalog].[projects] projs INNER JOIN [catalog].[folders] fds
            ON projs.[folder_id] = fds.[folder_id] INNER JOIN [catalog].[packages] pkgs
            ON projs.[project_id] = pkgs.[project_id] 
            WHERE fds.[name] = @folder_name AND projs.[name] = @project_name
            AND pkgs.[name] = @package_name
        
        IF (@project_id IS NULL OR @version_id IS NULL OR @package_id IS NULL)
        BEGIN
            RAISERROR(27146, 16, 1) WITH NOWAIT
        END
        
        INSERT into [internal].[validations]
               (
                  [validation_id],
                  [environment_scope],
                  [validate_type],
                  [folder_name],
                  [project_name],
                  [project_lsn],
                  [use32bitruntime],
                  [reference_id]
               )
        VALUES (
                  @validation_id,              
                  @environment_scope,
                  'F',                        
                  @folder_name,               
                  @project_name,              
                  @version_id,                
                  @use32bitruntime,           
                  @reference_id               
               )
        
        UPDATE [internal].[operations]
            SET [object_id] = @package_id
            WHERE [operation_id] = @operation_id
        IF @@ROWCOUNT <> 1
        BEGIN
            RAISERROR(27112, 16, 1, N'operations') WITH NOWAIT
        END
        
        
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        UPDATE [internal].[operations] SET 
            [end_time]  = SYSDATETIMEOFFSET(),
            [status]    = 4
            WHERE operation_id    = @operation_id;
        THROW;
    END CATCH
    RETURN 0
GO
IF (OBJECT_ID('[internal].[prepare_validate_project]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[prepare_validate_project]
END
GO

CREATE PROCEDURE [internal].[prepare_validate_project] 
        @folder_name                     nvarchar(128),
        @project_name                    nvarchar(128),
        @validate_type                   char(1),             
        @use32bitruntime                 bit =0,            
        @environment_scope              char(1) = 'D',     
        @reference_id                    bigint = NULL,
        @validation_id                   bigint OUTPUT,
        @project_id                      bigint OUTPUT,
        @version_id                      bigint OUTPUT
AS
    SET NOCOUNT ON

	 
	
    
    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
    END

    DECLARE @created_time    DATETIMEOFFSET
    DECLARE @return_value   int
    DECLARE @operation_id bigint
    DECLARE @result bit
    
    BEGIN TRY
        SET @created_time = SYSDATETIMEOFFSET() 
        
        EXEC @return_value = [internal].[insert_operation] 
                        300,  
                        @created_time,          
                        20,    
                        NULL,                   
                        @project_name,          
                        5,    
                        @created_time,          
                        null,                   
                        @caller_sid,            
                        @caller_name,           
                        null,                   
                        null,                   
                        null,                   
                        @operation_id OUTPUT  
        IF @return_value <> 0
            RETURN 1;
        
        SET @validation_id = @operation_id
        
        EXEC @return_value = [internal].[init_object_permissions] 4, @operation_id, @caller_id

                      
        IF @return_value <> 0
        BEGIN
            
            RAISERROR(27153, 16, 1) WITH NOWAIT
            RETURN 1
        END 
    END TRY
    BEGIN CATCH
        UPDATE [internal].[operations] SET 
            [end_time]  = SYSDATETIMEOFFSET(),
            [status]    = 4
            WHERE operation_id    = @operation_id;
        THROW           
    END CATCH

    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY
               
        
        SELECT @project_id = projs.[project_id],  
               @version_id = projs.[object_version_lsn]
            FROM [catalog].[projects] projs INNER JOIN [catalog].[folders] fds
            ON projs.[folder_id] = fds.[folder_id] 
            WHERE fds.[name] = @folder_name AND projs.[name] = @project_name
        
        IF (@project_id IS NULL OR @version_id IS NULL)
        BEGIN
            RAISERROR(27109, 16, 1, @project_name) WITH NOWAIT
        END
        
        INSERT into [internal].[validations]
               (
                  [validation_id],
                  [environment_scope],
                  [validate_type],
                  [folder_name],
                  [project_name],
                  [project_lsn],
                  [use32bitruntime],
                  [reference_id]
               )
        VALUES (
                  @validation_id,              
                  @environment_scope,
                  @validate_type,              
                  @folder_name,               
                  @project_name,              
                  @version_id,                
                  @use32bitruntime,           
                  @reference_id               
               )
        
        UPDATE [internal].[operations]
            SET [object_id] = @project_id
            WHERE [operation_id] = @operation_id
        IF @@ROWCOUNT <> 1
        BEGIN
            RAISERROR(27112, 16, 1, N'operations') WITH NOWAIT
        END
        
        
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        UPDATE [internal].[operations] SET 
            [end_time]  = SYSDATETIMEOFFSET(),
            [status]    = 4
            WHERE operation_id    = @operation_id;
        THROW;
    END CATCH
    RETURN 0
GO
IF (OBJECT_ID('[internal].[configure_execution_encryption_algorithm]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[configure_execution_encryption_algorithm]
END
GO

CREATE PROCEDURE [internal].[configure_execution_encryption_algorithm]
        @algorithm_name     nvarchar(255),
        @operation_id       bigint
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON
    IF (@algorithm_name IS NULL)
    BEGIN
        RAISERROR(27100, 16, 2, N'algorithm_name') WITH NOWAIT
        RETURN 1;
    END
    
    DECLARE @execution_id bigint
    DECLARE @decrypt_values [internal].[decrypted_data_table]
    DECLARE @decrypt_property_override_values [internal].[decrypted_data_table]
    
    DECLARE @key_name               [internal].[adt_name]
    DECLARE @certificate_name       [internal].[adt_name]
    DECLARE @sqlString              nvarchar(1024)
    DECLARE @server_operation_encryption_level       int


    SELECT @server_operation_encryption_level = CONVERT(int,property_value)  
            FROM [catalog].[catalog_properties]
            WHERE property_name = 'SERVER_OPERATION_ENCRYPTION_LEVEL'

    IF @server_operation_encryption_level NOT in (1, 2)     
    BEGIN
        RAISERROR(27163    ,16,1,'SERVER_OPERATION_ENCRYPTION_LEVEL') WITH NOWAIT
        RETURN 1
    END
    
    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    IF @server_operation_encryption_level = 1
    BEGIN
    BEGIN TRY
        
        IF EXISTS (SELECT operation_id FROM [internal].[operations]
                WHERE [status] IN (2, 5)
                AND   [operation_id] <> @operation_id )
        BEGIN    
            RAISERROR(27139, 16, 1) WITH NOWAIT
            RETURN 1
        END
        
        
        DECLARE execution_cursor CURSOR LOCAL
            FOR SELECT [execution_id] FROM [internal].[executions]
        OPEN execution_cursor
        
        FETCH NEXT FROM execution_cursor
            INTO @execution_id
        
        
        WHILE (@@FETCH_STATUS = 0)
        BEGIN
            
            DELETE @decrypt_values
            DELETE @decrypt_property_override_values
            
            
            SET @key_name = 'MS_Enckey_Exec_'+CONVERT(varchar(1024),@execution_id)
            SET @certificate_name = 'MS_Cert_Exec_'+CONVERT(varchar(1024),@execution_id)
            
            SELECT @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name + ' DECRYPTION BY CERTIFICATE '+ @certificate_name
            EXECUTE sp_executesql @sqlString
    
            
            INSERT @decrypt_values 
            SELECT [execution_parameter_id], DECRYPTBYKEY(sensitive_parameter_value)            
               FROM [internal].[execution_parameter_values] 
               WHERE [execution_id] = @execution_id
               AND [sensitive] = 1
               AND [value_set] = 1
    
            INSERT @decrypt_property_override_values 
            SELECT [property_id], DECRYPTBYKEY(sensitive_property_value)            
               FROM [internal].[execution_property_override_values] 
               WHERE [execution_id] = @execution_id
               AND [sensitive] = 1

            
            SELECT @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString
        
            
            SELECT @sqlString = 'DROP SYMMETRIC KEY ' + @key_name
            EXECUTE sp_executesql @sqlString
            SELECT @sqlString = 'CREATE SYMMETRIC KEY '+ @key_name + ' WITH ALGORITHM = ' 
                + @algorithm_name + ' ENCRYPTION BY CERTIFICATE ' + @certificate_name
            EXECUTE sp_executesql @sqlString
            
            
            SELECT @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name + ' DECRYPTION BY CERTIFICATE '+ @certificate_name
            EXECUTE sp_executesql @sqlString
            
            
            UPDATE [internal].[execution_parameter_values] 
                SET sensitive_parameter_value =  EncryptByKey(KEY_GUID(@key_name),src.value)
                FROM @decrypt_values src
                WHERE execution_parameter_id = src.id
            
            
            UPDATE [internal].[execution_property_override_values] 
                SET sensitive_property_value =  EncryptByKey(KEY_GUID(@key_name),src.value)
                FROM @decrypt_property_override_values src
                WHERE property_id = src.id

            
            SELECT @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString
            
            
            FETCH NEXT FROM execution_cursor
                INTO @execution_id            
        END
        CLOSE execution_cursor
        DEALLOCATE execution_cursor
        
        
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           

        
        IF (CURSOR_STATUS('local', 'execution_cursor') = 1 
            OR CURSOR_STATUS('local', 'execution_cursor') = 0)
        BEGIN
            CLOSE execution_cursor
            DEALLOCATE execution_cursor            
        END
            IF (@key_name <> '')
            BEGIN
                SET @sqlString = 'IF EXISTS (SELECT key_name FROM sys.openkeys WHERE key_name = ''' + @key_name +''') ' 
                  + 'CLOSE SYMMETRIC KEY '+ @key_name
                EXECUTE sp_executesql @sqlString
            END;
            THROW;
        END CATCH
    END
    ELSE 
    BEGIN
        BEGIN TRY
             
            IF EXISTS (SELECT operation_id FROM [internal].[operations]
                WHERE [status] IN (2, 5)
                AND   [operation_id] <> @operation_id )
            BEGIN
                RAISERROR(27139, 16, 1) WITH NOWAIT
                RETURN 1
            END

            
            DECLARE project_cursor CURSOR LOCAL
                FOR SELECT [project_id] FROM [internal].[projects]

            CREATE TABLE #decryped_values_table (execution_parameter_id bigint, execution_id bigint, 
                decrypt_values varbinary(MAX))
                        
            CREATE TABLE #decryped_property_override_values_table(property_id bigint, execution_id bigint, 
                decrypt_property_override_values varbinary(MAX))

            DECLARE @project_id bigint

            OPEN project_cursor
            FETCH NEXT FROM project_cursor
                INTO @project_id

            
            WHILE (@@FETCH_STATUS = 0)
            BEGIN
                
                SET @key_name = 'MS_Enckey_Proj_Param_' + CONVERT(varchar(1024),@project_id)
                SET @certificate_name = 'MS_Cert_Proj_Param_' + CONVERT(varchar(1024),@project_id)

                IF EXISTS (SELECT * from sys.symmetric_keys WHERE name = @key_name)
                BEGIN
                    SELECT @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name + ' DECRYPTION BY CERTIFICATE '+ @certificate_name
                    EXECUTE sp_executesql @sqlString

                    TRUNCATE TABLE #decryped_values_table
                    TRUNCATE TABLE #decryped_property_override_values_table

                    INSERT INTO #decryped_values_table(execution_parameter_id, execution_id, decrypt_values) 
                        SELECT [execution_parameter_id],[execution_id], DECRYPTBYKEY(sensitive_parameter_value)
                        FROM [internal].[execution_parameter_values] inner join [internal].[operations] ON 
                                [execution_parameter_values].[execution_id] = [operations].[operation_id]
                        WHERE [operations].[object_id] = @project_id
                            AND [execution_parameter_values].[sensitive] = 1
                            AND [execution_parameter_values].[value_set] = 1

                    INSERT INTO #decryped_property_override_values_table(property_id, execution_id,decrypt_property_override_values)
                        SELECT [property_id], [execution_id],DECRYPTBYKEY(sensitive_property_value)
                        FROM [internal].[execution_property_override_values] inner join [internal].[operations] ON
                            [execution_property_override_values].[execution_id] = [operations].[operation_id]
                        WHERE [operations].[object_id] = @project_id
                        AND [execution_property_override_values].[sensitive] = 1

                    
                    SELECT @sqlString = 'DROP SYMMETRIC KEY ' + @key_name
                    EXECUTE sp_executesql @sqlString
                    SELECT @sqlString = 'CREATE SYMMETRIC KEY '+ @key_name + ' WITH ALGORITHM = ' 
                        + @algorithm_name + ' ENCRYPTION BY CERTIFICATE ' + @certificate_name
                    EXECUTE sp_executesql @sqlString
               
                    
                    SELECT @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name + ' DECRYPTION BY CERTIFICATE '+ @certificate_name
                    EXECUTE sp_executesql @sqlString

                    
                    UPDATE [internal].[execution_parameter_values] 
                        SET sensitive_parameter_value =  EncryptByKey(KEY_GUID(@key_name),#decryped_values_table.decrypt_values)
                        FROM #decryped_values_table
                        WHERE [execution_parameter_values].[execution_id] = #decryped_values_table.execution_id
                            AND [execution_parameter_values].[execution_parameter_id] = #decryped_values_table.execution_parameter_id

                    
                    UPDATE [internal].[execution_property_override_values] 
                        SET sensitive_property_value =  EncryptByKey(KEY_GUID(@key_name),#decryped_property_override_values_table.decrypt_property_override_values)
                        FROM #decryped_property_override_values_table
                        WHERE [execution_property_override_values].[property_id] = #decryped_property_override_values_table.property_id
                            AND [execution_property_override_values].[execution_id] = #decryped_property_override_values_table.execution_id

                    
                    SELECT @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
                    EXECUTE sp_executesql @sqlString
                END
                FETCH NEXT FROM project_cursor
                    INTO @project_id
            END
        
            CLOSE project_cursor

            DEALLOCATE project_cursor
            DROP TABLE #decryped_values_table
            DROP TABLE #decryped_property_override_values_table

            IF @tran_count = 0
                COMMIT TRANSACTION;
        END TRY
        BEGIN CATCH
            
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           

            
            IF (CURSOR_STATUS('local', 'project_cursor') = 1 
                OR CURSOR_STATUS('local', 'project_cursor') = 0)
            BEGIN
                CLOSE project_cursor
                DEALLOCATE project_cursor
            END

        IF (@key_name <> '')
        BEGIN
            SET @sqlString = 'IF EXISTS (SELECT key_name FROM sys.openkeys WHERE key_name = ''' + @key_name +''') ' 
                  + 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString
        END;
        THROW;
    END CATCH
    END
    RETURN 0
GO
IF (OBJECT_ID('[internal].[create_deploy_package_operation]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[create_deploy_package_operation]
END
GO

CREATE PROCEDURE [internal].[create_deploy_package_operation]
    @folder_name nvarchar(128),
    @project_name nvarchar(128),
    @operation_id bigint output
AS 
    SET NOCOUNT ON
   
    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
    END  
    
    DECLARE @folder_id      bigint
    DECLARE @project_id     bigint
    DECLARE @start_time     DATETIMEOFFSET
    DECLARE @return_value   int 
     
    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                        
    
    BEGIN TRY
        EXECUTE AS CALLER
           SET @folder_id =
                (SELECT [folder_id] FROM [catalog].[folders] WHERE [name] = @folder_name)
        REVERT

        IF @folder_id IS NULL
        BEGIN
            RAISERROR(27104 , 16 , 1, @folder_name) WITH NOWAIT
        END 

        EXECUTE AS CALLER
           SET @project_id = 
                (SELECT [project_id] FROM [catalog].[projects] WHERE [name] = @project_name AND [folder_id] = @folder_id)
        REVERT

        IF @project_id IS NULL
        BEGIN
            RAISERROR(27187 , 16 , 1, @project_name) WITH NOWAIT
        END 

        SET @start_time = SYSDATETIMEOFFSET() 
        

        EXEC @return_value = [internal].[insert_operation] 
                            102, 
                            @start_time,    
                            20,             
                            @project_id,    
                            @project_name,
                            5,                                  
                            @start_time,    
                            null,           
                            @caller_sid,    
                            @caller_name,   
                            null,           
                            null,           
                            null,           
                            @operation_id OUTPUT  
        
        IF @return_value <> 0
        BEGIN
            RAISERROR(27169,16,1) WITH NOWAIT
        END
        
        
        EXEC @return_value = [internal].[init_object_permissions] 
                4, @operation_id, @caller_id 
        IF @return_value <> 0
        BEGIN
            
            RAISERROR(27153,16,1) WITH NOWAIT
        END
     
    
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                                  
        THROW 
    END CATCH
    
    RETURN 0
GO
IF (OBJECT_ID('[internal].[prepare_packages_deploy]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[prepare_packages_deploy]
END
GO

CREATE PROCEDURE [internal].[prepare_packages_deploy]
    @folder_name nvarchar(128),
    @project_name nvarchar(128),
    @packages_table [catalog].[Package_Table_Type] ReadOnly,
    @operation_id bigint,
    @version_id bigint output,
    @project_id bigint output
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON

    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
    END

    DECLARE @start_time             DATETIMEOFFSET
    DECLARE @return_value           int
    DECLARE @folder_id              bigint
    DECLARE @sqlString              nvarchar(1024)
    DECLARE @key_name               [internal].[adt_name]
    DECLARE @certificate_name       [internal].[adt_name]
    DECLARE @encryption_algorithm   nvarchar(255)
    DECLARE @result                 bit
    DECLARE @KEY                    varbinary(8000)
    DECLARE @IV                     varbinary(8000)
    
    EXECUTE AS CALLER
        SET @folder_id = 
            (SELECT [folder_id] FROM [catalog].[folders] WHERE [name] = @folder_name)
            
    IF @folder_id IS NULL
    BEGIN
        RAISERROR(27104 , 16 , 1, @folder_name) WITH NOWAIT
    END 
    REVERT

    
    SET @project_id = (SELECT [project_id] FROM [catalog].[projects]
                   WHERE [folder_id] = @folder_id AND [name] = @project_name)

    IF(@project_id IS NULL)
    
    BEGIN
        RAISERROR(27187, 16, 1) WITH NOWAIT
        RETURN 1
    END
    
    BEGIN TRY

        SET @start_time = SYSDATETIMEOFFSET() 

        BEGIN
            EXECUTE AS CALLER   
                SET @result = [internal].[check_permission] 
                (
                    2,
                    @project_id,
                    1
                ) 
            REVERT
            
            IF @result = 0
            BEGIN
                RAISERROR(27109 , 16 , 1, @project_name) WITH NOWAIT
                RETURN 1
            END
            
            
            IF EXISTS (SELECT [project_id] FROM [internal].[projects] projs INNER JOIN [internal].[object_versions] vers
                            ON projs.[project_id] = vers.[object_id] WHERE vers.[object_status] = 'D' AND
                            [folder_id] = @folder_id AND [name] = @project_name)
            BEGIN
                RAISERROR(27230, 16, 1) WITH NOWAIT
                RETURN 1
            END

            
            EXECUTE AS CALLER   
                SET @result = [internal].[check_permission] 
                (
                    2,
                    @project_id,
                    2
                ) 
            REVERT
            
            IF @result = 0
            BEGIN
                RAISERROR(27109 , 16 , 1, @project_name) WITH NOWAIT
                RETURN 1
            END
            
            SET @encryption_algorithm = (SELECT [internal].[get_encryption_algorithm]())
        
            IF @encryption_algorithm IS NULL
            BEGIN
                RAISERROR(27156, 16, 1, 'ENCRYPTION_ALGORITHM') WITH NOWAIT
                RETURN 1
            END
            
            SET @key_name = 'MS_Enckey_Proj_'+CONVERT(varchar,@project_id)
            SET @certificate_name = 'MS_Cert_Proj_'+CONVERT(varchar,@project_id)
            SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
                            + ' DECRYPTION BY CERTIFICATE ' + @certificate_name  
            EXECUTE sp_executesql @sqlString 
            
            SELECT @KEY = DECRYPTBYKEY([key]), @IV = DECRYPTBYKEY([IV]) 
                FROM [internal].[catalog_encryption_keys]
                WHERE [key_name] = @key_name
                
            IF (@KEY IS NULL OR @IV IS NULL)
            BEGIN
                RAISERROR(27117, 16 ,1) WITH NOWAIT
                RETURN 1
            END
            
            SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString
            
            INSERT INTO [internal].[object_versions] (
                [object_id],
                [object_type],
                [description],
                [created_by],
                [created_time],
                [restored_by],
                [last_restored_time],
                [object_data],
                [object_status]) 
            VALUES (
                @project_id,
                20,
                null,
                @caller_name,
                @start_time,
                null,
                null,
                0,
                'D')
  
            IF @@ROWCOUNT = 1
                BEGIN
                SET @version_id = scope_identity()
            END
            ELSE BEGIN
                 RAISERROR(27112, 16, 1, N'object_versions') WITH NOWAIT
                 RETURN 1
            END

            INSERT INTO [internal].[packages]
               ([project_version_lsn]
               ,[name]
               ,[package_guid]
               ,[description]
               ,[package_format_version]
               ,[version_major]
               ,[version_minor]
               ,[version_build]
               ,[version_comments]
               ,[version_guid]
               ,[project_id]
               ,[entry_point]
               ,[validation_status]
               ,[last_validation_time]
               ,[package_data])
                SELECT
                @version_id
               ,[name]
               ,'00000000-0000-0000-0000-000000000000'
               ,null
               ,0
               ,0
               ,0
               ,0
               ,null
               ,'00000000-0000-0000-0000-000000000000'
               ,@project_id
               ,0
               ,'0'
               ,null
               ,[internal].[encrypt_lob_data](@encryption_algorithm, @KEY, @IV, [package_data])
            FROM @packages_table
        END
    END TRY
    BEGIN CATCH 
        UPDATE [internal].[operations] SET 
            [end_time]  = SYSDATETIMEOFFSET(),
            [status]    = 4
            WHERE [operation_id] = @operation_id;
        THROW 
    END CATCH
GO
IF (OBJECT_ID('[internal].[get_updatedpackages]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[get_updatedpackages]
END
GO

CREATE PROCEDURE [internal].[get_updatedpackages]
    @project_version_lsn bigint,
    @project_name       nvarchar(128),
    @folder_name       nvarchar(128)
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON
    
    DECLARE @result             bit
    DECLARE @project_id         bigint
    
    EXECUTE AS CALLER 
        SELECT @project_id = projs.[project_id]
        FROM [catalog].[projects] projs INNER JOIN [catalog].[folders] fds
        ON projs.[folder_id] = fds.[folder_id] 
        WHERE fds.[name] = @folder_name AND projs.[name] = @project_name
    REVERT
        
    IF (@project_id IS NULL OR @project_id < 0 
            OR @project_version_lsn IS NULL OR @project_version_lsn < 0)
    BEGIN
        RAISERROR(27138, 16 , 6) WITH NOWAIT 
        RETURN 1 
    END
    
    EXECUTE AS CALLER   
        SET @result = [internal].[check_permission] 
        (
            2,
            @project_id,
            1
        ) 
    REVERT
    IF @result = 0
    BEGIN
        RAISERROR(27109 , 16 , 1, @project_name) WITH NOWAIT
        RETURN 1
    END
     
    DECLARE @sqlString              nvarchar(1024)
    DECLARE @key_name               [internal].[adt_name]
    DECLARE @certificate_name       [internal].[adt_name]
    DECLARE @encryption_algorithm   nvarchar(255)
    DECLARE @KEY                    varbinary(8000)
    DECLARE @IV                     varbinary(8000)
    
    SET @key_name = 'MS_Enckey_Proj_'+CONVERT(varchar,@project_id)
    SET @certificate_name = 'MS_Cert_Proj_'+CONVERT(varchar,@project_id)
    
    BEGIN TRY       
        SET @encryption_algorithm = (SELECT [internal].[get_encryption_algorithm]())
        IF @encryption_algorithm IS NULL
        BEGIN
            RAISERROR(27156, 16, 1, 'ENCRYPTION_ALGORITHM') WITH NOWAIT
            RETURN 1
        END
        
        SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
                            + ' DECRYPTION BY CERTIFICATE ' + @certificate_name  
        EXECUTE sp_executesql @sqlString 
        
        SELECT @KEY = DECRYPTBYKEY([key]), @IV = DECRYPTBYKEY([IV]) 
            FROM [internal].[catalog_encryption_keys]
            WHERE [key_name] = @key_name
        IF (@KEY IS NULL OR @IV IS NULL)
        BEGIN
            RAISERROR(27117, 16 ,1) WITH NOWAIT
            RETURN 1
        END
        
        SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString

        SELECT [name], [package_data] FROM [internal].[get_package_data](@encryption_algorithm, @KEY, @IV, @project_version_lsn, @project_id)

        RETURN 0
    END TRY
    BEGIN CATCH
        
        SET @sqlString = 'IF EXISTS (SELECT key_name FROM sys.openkeys WHERE key_name = ''' + @key_name +''') ' 
                    + 'CLOSE SYMMETRIC KEY '+ @key_name
        EXECUTE sp_executesql @sqlString;
        THROW
    END CATCH
GO
IF (OBJECT_ID('[internal].[update_project_object]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[update_project_object]
END
GO

CREATE PROCEDURE [internal].[update_project_object]
        @project_id              bigint,
        @version_id              bigint,
        @object_data             varbinary(MAX)
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON

    DECLARE @result bit
    EXECUTE AS CALLER
        SET @result = [internal].[check_permission] 
        (
            2,
            @project_id,
            2
        ) 
    REVERT
    IF @result = 0
    BEGIN
        RAISERROR(27109 , 16 , 1, @project_id) WITH NOWAIT
        RETURN 1        
    END

    DECLARE @sqlString              nvarchar(1024)
    DECLARE @key_name               [internal].[adt_name]
    DECLARE @certificate_name       [internal].[adt_name]
    DECLARE @encryption_algorithm   nvarchar(255)
    DECLARE @KEY                    varbinary(8000)
    DECLARE @IV                     varbinary(8000)
    
    SET @key_name = 'MS_Enckey_Proj_'+CONVERT(varchar,@project_id)
    SET @certificate_name = 'MS_Cert_Proj_'+CONVERT(varchar,@project_id)

    BEGIN TRY
        SET @encryption_algorithm = (SELECT [internal].[get_encryption_algorithm]())
        IF @encryption_algorithm IS NULL
        BEGIN
            RAISERROR(27156, 16, 1, 'ENCRYPTION_ALGORITHM') WITH NOWAIT
            RETURN 1
        END

        SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
                            + ' DECRYPTION BY CERTIFICATE ' + @certificate_name  
        EXECUTE sp_executesql @sqlString 
        
        SELECT @KEY = DECRYPTBYKEY([key]), @IV = DECRYPTBYKEY([IV]) 
            FROM [internal].[catalog_encryption_keys]
            WHERE [key_name] = @key_name
        IF (@KEY IS NULL OR @IV IS NULL)
        BEGIN
            RAISERROR(27117, 16 ,1) WITH NOWAIT
            RETURN 1
        END
        
        SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString
            
        EXEC [internal].[update_object_versions]
            @project_id
            ,@version_id
            ,@object_data
            ,@KEY
            ,@IV
            ,@encryption_algorithm
        
        RETURN 0
    END TRY
    BEGIN CATCH
        
        SET @sqlString = 'IF EXISTS (SELECT key_name FROM sys.openkeys WHERE key_name = ''' + @key_name +''') ' 
                    + 'CLOSE SYMMETRIC KEY '+ @key_name
        EXECUTE sp_executesql @sqlString;
        THROW        
    END CATCH   
GO
IF (OBJECT_ID('[internal].[update_object_versions]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[update_object_versions]
END
GO

CREATE PROCEDURE [internal].[update_object_versions]
        @object_id              bigint,
        @version_id             bigint,
        @object_data            varbinary(MAX), 
        @KEY                    varbinary(8000),
        @IV                     varbinary(8000),
        @algorithm_name         nvarchar(255)
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON
    
    DECLARE @sqlString              nvarchar(1024)
    DECLARE @key_name               [internal].[adt_name]
    DECLARE @certificate_name       [internal].[adt_name]
    DECLARE @encryption_algorithm   nvarchar(255)
    DECLARE @encrypted_value        varbinary(MAX)
    
    SET @key_name = 'MS_Enckey_Proj_'+CONVERT(varchar,@object_id)
    SET @certificate_name = 'MS_Cert_Proj_'+CONVERT(varchar,@object_id)
    
    IF NOT EXISTS (SELECT name FROM sys.symmetric_keys WHERE name = @key_name )
       OR NOT EXISTS (SELECT name FROM sys.certificates WHERE name = @certificate_name )
    BEGIN
        
        RAISERROR(27172, 16, 1, @object_id) WITH NOWAIT
        RETURN 1
    END
    
    BEGIN TRY
        SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name
                            + ' DECRYPTION BY CERTIFICATE ' + @certificate_name
        EXECUTE sp_executesql @sqlString 
        
        SET @encrypted_value = [internal].[encrypt_lob_data](@algorithm_name, @KEY, @IV, @object_data); 
        
        IF @encrypted_value IS NULL
        BEGIN
            RAISERROR(27119, 16, 1, @object_id) WITH NOWAIT
            RETURN 1
        END
    
        SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString
            
        UPDATE [internal].[object_versions]
        SET [object_data] = @encrypted_value
        WHERE [object_id] = @object_id AND [object_version_lsn] = @version_id
        
    END TRY  
    BEGIN CATCH
        SET @sqlString = 'IF EXISTS (SELECT key_name FROM sys.openkeys WHERE key_name = ''' + @key_name +''') ' 
                    + 'CLOSE SYMMETRIC KEY '+ @key_name
        EXECUTE sp_executesql @sqlString;        
        THROW
    END CATCH
GO
IF (OBJECT_ID('[internal].[update_package_deployment_status]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[update_package_deployment_status]
END
GO

CREATE PROCEDURE [internal].[update_package_deployment_status]
        @operation_id           bigint,
        @project_version_lsn    bigint,
        @end_time               datetimeoffset,
        @status                 int,
        @description            nvarchar(1024) = NULL,
        @project_format_version int = NULL
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON
    
    DECLARE @project_id bigint
    DECLARE @result bit
    
    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
    END

    IF (@operation_id IS NULL OR @end_time IS NULL  OR @status IS NULL)
    BEGIN
        RAISERROR(27138, 16 , 6) WITH NOWAIT 
        RETURN 1
    END
        
    EXECUTE AS CALLER
        SET @result = [internal].[check_permission] 
        (
            4,
            @operation_id,
            2
        )
    REVERT
    
    IF @result = 0
    BEGIN
        RAISERROR(27105 , 16 , 1, @operation_id) WITH NOWAIT
        RETURN 1
    END
    
    
    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                       
    BEGIN TRY
        
        
        IF EXISTS (SELECT [operation_id] FROM [internal].[operations]
            WHERE ([status] = 5 OR [status] = 2 
            OR [status] = 4) 
            AND [operation_id] = @operation_id AND [operation_type] = 102)
        BEGIN
            
            
            IF @project_version_lsn IS NULL 
            BEGIN
                UPDATE [internal].[operations] SET 
                    [end_time]  = SYSDATETIMEOFFSET(),
                    [status]    = 4
                    WHERE [operation_id]    = @operation_id;
            END
            
            ELSE
                BEGIN
                SET @project_id = (SELECT [object_id] 
                        FROM [internal].[object_versions]
                        WHERE [object_status] = 'D' AND [object_version_lsn] = @project_version_lsn)
                        
                
                IF @project_id IS NOT NULL
                BEGIN
                    
                    EXECUTE AS CALLER
                        IF [internal].[check_permission] 
                        (
                            2,
                            @project_id,
                            2
                        ) = 0
                        BEGIN
                            RAISERROR(27194 , 16 , 1) WITH NOWAIT       
                        END
                    REVERT
                    
                    IF @status = 4
                    BEGIN
                        
                        UPDATE [internal].[operations]
                            SET [end_time] = @end_time,
                            [status] = 4
                        WHERE [operation_id] = @operation_id

                        
                        DELETE FROM [internal].[object_versions]
                            WHERE [object_version_lsn] = @project_version_lsn
                            AND [object_status] = 'D' 
                    END
                    
                    ELSE IF @status = 7
                    BEGIN
                        
                        UPDATE [internal].[operations]
                            SET [end_time] = @end_time,
                            [status] = 7
                        WHERE [operation_id] = @operation_id

                        
                        UPDATE [internal].[projects]
                            SET [last_deployed_time] = @end_time,
                            [object_version_lsn] = @project_version_lsn,
                            [description] = @description,
                            [project_format_version] = @project_format_version
                            WHERE [project_id] = @project_id
                            
                        
                        UPDATE [internal].[object_versions]
                            SET [object_status] = 'C', 
                                [description] = @description
                            WHERE [object_version_lsn] = @project_version_lsn
                            AND [object_status] = 'D'
                    END
                END
            END
        END        
        
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                             
        UPDATE [internal].[operations] SET 
            [end_time]  = SYSDATETIMEOFFSET(),
            [status]    = 4
            WHERE [operation_id]    = @operation_id;
        THROW 
    END CATCH
    
GO
IF (OBJECT_ID('[internal].[clean_update_packages]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[clean_update_packages]
END
GO

CREATE PROCEDURE [internal].[clean_update_packages]
        @project_id             bigint,
        @project_version_lsn    bigint
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON

    DECLARE @result bit
    EXECUTE AS CALLER   
        SET @result = [internal].[check_permission] 
        (
            2,
            @project_id,
            2
        ) 
    REVERT

    IF @result = 0
    BEGIN
        RAISERROR(27109 , 16 , 1, @project_id) WITH NOWAIT
        RETURN 1
    END
     
    DELETE FROM [internal].[packages]
    WHERE [project_id] = @project_id AND [project_version_lsn] = @project_version_lsn
GO
IF (OBJECT_ID('[internal].[encrypt_data]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[encrypt_data]
END
GO

CREATE PROCEDURE [internal].[encrypt_data]
		@key_name               [internal].[adt_name],
		@certificate_name       [internal].[adt_name],
		@data 	nvarchar(max),
		@encrypted_data nvarchar(max) output
WITH EXECUTE AS 'AllSchemaOwner'
AS
	SET NOCOUNT ON
	DECLARE @sqlString  			nvarchar(1024)
    DECLARE @open_cert_sqlString    nvarchar(1024)
	DECLARE @close_cert_sqlString   nvarchar(1024)
	DECLARE @encryption_algorithm   nvarchar(255)
    DECLARE @return_value        bit
    DECLARE @KEY            varbinary(8000)
    DECLARE @IV             varbinary(8000)
	
		
	SET @encryption_algorithm = 'AES_256'
	
	SET @open_cert_sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
		+ ' DECRYPTION BY CERTIFICATE ' + @certificate_name
	SET @close_cert_sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
					
	
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                       
    BEGIN TRY
	
	SELECT @KEY = DECRYPTBYKEY([key]), @IV = DECRYPTBYKEY([IV]) 
    FROM [internal].[catalog_encryption_keys]
    WHERE [key_name] = @key_name
                
    IF (@KEY IS NULL)
	BEGIN
		SET @sqlString = 'CREATE CERTIFICATE ' + @certificate_name + ' WITH SUBJECT = ''ISServerCertificate'''

		IF  NOT EXISTS (SELECT [name] FROM [sys].[certificates] WHERE [name] = @certificate_name)
			EXECUTE sp_executesql @sqlString 
		
		SET @sqlString = 'CREATE SYMMETRIC KEY ' + @key_name +' WITH ALGORITHM = ' 
							+ @encryption_algorithm + ' ENCRYPTION BY CERTIFICATE ' + @certificate_name
							
		IF  NOT EXISTS (SELECT [name] FROM [sys].[symmetric_keys] WHERE [name] = @key_name)
			EXECUTE sp_executesql @sqlString 		
		
		EXECUTE sp_executesql @open_cert_sqlString 		
		
		
		EXEC @return_value = 
			[internal].[create_key_information] @encryption_algorithm, @KEY output, @IV output
		IF(@return_value <> 0)
		BEGIN
		    RAISERROR(27249, 16 ,1) WITH NOWAIT
			RETURN 1
		END
		
		INSERT INTO [internal].[catalog_encryption_keys]
		VALUES (@key_name, ENCRYPTBYKEY( KEY_GUID(@key_name), @KEY), ENCRYPTBYKEY( KEY_GUID(@key_name), @IV ))
		
		EXECUTE sp_executesql @close_cert_sqlString 
	END
	 
	EXECUTE sp_executesql @open_cert_sqlString 
	
	SET @encrypted_data = ENCRYPTBYKEY(KEY_GUID(@key_name), @data)

	EXECUTE sp_executesql @close_cert_sqlString   

	
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                             
        THROW 
    END CATCH	
GO
IF (OBJECT_ID('[internal].[decrypt_data]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[decrypt_data]
END
GO

CREATE PROCEDURE [internal].[decrypt_data]
		@key_name               [internal].[adt_name],
		@certificate_name       [internal].[adt_name],
		@data 	nvarchar(max),
		@decrypted_data nvarchar(max) output
WITH EXECUTE AS 'AllSchemaOwner'
AS
	SET NOCOUNT ON
    DECLARE @open_cert_sqlString    nvarchar(1024)
	DECLARE @close_cert_sqlString   nvarchar(1024)

	SET @open_cert_sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
		+ ' DECRYPTION BY CERTIFICATE ' + @certificate_name
	SET @close_cert_sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
	
	IF NOT EXISTS (SELECT [key_name] 
    FROM [internal].[catalog_encryption_keys]
    WHERE [key_name] = @key_name)
    BEGIN
        RAISERROR(27250, 16 ,1) WITH NOWAIT
        RETURN 1
    END	 

	EXECUTE sp_executesql @open_cert_sqlString 
	
	SET @decrypted_data = DECRYPTBYKEY(@data)

	EXECUTE sp_executesql @close_cert_sqlString 
GO
IF (OBJECT_ID('[internal].[update_logdb_info]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[update_logdb_info]
END
GO

CREATE PROCEDURE [internal].[update_logdb_info]
		@server_name		nvarchar(max),
		@connection_string 	nvarchar(max)
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON
	DECLARE @encrypted_value  		nvarchar(max)  
    DECLARE @key_name               [internal].[adt_name]
    DECLARE @certificate_name       [internal].[adt_name]
    DECLARE @return_value           bit
	
	SET @key_name = 'MS_Enckey_LOGDB'
	SET @certificate_name = 'MS_Cert_LOGDB'	
				
	
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                       
    BEGIN TRY
	
	EXEC [internal].[encrypt_data]
			@key_name,
			@certificate_name,
			@connection_string,
			@encrypted_value output
	UPDATE [internal].[master_properties]
	SET property_value = @server_name
	WHERE property_name = 'CLUSTER_LOGDB_SERVER'
	
	UPDATE [internal].[master_properties]
	SET property_value = @encrypted_value
	WHERE property_name = 'CLUSTER_LOGDB_CONNECTIONSTRING'  

	
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                             
        THROW 
    END CATCH	
GO
IF (OBJECT_ID('[catalog].[update_logdb_info]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[update_logdb_info]
END
GO

CREATE PROCEDURE [catalog].[update_logdb_info]
		@server_name		nvarchar(max),
		@connection_string 	nvarchar(max)
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON
	EXEC [internal].[update_logdb_info]
	    @server_name,
		@connection_string
GO

GRANT EXECUTE ON [catalog].[update_logdb_info] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[internal].[get_log_dbconnection]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[get_log_dbconnection]
END
GO

CREATE PROCEDURE [internal].[get_log_dbconnection]
		@connection_string 	nvarchar(max) output
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON 
    DECLARE @key_name               [internal].[adt_name]
    DECLARE @certificate_name       [internal].[adt_name]
	DECLARE @encrypted_value  		nvarchar(max) 
	
	SET @key_name = 'MS_Enckey_LOGDB'
	SET @certificate_name = 'MS_Cert_LOGDB'
	
	SELECT @encrypted_value = [property_value]
	FROM [internal].[master_properties]
	WHERE [property_name] = 'CLUSTER_LOGDB_CONNECTIONSTRING'
	
	EXEC [internal].[decrypt_data]
			@key_name,
			@certificate_name,
			@encrypted_value,
			@connection_string output
GO
IF (OBJECT_ID('[internal].[get_database_principals]', 'TF') IS NOT NULL)
BEGIN
DROP FUNCTION  [internal].[get_database_principals]
END
GO

CREATE FUNCTION [internal].[get_database_principals]()
RETURNS @ret TABLE
(
    [name] sysname NOT NULL,
    [principal_id] int NOT NULL,
    [type] char(1) NOT NULL,
    [type_desc] nvarchar(60) NULL,
    [default_schema_name] sysname NULL,
    [create_date] datetime NOT NULL,
    [modify_date] datetime NOT NULL,
    [owning_principal_id] int NULL,
    [sid] varbinary(85) NULL,
    [is_fixed_role] bit NOT NULL
)
AS
BEGIN
    INSERT INTO @ret
    SELECT
        [name],
        [principal_id],
        [type],
        [type_desc],
        [default_schema_name],
        [create_date],
        [modify_date],
        [owning_principal_id],
        [sid],
        [is_fixed_role]
     FROM [sys].[database_principals]
     RETURN
END
GO
IF (OBJECT_ID('[internal].[get_space_used]', 'FN') IS NOT NULL)
BEGIN
DROP FUNCTION  [internal].[get_space_used]
END
GO

CREATE FUNCTION [internal].[get_space_used]
(
     @objname nvarchar(776)   
)
RETURNS bigint
AS 
BEGIN
    IF @objname IS NULL    
    BEGIN
        RETURN -1
    END
    
    DECLARE  @id int         
    DECLARE @type  character(2) 
    DECLARE @pages bigint          
            
    
    SELECT @id = [object_id], @type = [type] 
    FROM [sys].[objects] 
    WHERE object_id = object_id(@objname)

    
    
    IF (@id IS NULL OR @type  <> 'U')
    BEGIN
        RETURN -1
    END

    SELECT 
        @pages = SUM(in_row_data_page_count + lob_used_page_count + row_overflow_used_page_count)
    FROM sys.dm_db_partition_stats
    WHERE object_id = @id;

    
    RETURN @pages * 8
    
END
GO
IF (OBJECT_ID('[internal].[append_event_message_for_worker]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[append_event_message_for_worker]
END
GO

CREATE PROCEDURE [internal].[append_event_message_for_worker]
        @operation_id       bigint,                             
        @message_type       int,                                
        @message_time         datetimeoffset,                     
        @message_source       smallint,                           
        @message              nvarchar(max),                      
        @extended_info_id     bigint = NULL,
        @package_name         nvarchar(260),
        @package_location_type nvarchar(128),
		@package_path_full    nvarchar(4000),
        @event_name           nvarchar(1024),
        @message_source_name  nvarchar(4000),
        @message_source_id    nvarchar(38),
        @subcomponent_name    nvarchar(4000),
        @package_path         nvarchar(MAX),
        @execution_path       nvarchar(MAX),
        @thread_id            int,
        @message_code         int,
        @event_message_guid	  uniqueidentifier
WITH EXECUTE AS 'AllSchemaOwner'
AS
SET NOCOUNT ON

    DECLARE @operation_message_id   bigint    

    INSERT INTO [internal].[operation_messages] 
           ([operation_id], 
            [message_type], 
            [message_time],
            [message_source_type], 
            [message], 
            [extended_info_id])
        VALUES(
            @operation_id,  
            @message_type,
            @message_time,
            @message_source,
            @message,
            @extended_info_id)
            
    SET @operation_message_id = SCOPE_IDENTITY()

    INSERT INTO [internal].[event_messages]
           ([operation_id],
           [event_message_id],
           [package_name],
		   [package_location_type],
		   [package_path_full],
           [event_name],
           [message_source_name],
           [message_source_id],
           [subcomponent_name],
           [package_path],
           [execution_path],
           [threadID],
           [message_code],
		   [event_message_guid]
		   )
     VALUES
           (
           @operation_id,
           @operation_message_id,
           @package_name,
           @package_location_type,
           @package_path_full,
           @event_name,
           @message_source_name,
           @message_source_id,
           @subcomponent_name,
           @package_path,
           @execution_path,
           @thread_id,
           @message_code,
		   @event_message_guid
           )
    RETURN 0
GO
IF (OBJECT_ID('[internal].[append_executable_statistics_for_worker]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[append_executable_statistics_for_worker]
END
GO

CREATE PROCEDURE [internal].[append_executable_statistics_for_worker]
        @operation_id       bigint,                             
        @project_id         bigint,
        @version_id         bigint,
        @package_name       nvarchar(260),
        @package_location_type nvarchar(128),
		@package_path_full    nvarchar(4000),
        @executable_name    nvarchar(4000),
        @executable_guid    nvarchar(38),
        @package_path       nvarchar(MAX),
        @execution_path     nvarchar(MAX),
        @start_time         datetimeoffset(7),
        @end_time           datetimeoffset(7),
        @execution_hierarchy hierarchyid = null,
        @execution_duration  int,
        @execution_result    smallint,
        @execution_value     sql_variant
WITH EXECUTE AS 'AllSchemaOwner'
AS
SET NOCOUNT ON
               
    DECLARE @executable_id bigint

	SELECT @version_id = [project_lsn] FROM internal.executions 
	WHERE [execution_id] = @operation_id
	
	SELECT @project_id = [object_id] FROM internal.object_versions
	WHERE [object_version_lsn] = @version_id
	
    SELECT @executable_id = [executable_id] FROM internal.executables
        WHERE project_id = @project_id AND project_version_lsn = @version_id
        AND package_name = @package_name AND executable_name = @executable_name
        AND executable_guid = @executable_guid AND package_path = @package_path
        
    IF @executable_id IS NULL
    BEGIN
        
        IF NOT EXISTS(SELECT * FROM internal.projects WHERE project_id = @project_id )
            RETURN 0

    
        INSERT INTO [internal].[executables]
           ([project_id],
           [project_version_lsn],
           [package_name],
		   [package_location_type],
	       [package_path_full],
           [executable_name],
           [executable_guid],
           [package_path])
        VALUES
           (
             @project_id,
             @version_id,
             @package_name,
             @package_location_type,
	         @package_path_full,
             @executable_name,
             @executable_guid,
             @package_path
           )
        SET @executable_id = SCOPE_IDENTITY()      
    END 

    INSERT INTO [internal].[executable_statistics]
           ([execution_id],
           [executable_id],
           [execution_path],
           [start_time],
           [end_time],
           [execution_hierarchy],
           [execution_duration],
           [execution_result],
           [execution_value])
     VALUES
           (
               @operation_id,
               @executable_id,
               @execution_path,
               @start_time,
               @end_time,
               null,
               @execution_duration,
               @execution_result,
               @execution_value
           )

    RETURN 0
GO
IF (OBJECT_ID('[internal].[append_message_context_for_worker]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[append_message_context_for_worker]
END
GO

CREATE PROCEDURE [internal].[append_message_context_for_worker]
        @operation_id       bigint,                             
        @event_message_guid uniqueidentifier,                             
        @context_depth      int,                                
        @package_path       nvarchar(MAX),
        @context_type       smallint,
        @context_source_name nvarchar(MAX),
        @context_source_id   nvarchar(38),
        @property_name       nvarchar(4000),
        @property_value      sql_variant
WITH EXECUTE AS 'AllSchemaOwner'
AS
SET NOCOUNT ON

	DECLARE @IsExist 		   BIT = 0
	declare @event_message_id bigint = null
	select	@IsExist = 1, @event_message_id = [event_message_id] from [event_messages]
	where [event_message_guid] = @event_message_guid
	IF @IsExist = 0
	BEGIN
		RAISERROR(27256, 16, 1, @event_message_id) WITH NOWAIT	
		return 1	
	END

    INSERT INTO [internal].[event_message_context]
           ([operation_id],
           [event_message_id],
           [context_depth],
           [package_path],
           [context_type],
           [context_source_name],
           [context_source_id],
           [property_name],
           [property_value])
     VALUES(
              @operation_id,
              @event_message_id,
              @context_depth,
              @package_path,
              @context_type,
              @context_source_name,
              @context_source_id,
              @property_name,
              @property_value 
           )
    RETURN 0
GO
IF (OBJECT_ID('[internal].[append_execution_data_statistics_for_worker]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[append_execution_data_statistics_for_worker]
END
GO

CREATE PROCEDURE [internal].[append_execution_data_statistics_for_worker]
        @execution_id                 bigint,                             
        @package_name                 nvarchar(260),  
        @package_location_type        nvarchar(128),
        @package_path_full            nvarchar(4000),                           
        @task_name                    nvarchar(4000), 
        @dataflow_path_id_string      nvarchar(4000),                               
        @dataflow_path_name           nvarchar(4000),
        @source_component_name        nvarchar(4000),
        @destination_component_name   nvarchar(4000),
        @rows_sent                    bigint,
        @created_time                 datetimeoffset,
        @execution_path               nvarchar(MAX)
WITH EXECUTE AS 'AllSchemaOwner'
AS
SET NOCOUNT ON

    INSERT INTO [internal].[execution_data_statistics]
           ([execution_id],
            [package_name],
            [package_location_type],
            [package_path_full],
            [task_name],
            [dataflow_path_id_string],
            [dataflow_path_name],
            [source_component_name],
            [destination_component_name],
            [rows_sent],
            [created_time],
            [execution_path])
     VALUES(
            @execution_id,                             
            @package_name,                              
            @package_location_type,
            @package_path_full,
            @task_name,
            @dataflow_path_id_string,                             
            @dataflow_path_name,
            @source_component_name,
            @destination_component_name,
            @rows_sent,
            @created_time,
            @execution_path
           )
    RETURN 0
GO
IF (OBJECT_ID('[internal].[append_execution_component_phases_for_worker]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[append_execution_component_phases_for_worker]
END
GO

CREATE PROCEDURE [internal].[append_execution_component_phases_for_worker]
        @execution_id                 bigint,                             
        @package_name                 nvarchar(260),
        @package_location_type        nvarchar(128),
        @package_path_full            nvarchar(4000),                            
        @task_name                    nvarchar(4000),                                
        @subcomponent_name            nvarchar(4000),
        @phase                        sysname,
        @is_start                     bit,
        @start_phase_time             datetimeoffset,
        @end_phase_time               datetimeoffset,
        @execution_path               nvarchar(MAX),
        @sequence_id                  int
WITH EXECUTE AS 'AllSchemaOwner'
AS
SET NOCOUNT ON

    DECLARE @phase_time datetimeoffset
    SET @phase_time = NULL;

    IF(@is_start = 'False')
    BEGIN
        UPDATE [internal].[execution_component_phases] 
        SET [phase_time] = @start_phase_time
        WHERE [sequence_id] = @sequence_id 
        AND [execution_id] = @execution_id;

        SET @phase_time = @end_phase_time;
    END

    INSERT INTO [internal].[execution_component_phases]
           ([execution_id],
            [package_name],
            [package_location_type],
            [package_path_full],
            [task_name],
            [subcomponent_name],
            [phase],
            [is_start],
            [phase_time], 
            [execution_path],
            [sequence_id])
     VALUES(
            @execution_id,                             
            @package_name, 
            @package_location_type,
            @package_path_full,                              
            @task_name,                                
            @subcomponent_name,
            @phase,
            @is_start,
            @phase_time,
            @execution_path,
	    @sequence_id
           )
    RETURN 0
GO

GRANT SELECT ON [catalog].[catalog_properties] TO ModuleSigner
GO
IF (OBJECT_ID('[internal].[GenerateRandomPasswords]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[GenerateRandomPasswords]
END
GO

CREATE PROCEDURE [internal].[GenerateRandomPasswords] (
	@pLength int = 32, 
	@charset varchar(max),
	@password varchar(max) out
)
AS
BEGIN
SET NOCOUNT ON

SET @password = ''
DECLARE @i INT = @pLength
WHILE (@i > 0)
BEGIN
	SET @password = @password + SUBSTRING(@charset, CONVERT(INT, len(@charset)*RAND()) + 1 , 1)
	SET @i -= 1 
END
END
GO
IF (OBJECT_ID('[internal].[enable_scaleout]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[enable_scaleout]
END
GO

CREATE PROCEDURE [internal].[enable_scaleout]
  @agent_password			NVARCHAR(MAX) = NULL

AS
BEGIN
SET NOCOUNT ON

IF (IS_SRVROLEMEMBER('sysadmin') <> 1)
	BEGIN
		RAISERROR(27260, 16, 1) WITH NOWAIT
		RETURN 1
	END

DECLARE @accountName AS SYSNAME
DECLARE @strExec NVARCHAR (MAX)
DECLARE @instance_version NVARCHAR(1024)
DECLARE @master_registry_path NVARCHAR(MAX)
DECLARE @key_value NVARCHAR(1024)

SET @instance_version = SUBSTRING (CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR(20)),1,2) + N'0';
SET @master_registry_path = N'SOFTWARE\\Microsoft\\Microsoft SQL Server\\' + @instance_version + N'\\DTS\\Setup\\SQL_IS_Master';
EXEC master.dbo.xp_regread 'HKEY_LOCAL_MACHINE',@master_registry_path, N'Version', @key_value output;

IF @key_value IS NULL
	BEGIN
		RAISERROR(27261, 16, 1) WITH NOWAIT
		RETURN 1
	END

SET @accountName = N'NT Service\SSISScaleOutMaster150'
IF (SUSER_SID(@accountName) IS NOT NULL AND NOT EXISTS (SELECT * FROM sys.server_principals WHERE NAME = @accountName))
BEGIN
	PRINT N'Adding login for ' + @accountName

	SET @strExec = N'CREATE LOGIN ' + QUOTENAME(@accountName) + ' FROM WINDOWS'
	EXEC (@strExec)

	IF EXISTS(SELECT * FROM sys.database_principals where name = 'SSISScaleOutMasterUser150')
		DROP USER SSISScaleOutMasterUser150

	SET @strExec = N'CREATE USER SSISScaleOutMasterUser150 for login '  + QUOTENAME(@accountName) 
	EXEC (@strExec)

	SET @strExec = N'sp_addrolemember ''ssis_admin'', ''SSISScaleOutMasterUser150'''
	EXEC (@strExec)
END


IF EXISTS(SELECT * FROM sys.server_principals where name = '##MS_SSISLogDBWorkerAgentLogin##')
    DROP LOGIN ##MS_SSISLogDBWorkerAgentLogin##

EXEC ('CREATE LOGIN ##MS_SSISLogDBWorkerAgentLogin## WITH PASSWORD =''' + @agent_password+''', CHECK_POLICY = OFF')

IF EXISTS(SELECT * FROM sys.database_principals where name = '##MS_SSISLogDBWorkerAgentUser##')
	DROP USER ##MS_SSISLogDBWorkerAgentUser##

CREATE USER ##MS_SSISLogDBWorkerAgentUser## FOR LOGIN ##MS_SSISLogDBWorkerAgentLogin##

EXEC sp_addrolemember 'ssis_cluster_worker', '##MS_SSISLogDBWorkerAgentUser##'

IF EXISTS(SELECT * FROM [SSISDB].[catalog].[master_properties] WHERE [property_name] = 'IS_SCALEOUT_ENABLED')
	UPDATE [internal].[master_properties] SET [property_value] = 'TRUE' WHERE [property_name] = 'IS_SCALEOUT_ENABLED'
ELSE
	INSERT INTO [internal].[master_properties] VALUES ('IS_SCALEOUT_ENABLED', 'TRUE')

END
GO

GRANT EXECUTE ON [internal].[enable_scaleout] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[internal].[disable_scaleout]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[disable_scaleout]
END
GO

CREATE PROCEDURE [internal].[disable_scaleout]
AS

BEGIN
SET NOCOUNT ON

BEGIN TRY
	ALTER DATABASE SSISDB SET SINGLE_USER WITH ROLLBACK IMMEDIATE

DECLARE @accountName AS SYSNAME
SET @accountName = N'NT Service\SSISScaleOutMaster150'
DECLARE @strExec NVARCHAR (MAX)
    
	UPDATE [internal].[master_properties] SET [property_value] = '' WHERE [property_name] = 'IS_SCALEOUT_ENABLED'
		
IF (SUSER_SID(@accountName) IS NOT NULL AND EXISTS (SELECT * FROM sys.syslogins WHERE NAME = @accountName))
BEGIN
	SET @strExec = N'Drop LOGIN ' + QUOTENAME(@accountName) 
	EXEC (@strExec)
END
   
	IF EXISTS(SELECT * FROM sys.database_principals where name = 'SSISScaleOutMasterUser150')
		DROP USER SSISScaleOutMasterUser150

	SET @accountName = N'##MS_SSISLogDBWorkerAgentLogin##'
	IF (SUSER_SID(@accountName) IS NOT NULL AND EXISTS (SELECT * FROM sys.syslogins WHERE NAME = @accountName))
	BEGIN
		SET @strExec = N'Drop LOGIN ' + QUOTENAME(@accountName)
		EXEC (@strExec)
END

	IF EXISTS(SELECT * FROM sys.database_principals where name = '##MS_SSISLogDBWorkerAgentUser##')
		DROP USER ##MS_SSISLogDBWorkerAgentUser##

	ALTER DATABASE [SSISDB] SET MULTI_USER WITH ROLLBACK IMMEDIATE
END TRY
BEGIN CATCH
	ALTER DATABASE [SSISDB] SET MULTI_USER WITH ROLLBACK IMMEDIATE
END CATCH
   
END
GO

GRANT EXECUTE ON [internal].[disable_scaleout] TO [SSIS_ADMIN]
GO
IF (OBJECT_ID('[catalog].[enable_scaleout]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[enable_scaleout]
END
GO

CREATE PROCEDURE [catalog].[enable_scaleout]

AS
BEGIN
SET NOCOUNT ON

IF (IS_SRVROLEMEMBER('sysadmin') <> 1)
	BEGIN
		RAISERROR(27260, 16, 1) WITH NOWAIT
		RETURN 1
	END

DECLARE @agentPassword nvarchar(256)
DECLARE @servername sysname
DECLARE @connectionString nvarchar(max)
DECLARE @hardcodedCatalogName nvarchar(max)

EXEC [internal].[GenerateRandomPasswords] @charset =  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789>_!@#$%&=?<>*()^&=-;.', @password = @agentPassword OUTPUT
SET @servername = CONVERT(sysname, SERVERPROPERTY('servername'))
SET @hardcodedCatalogName = 'SSISDB'
SET @connectionString = 'Data Source=' + @servername + ';Initial Catalog=' + @hardcodedCatalogName + ';User Id=##MS_SSISLogDBWorkerAgentLogin##;Password=''' + @agentPassword + ''';'

EXEC [internal].[update_logdb_info] @servername, @connectionString
EXEC [internal].[enable_scaleout] @agentPassword

END
GO

GRANT EXECUTE ON [catalog].[enable_scaleout] TO [SSIS_ADMIN]
GO

GRANT EXECUTE ON [internal].[append_execution_component_phases_for_worker] TO ssis_cluster_worker
GO

GRANT EXECUTE ON [internal].[append_event_message_for_worker] TO ssis_cluster_worker
GO

GRANT EXECUTE ON [internal].[append_executable_statistics_for_worker] TO ssis_cluster_worker
GO

GRANT EXECUTE ON [internal].[append_message_context_for_worker] TO ssis_cluster_worker
GO

GRANT EXECUTE ON [internal].[append_execution_data_statistics_for_worker] TO ssis_cluster_worker
GO
IF (OBJECT_ID('[catalog].[create_environment_variable]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[create_environment_variable]
END
GO

CREATE PROCEDURE [catalog].[create_environment_variable]
        @folder_name        sysname,                  
        @environment_name   nvarchar(128),                  
        @variable_name      nvarchar(128),            
        @data_type          nvarchar(128),            
        @sensitive          bit,                      
        @value              sql_variant,              
        @description        nvarchar(1024)= NULL
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON 
    
    DECLARE @result bit
    
    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 5) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 5) WITH NOWAIT
            RETURN 1
    END   
     
    DECLARE @binary_value   varbinary(MAX)
    
    DECLARE @sqlString    nvarchar(1024) 
    DECLARE @key_name               [internal].[adt_name] 
    DECLARE @certificate_name       [internal].[adt_name] 
    DECLARE @variable_type          [nvarchar](128)
    DECLARE @return_value           bit = 1
    
    IF (@folder_name IS NULL OR @environment_name IS NULL 
            OR @variable_name IS NULL OR @data_type IS NULL
            OR @sensitive IS NULL OR @value IS NULL)
    BEGIN
        RAISERROR(27138, 16 , 1) WITH NOWAIT 
        RETURN 1 
    END
    
    IF [internal].[is_valid_name](@variable_name) = 0
    BEGIN
        RAISERROR(27180, 16, 1, @variable_name) WITH NOWAIT
        RETURN 1
    END
    
    IF NOT EXISTS (SELECT [ssis_data_type] FROM [internal].[data_type_mapping]
                  WHERE [ssis_data_type] = @data_type)
    BEGIN
        RAISERROR(27159, 16 , 1) WITH NOWAIT 
        RETURN 1         
    END        
    
    SET @variable_type = CONVERT(nvarchar(128), SQL_VARIANT_PROPERTY(@value, 'BaseType'))
    IF (@variable_type IS NULL)
    BEGIN
        RAISERROR(27159, 16 , 1) WITH NOWAIT 
        RETURN 1   
    END
    
    EXEC @return_value = [internal].[check_data_type_value] 
        @value, @data_type
 
    IF (@return_value <> 0)         
    
    BEGIN
        RETURN 1               
    END 
    
    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                         
    BEGIN TRY
    
        
    DECLARE @environment_id bigint;
    EXECUTE AS CALLER
        SET @environment_id = (SELECT env.[environment_id]
                                FROM [catalog].[environments] env INNER JOIN [catalog].[folders] fld
                                ON env.[folder_id] = fld.[folder_id]
                                AND env.[name] = @environment_name
                                AND fld.name = @folder_name);
    REVERT
    IF @environment_id IS NULL
    BEGIN
        RAISERROR(27182 , 16 , 1, @environment_name) WITH NOWAIT
    END
    EXECUTE AS CALLER
        SET @result = [internal].[check_permission]
        (
            3,
            @environment_id,
            2
         )
   REVERT
   IF @result = 0
   BEGIN
       RAISERROR(27182 , 16 , 1, @environment_name) WITH NOWAIT
   END    
           
        IF EXISTS (SELECT [variable_id] FROM [internal].[environment_variables]
                        WHERE [environment_id]= @environment_id AND [name] = @variable_name)
        BEGIN
            RAISERROR(27173 , 16 , 1, @variable_name) WITH NOWAIT   
        END
    
        
        IF (@sensitive = 1)
        BEGIN
            SET @key_name = 'MS_Enckey_Env_'+CONVERT(varchar,@environment_id)
            SET @certificate_name = 'MS_Cert_Env_'+CONVERT(varchar,@environment_id)
            
            SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
                                + ' DECRYPTION BY CERTIFICATE ' + @certificate_name  
            EXECUTE sp_executesql @sqlString
            
            
            
            
            IF @data_type = 'datetime'
            BEGIN
                SET @binary_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(4000),CONVERT(datetime2,@value)))
            END
            
            ELSE IF @data_type = 'single' OR @data_type = 'double' OR @data_type = 'decimal'
            BEGIN
                SET @binary_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(4000),CONVERT(decimal(38,18),@value)))
            END
                        
            ELSE
            BEGIN
                SET @binary_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(MAX),@value))   
            END
            
            SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString    
            
            INSERT INTO  [internal].[environment_variables] ([environment_id], [name], [description], [type], [sensitive], [value], [sensitive_value], [base_data_type])      
                VALUES (@environment_id, @variable_name, @description, @data_type, @sensitive, null, @binary_value, @variable_type)
        END
        
        
        ELSE    
        BEGIN
            INSERT INTO  [internal].[environment_variables] ([environment_id], [name], [description], [type], [sensitive], [value], [sensitive_value], [base_data_type])      
                VALUES (@environment_id, @variable_name, @description, @data_type, @sensitive, @value, null, @variable_type)            
        END
                
    
    
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                                  
        THROW 
    END CATCH
    
    RETURN 0  
    
GO
IF (OBJECT_ID('[catalog].[set_environment_variable_property]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[set_environment_variable_property]
END
GO

CREATE PROCEDURE [catalog].[set_environment_variable_property]
        @folder_name        nvarchar(128),        
        @environment_name   nvarchar(128),        
        @variable_name      nvarchar(128),        
        @property_name      nvarchar(128),        
        @property_value     nvarchar(4000)        
AS
    SET NOCOUNT ON 
    
    DECLARE @result bit
    
    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 7) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 7) WITH NOWAIT
            RETURN 1
    END    
    
    IF (@folder_name IS NULL OR @environment_name IS NULL OR
            @variable_name IS NULL OR @property_name IS NULL)
    BEGIN
        RAISERROR(27138, 16 , 6) WITH NOWAIT 
        RETURN 1     
    END    

    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                        
    
    BEGIN TRY   
        
    DECLARE @environment_id bigint;
    EXECUTE AS CALLER
        SET @environment_id = (SELECT env.[environment_id]
                                FROM [catalog].[environments] env INNER JOIN [catalog].[folders] fld
                                ON env.[folder_id] = fld.[folder_id]
                                AND env.[name] = @environment_name
                                AND fld.name = @folder_name);
    REVERT
    IF @environment_id IS NULL
    BEGIN
        RAISERROR(27182 , 16 , 1, @environment_name) WITH NOWAIT
    END
    EXECUTE AS CALLER
        SET @result = [internal].[check_permission]
        (
            3,
            @environment_id,
            2
         )
   REVERT
   IF @result = 0
   BEGIN
       RAISERROR(27182 , 16 , 1, @environment_name) WITH NOWAIT
   END 
        
        
    DECLARE @variable_id    bigint
    SET @variable_id = (SELECT [variable_id] FROM [internal].[environment_variables]
                            WHERE [environment_id] = @environment_id AND [name] = @variable_name)
    IF (@variable_id IS NULL)
    BEGIN
        RAISERROR(27183 , 16 , 1, @variable_name) WITH NOWAIT
    END  

        IF (@property_name = 'DESCRIPTION')
        BEGIN
            UPDATE [internal].[environment_variables] 
                SET [description] = @property_value 
                WHERE [environment_id] = @environment_id AND [name] = @variable_name
            IF @@ROWCOUNT <> 1
            BEGIN
                RAISERROR(27112, 16, 1, N'environment_variables') WITH NOWAIT
            END
        END
        ELSE IF (@property_name = 'NAME')
        BEGIN
            
            IF [internal].[is_valid_name](@property_value) = 0
            BEGIN
                RAISERROR(27180, 16, 1, @property_value) WITH NOWAIT
            END
            
            
            IF EXISTS (SELECT [name] FROM [internal].[environment_variables]
                WHERE [name] = @property_value AND [environment_id] = @environment_id
                AND [name] <> @variable_name)
            BEGIN
                RAISERROR(27173 , 16 , 1, @property_value) WITH NOWAIT
            END
            UPDATE [internal].[environment_variables] 
                SET [name] = @property_value 
                WHERE [environment_id] = @environment_id AND [name] = @variable_name
            IF @@ROWCOUNT <> 1
            BEGIN
                RAISERROR(27112, 16, 1, N'environment_variables') WITH NOWAIT
            END
        END
        
        ELSE IF (@property_name = 'TYPE')
        BEGIN
            DECLARE @sensitive bit
            DECLARE @origin_data_type nvarchar(128) 
            DECLARE @value sql_variant
            DECLARE @new_value sql_variant
            DECLARE @sensitive_value varbinary(MAX)
            DECLARE @sqlString      nvarchar(1024) 
            DECLARE @key_name               [internal].[adt_name] 
            DECLARE @certificate_name       [internal].[adt_name]
            DECLARE @base_type nvarchar(128) 

            
            IF NOT EXISTS (SELECT [ssis_data_type] FROM [internal].[data_type_mapping]
                   WHERE [ssis_data_type] = @property_value)
            BEGIN
                RAISERROR(27159, 16 , 1) WITH NOWAIT
            END
            
            
            SELECT @sensitive = [sensitive], @origin_data_type = [type], 
                @value = [value], @sensitive_value = [sensitive_value]
                FROM [internal].[environment_variables]
                WHERE [variable_id] = @variable_id
            
            
            IF (@origin_data_type <> @property_value)
            BEGIN
                IF (@sensitive = 1)
                BEGIN
                    
                    SET @key_name = 'MS_Enckey_Env_'+CONVERT(varchar,@environment_id)
                    SET @certificate_name = 'MS_Cert_Env_'+CONVERT(varchar,@environment_id)
                    
                    DECLARE @decrypted_value    varbinary(MAX)
                                
                    SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
                                + ' DECRYPTION BY CERTIFICATE ' + @certificate_name  
                    EXECUTE sp_executesql @sqlString
            
                    SET @decrypted_value = DECRYPTBYKEY(@sensitive_value)
            
                    SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name   

                    SET @value = [internal].[get_value_by_data_type] (@decrypted_value, @origin_data_type)  

                    
                    
                    SET @new_value = [internal].convert_value(@value, @property_value)
                    if(@new_value IS NULL)
                    BEGIN
                        RAISERROR(27210, 16, 1, @origin_data_type, @property_value) WITH NOWAIT
                    END

                    
                    SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
                                + ' DECRYPTION BY CERTIFICATE ' + @certificate_name  
                    EXECUTE sp_executesql @sqlString
                    IF @property_value = 'datetime'
                    BEGIN
                        SET @sensitive_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(4000),CONVERT(datetime2,@new_value)))
                    END
            
                    ELSE IF @property_value = 'single' OR @property_value = 'double' OR @property_value = 'decimal'
                    BEGIN
                        SET @sensitive_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(4000),CONVERT(decimal(38,18),@new_value)))
                    END
                        
                    ELSE
                    BEGIN
                        SET @sensitive_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(MAX),@new_value))   
                    END
            
                    SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
                    EXECUTE sp_executesql @sqlString

                    SET @base_type = CONVERT(sysname, SQL_VARIANT_PROPERTY(@new_value, 'BaseType'));

                    UPDATE [internal].[environment_variables] 
                        SET [type] = @property_value, [sensitive_value] = @sensitive_value, 
                            [base_data_type] = @base_type
                        WHERE [environment_id] = @environment_id AND [name] = @variable_name
                    IF @@ROWCOUNT <> 1
                    BEGIN
                        RAISERROR(27112, 16, 1, N'environment_variables') WITH NOWAIT
                    END

                END

                ELSE
                BEGIN
                    
                    SET @new_value = [internal].convert_value(@value, @property_value)
                    if(@new_value IS NULL)
                    BEGIN
                        RAISERROR(27210, 16, 1, @origin_data_type, @property_value) WITH NOWAIT
                    END
                    SET @base_type = CONVERT(sysname, SQL_VARIANT_PROPERTY(@new_value, 'BaseType'));

                    UPDATE [internal].[environment_variables] 
                        SET [type] = @property_value, [value] = @new_value, [base_data_type] = @base_type
                        WHERE [environment_id] = @environment_id AND [name] = @variable_name
                    IF @@ROWCOUNT <> 1
                    BEGIN
                        RAISERROR(27112, 16, 1, N'environment_variables') WITH NOWAIT
                    END
                END     
            END    
        END
        ELSE
        BEGIN
            RAISERROR(27101, 16 , 1, 'DESCRIPTION') WITH NOWAIT      
        END
    
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                                  
        THROW 
    END CATCH
    
    RETURN 0  
GO
IF (OBJECT_ID('[catalog].[set_environment_variable_value]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[set_environment_variable_value]
END
GO

CREATE PROCEDURE [catalog].[set_environment_variable_value]
        @folder_name        nvarchar(128),        
        @environment_name   nvarchar(128),        
        @variable_name      nvarchar(128),        
        @value              sql_variant           
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON 
    
    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 8) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 8) WITH NOWAIT
            RETURN 1
    END
    
    DECLARE @data_type      nvarchar(128)

    DECLARE @sqlString      nvarchar(1024) 
    DECLARE @key_name               [internal].[adt_name] 
    DECLARE @certificate_name       [internal].[adt_name] 
    
    DECLARE @binary_value   varbinary(MAX)
    DECLARE @sensitive      bit
    DECLARE @result bit
    DECLARE @return_value           bit = 1
    
    DECLARE @variable_type nvarchar(128)
    
    IF (@folder_name IS NULL OR @environment_name IS NULL OR
            @variable_name IS NULL OR @value IS NULL)
    BEGIN
        RAISERROR(27138, 16 , 6) WITH NOWAIT 
        RETURN 1     
    END    
    
    SET @variable_type = CONVERT(nvarchar(128), SQL_VARIANT_PROPERTY(@value, 'BaseType'))
    IF (@variable_type IS NULL)
    BEGIN
        RAISERROR(27159, 16 , 1) WITH NOWAIT 
        RETURN 1   		
    END

    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                          
    BEGIN TRY

        
    DECLARE @environment_id bigint;
    EXECUTE AS CALLER
        SET @environment_id = (SELECT env.[environment_id]
                                FROM [catalog].[environments] env INNER JOIN [catalog].[folders] fld
                                ON env.[folder_id] = fld.[folder_id]
                                AND env.[name] = @environment_name
                                AND fld.name = @folder_name);
    REVERT
    IF @environment_id IS NULL
    BEGIN
        RAISERROR(27182 , 16 , 1, @environment_name) WITH NOWAIT
    END
    EXECUTE AS CALLER
        SET @result = [internal].[check_permission]
        (
            3,
            @environment_id,
            2
         )
   REVERT
   IF @result = 0
   BEGIN
       RAISERROR(27182 , 16 , 1, @environment_name) WITH NOWAIT
   END  
           
        
    DECLARE @variable_id    bigint
    SET @variable_id = (SELECT [variable_id] FROM [internal].[environment_variables]
                            WHERE [environment_id] = @environment_id AND [name] = @variable_name)
    IF (@variable_id IS NULL)
    BEGIN
        RAISERROR(27183 , 16 , 1, @variable_name) WITH NOWAIT
    END 
        
        SET @sensitive = (SELECT [sensitive] FROM [internal].[environment_variables]
                                WHERE [environment_id] = @environment_id AND [name] = @variable_name)
        IF (@sensitive IS NULL)
        BEGIN
            RAISERROR(27154 , 16 , 1) WITH NOWAIT  
        END 

        SET @data_type = (SELECT [type] FROM [internal].[environment_variables]
                                WHERE [environment_id] = @environment_id AND [name] = @variable_name)
                                
        EXEC @return_value = [internal].[check_data_type_value] 
            @value, @data_type
     
        IF (@return_value <> 0)         
        
        BEGIN
            RAISERROR(27147, 16 , 1, @data_type) WITH NOWAIT
        END 
    
        IF (@sensitive = 1)
        BEGIN
            SET @key_name = 'MS_Enckey_Env_'+CONVERT(varchar,@environment_id)
            SET @certificate_name = 'MS_Cert_Env_'+CONVERT(varchar,@environment_id)
            
            SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
                                + ' DECRYPTION BY CERTIFICATE ' + @certificate_name  
            EXECUTE sp_executesql @sqlString
            
            IF @data_type = 'datetime'
            BEGIN
                SET @binary_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(4000),CONVERT(datetime2, @value)))
            END
            
            ELSE IF @data_type = 'single' OR @data_type = 'double' OR @data_type = 'decimal'
            BEGIN
                SET @binary_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(4000),CONVERT(decimal(38,18),@value)))
            END
                        
            ELSE
            BEGIN
                SET @binary_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(MAX),@value))   
            END
            
            SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString
            
            UPDATE [internal].[environment_variables] 
                SET [sensitive_value] = @binary_value, [base_data_type] = @variable_type
                WHERE [environment_id] = @environment_id AND [name] = @variable_name 
            IF @@ROWCOUNT <> 1
            BEGIN
                RAISERROR(27112, 16, 1, N'environment_variables') WITH NOWAIT
            END            
        END   
        
        ELSE
        BEGIN
            UPDATE [internal].[environment_variables] 
                SET [value] = @value, [base_data_type] = @variable_type
                WHERE [environment_id] = @environment_id AND [name] = @variable_name  
            IF @@ROWCOUNT <> 1
            BEGIN
                RAISERROR(27112, 16, 1, N'environment_variables') WITH NOWAIT
            END   
        END  
    
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                                  
        THROW 
    END CATCH
    
    RETURN 0
GO
IF (OBJECT_ID('[catalog].[set_environment_variable_protection]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[set_environment_variable_protection]
END
GO

CREATE PROCEDURE [catalog].[set_environment_variable_protection]
        @folder_name        nvarchar(128),        
        @environment_name   nvarchar(128),        
        @variable_name      nvarchar(128),        
        @sensitive          bit                   
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON 
    
    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 9) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 9) WITH NOWAIT
            RETURN 1
    END
    
    DECLARE @sqlString      nvarchar(1024) 
    DECLARE @key_name               [internal].[adt_name] 
    DECLARE @certificate_name       [internal].[adt_name] 
    
    DECLARE @binary_value   varbinary(MAX)
    DECLARE @value sql_variant
    
    DECLARE @value_sensitive bit
    DECLARE @data_type      nvarchar(128)
    
    DECLARE @result bit
    
    IF (@folder_name IS NULL OR @environment_name IS NULL OR
            @variable_name IS NULL OR @sensitive IS NULL)
    BEGIN
        RAISERROR(27138, 16 , 6) WITH NOWAIT 
        RETURN 1     
    END 
          
    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                          
    BEGIN TRY  
    
        
    DECLARE @environment_id bigint;
    EXECUTE AS CALLER
        SET @environment_id = (SELECT env.[environment_id]
                                FROM [catalog].[environments] env INNER JOIN [catalog].[folders] fld
                                ON env.[folder_id] = fld.[folder_id]
                                AND env.[name] = @environment_name
                                AND fld.name = @folder_name);
    REVERT
    IF @environment_id IS NULL
    BEGIN
        RAISERROR(27182 , 16 , 1, @environment_name) WITH NOWAIT
    END
    EXECUTE AS CALLER
        SET @result = [internal].[check_permission]
        (
            3,
            @environment_id,
            2
         )
   REVERT
   IF @result = 0
   BEGIN
       RAISERROR(27182 , 16 , 1, @environment_name) WITH NOWAIT
   END  
        
        
    DECLARE @variable_id    bigint
    SET @variable_id = (SELECT [variable_id] FROM [internal].[environment_variables]
                            WHERE [environment_id] = @environment_id AND [name] = @variable_name)
    IF (@variable_id IS NULL)
    BEGIN
        RAISERROR(27183 , 16 , 1, @variable_name) WITH NOWAIT
    END     
        
        SET @value_sensitive = (SELECT [sensitive] FROM [internal].[environment_variables]
                                WHERE [environment_id] = @environment_id AND [name] = @variable_name)
                                
        SET @data_type = (SELECT [type] FROM [internal].[environment_variables]
                                WHERE [environment_id] = @environment_id AND [name] = @variable_name)
                                
        IF (@value_sensitive IS NULL)
        BEGIN
            RAISERROR(27154 , 16 , 1) WITH NOWAIT
        END     
        
        SET @key_name = 'MS_Enckey_Env_'+CONVERT(varchar,@environment_id)
        SET @certificate_name = 'MS_Cert_Env_'+CONVERT(varchar,@environment_id)
                
        IF (@sensitive = 1 AND @value_sensitive = 0) 
        BEGIN
            SET @value = (SELECT [value] FROM [internal].[environment_variables]
                            WHERE [environment_id] = @environment_id AND [name] = @variable_name )
                     
            SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
                                + ' DECRYPTION BY CERTIFICATE ' + @certificate_name  
            EXECUTE sp_executesql @sqlString
            
            IF @data_type = 'datetime'
            BEGIN
                SET @binary_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(4000),CONVERT(datetime2,@value)))
            END
            
            ELSE IF @data_type = 'single' OR @data_type = 'double' OR @data_type = 'decimal'
            BEGIN
                SET @binary_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(4000),CONVERT(decimal(38,18),@value)))
            END
                        
            ELSE
            BEGIN
                SET @binary_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(MAX),@value))   
            END
            
            SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString
            
            UPDATE [internal].[environment_variables] 
                SET [sensitive] = 1, [sensitive_value] = @binary_value, [value] = null
                WHERE [environment_id] = @environment_id AND [name] = @variable_name    
            IF @@ROWCOUNT <> 1
            BEGIN
                RAISERROR(27112, 16, 1, N'environment_variables') WITH NOWAIT
            END
        END 
        
        ELSE IF (@sensitive = 0 AND @value_sensitive = 1) 
        BEGIN
            DECLARE @decrypted_value    varbinary(MAX)
            
            SET @binary_value = (SELECT [sensitive_value] FROM [internal].[environment_variables]
                            WHERE [environment_id] = @environment_id AND [name] = @variable_name )
            
            SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
                                + ' DECRYPTION BY CERTIFICATE ' + @certificate_name  
            EXECUTE sp_executesql @sqlString
            
            SET @decrypted_value = DECRYPTBYKEY(@binary_value)
            
            SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString
            
            SET @value = [internal].[get_value_by_data_type] (@decrypted_value, @data_type)
            
            IF @value IS NULL
            BEGIN
                RAISERROR(27116 , 16 , 1) WITH NOWAIT            
            END
            
            UPDATE [internal].[environment_variables] 
                SET [sensitive] = @sensitive, [sensitive_value] = null, [value] = @value
                WHERE [environment_id] = @environment_id AND [name] = @variable_name  
            IF @@ROWCOUNT <> 1
            BEGIN
                RAISERROR(27112, 16, 1, N'environment_variables') WITH NOWAIT
            END
        END
        
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                                  
        THROW 
    END CATCH   
    
    RETURN 0
GO
IF (OBJECT_ID('[catalog].[deploy_packages]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[deploy_packages]
END
GO

CREATE PROCEDURE [catalog].[deploy_packages]
    @folder_name nvarchar(128),
    @project_name nvarchar(128),
    @packages_table Package_Table_Type ReadOnly,
    @operation_id bigint = NULL output
AS
    SET NOCOUNT ON
    
    DECLARE @deploy_id  bigint
    DECLARE @version_id bigint
    DECLARE @project_id bigint
    DECLARE @retval int
    DECLARE @time   datetimeoffset
    DECLARE @status int   

    IF (@folder_name IS NULL OR @project_name IS NULL)
    BEGIN
        RAISERROR(27138, 16 , 6) WITH NOWAIT 
        RETURN 1 
    END
    
    IF [internal].[is_valid_name](@project_name) = 0
    BEGIN
        RAISERROR(27145, 16, 1, @project_name) WITH NOWAIT
        RETURN 1
    END

    EXEC @retval = [internal].[create_deploy_package_operation] 
                       @folder_name,
                       @project_name,
                       @deploy_id output
    IF @retval <> 0
    BEGIN
        RETURN 1
    END   
    
    SET @operation_id = @deploy_id

    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                        
    BEGIN TRY
        EXEC @retval = [internal].[prepare_packages_deploy] 
                           @folder_name,
                           @project_name,
                           @packages_table,
                           @deploy_id,
                           @version_id output,
                           @project_id output
        IF @retval <> 0
        BEGIN
            RAISERROR(27230, 16, 1) WITH NOWAIT
        END
    
        EXEC @retval = [internal].[deploy_packages_internal]
                            @deploy_id,
                            @version_id,
                            @project_id,
                            @project_name,
                            @folder_name
        IF @retval <> 0
        BEGIN
            RAISERROR(27230, 16, 1) WITH NOWAIT
        END      
    
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        SET @time = SYSDATETIMEOFFSET()
        
        UPDATE [internal].[operations] SET 
            [end_time]  = SYSDATETIMEOFFSET(),
            [status]    = 4
            WHERE operation_id    = @operation_id;
        THROW 
    END CATCH
    
    
    DECLARE @process_id bigint
    SELECT @process_id = [process_id] FROM [catalog].[operations] 
        WHERE operation_id = @deploy_id
    
    SET @status = NULL
    WHILE @status IS NULL
    BEGIN
        WAITFOR DELAY '00:00:01'  
        
        SELECT @status = [status] FROM [catalog].[operations] 
                    WHERE operation_id = @deploy_id AND [status] <> 2
                    
        IF @status IS NULL
        BEGIN
           
           IF NOT EXISTS (SELECT [process_id] 
                 FROM internal.get_isserver_processes() 
                 WHERE [process_id]= @process_id)
            BEGIN
               
               
               DECLARE @end_time datetimeoffset(7)
               
               SET @status = 4
               SET @end_time = SYSDATETIMEOFFSET()
               EXEC @retval = [internal].[update_package_deployment_status]
                      @deploy_id,
                      @version_id,
                      @end_time,
                      4,
                      ''  
           END
        END
    END
    
    IF @status = 7
    BEGIN
        RETURN 0
    END
    ELSE
    BEGIN
        RAISERROR (27231, 16,1, @deploy_id) WITH NOWAIT
        RETURN 1
    END
GO
IF (OBJECT_ID('[catalog].[set_object_parameter_value]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[set_object_parameter_value]
END
GO

CREATE PROCEDURE [catalog].[set_object_parameter_value]
        @object_type            smallint,
        @folder_name            nvarchar(128),
        @project_name           nvarchar(128),
        @parameter_name         nvarchar(128),
        @parameter_value        sql_variant,
        @object_name            nvarchar(260) = null,
        @value_type             char(1) = 'V'
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON
     
    DECLARE @result bit
    DECLARE @sensitive bit
    DECLARE @parameter_id bigint
    DECLARE @parameter_data_type nvarchar(128)
    DECLARE @parameter_type nvarchar(128)
    DECLARE @return_value           bit = 1
    
    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
    END
       
    IF (@folder_name IS NULL OR @project_name IS NULL 
            OR @parameter_name IS NULL OR @parameter_value IS NULL)
    BEGIN
        RAISERROR(27138, 16 , 6) WITH NOWAIT 
        RETURN 1     
    END    
    
    IF @object_type NOT IN (20, 30)
    BEGIN
        RAISERROR(27101, 16 , 1, N'object_type') WITH NOWAIT
        RETURN 1;
    END
    
    IF @value_type NOT IN ('V', 'R')
    BEGIN
        RAISERROR(27101, 16 , 1, N'value_type') WITH NOWAIT
        RETURN 1;
    END
    
    SET @parameter_type = CONVERT(nvarchar(128), SQL_VARIANT_PROPERTY(@parameter_value, 'BaseType'));
    IF @parameter_type IS NULL
    BEGIN
        RAISERROR(27159, 16 , 1) WITH NOWAIT 
        RETURN 1   
    END  
       
    DECLARE @encrypted_value        varbinary(MAX)
    DECLARE @sqlString              nvarchar(1024)
    DECLARE @key_name               [internal].[adt_name]
    DECLARE @certificate_name       [internal].[adt_name]
    
    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                        
    
    BEGIN TRY
    
        
    DECLARE @project_id bigint;
    EXECUTE AS CALLER
        SET @project_id = (SELECT projs.[project_id]
                                FROM [catalog].[projects] projs INNER JOIN [catalog].[folders] fld
                                ON projs.[folder_id] = fld.[folder_id]
                                AND projs.[name] = @project_name
                                AND fld.name = @folder_name);
    REVERT
    IF @project_id IS NULL
    BEGIN
        RAISERROR(27109 , 16 , 1, @project_name) WITH NOWAIT
    END
    EXECUTE AS CALLER
        SET @result = [internal].[check_permission]
        (
            2,
            @project_id,
            2
         )
    REVERT
    IF @result = 0
    BEGIN
        RAISERROR(27109 , 16 , 1, @project_name) WITH NOWAIT
    END
        
        SET @key_name = 'MS_Enckey_Proj_'+CONVERT(varchar,@project_id)
        SET @certificate_name = 'MS_Cert_Proj_'+CONVERT(varchar,@project_id)
        
        IF @object_type = 20  
        BEGIN
            SELECT @parameter_id = [parameter_id], @sensitive = [sensitive],
                @parameter_data_type = [data_type]
                FROM [catalog].[object_parameters]
                WHERE [project_id] = @project_id AND [object_type] = @object_type
                AND [parameter_name] = @parameter_name COLLATE SQL_Latin1_General_CP1_CS_AS
        END
        ELSE IF @object_type = 30  
        BEGIN
            SELECT @parameter_id = [parameter_id], @sensitive = [sensitive],
                @parameter_data_type = [data_type]
                FROM [catalog].[object_parameters]
                WHERE [project_id] = @project_id AND [object_type] = @object_type
                AND [parameter_name] = @parameter_name COLLATE SQL_Latin1_General_CP1_CS_AS
                AND [object_name] = @object_name
        END
        
        IF @parameter_id IS NULL
        BEGIN
            RAISERROR(27106 , 16 , 1, @parameter_name) WITH NOWAIT     
        END
        
        IF @value_type = 'V'
        BEGIN
            EXEC @return_value = [internal].[check_data_type_value] 
                @parameter_value, @parameter_data_type
         
            IF (@return_value <> 0)         
            
            BEGIN
                RAISERROR(27147, 16 , 1, @parameter_data_type) WITH NOWAIT
            END 
            
            IF @sensitive = 0 
            BEGIN
                UPDATE [internal].[object_parameters]
                    SET [default_value] = @parameter_value,
                        [sensitive_default_value] = NULL,
                        [base_data_type] = @parameter_type,
                        [value_type] = @value_type,
                        [value_set] = 1,
                        [referenced_variable_name] = NULL
                WHERE parameter_id = @parameter_id
                IF @@ROWCOUNT <> 1
                BEGIN
                    RAISERROR(27112, 16, 1, N'object_parameters') WITH NOWAIT
                END
            END
            
            ELSE
            BEGIN
                SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
                                    + ' DECRYPTION BY CERTIFICATE ' + @certificate_name  
                EXECUTE sp_executesql @sqlString
                
                IF @parameter_data_type = 'datetime'
                BEGIN
                    SET @encrypted_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(4000),CONVERT(datetime2,@parameter_value)))
                END
                
                ELSE IF @parameter_data_type = 'single' OR @parameter_data_type = 'double' OR @parameter_data_type = 'decimal'
                BEGIN
                    SET @encrypted_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(4000),CONVERT(decimal(38,18),@parameter_value)))
                END
                            
                ELSE
                BEGIN
                    SET @encrypted_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(MAX),@parameter_value))   
                END
                
                SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
                
                EXECUTE sp_executesql @sqlString
                                
                UPDATE [internal].[object_parameters]
                    SET [default_value] = NULL,
                        [sensitive_default_value] = @encrypted_value,
                        [base_data_type] = @parameter_type,
                        [value_type] = @value_type,
                        [value_set] = 1,
                        [referenced_variable_name] = NULL
                WHERE parameter_id = @parameter_id
                IF @@ROWCOUNT <> 1
                BEGIN
                    RAISERROR(27112, 16, 1, N'object_parameters') WITH NOWAIT
                END                
            END
        END
        
        ELSE IF @value_type = 'R'  
        BEGIN
            UPDATE [internal].[object_parameters]
                SET [default_value] = NULL,
                    [sensitive_default_value] = NULL,
                    [base_data_type] = NULL,
                    [value_type] = @value_type,
                    [value_set] = 1,
                    [referenced_variable_name] = CONVERT(nvarchar(128), @parameter_value)
            WHERE parameter_id = @parameter_id
            IF @@ROWCOUNT <> 1
            BEGIN
                RAISERROR(27112, 16, 1, N'object_parameters') WITH NOWAIT
            END
                   
            
            
            IF EXISTS(
            SELECT envs.[environment_name]
            FROM ([catalog].[object_parameters] params 
            INNER JOIN [internal].[environment_references] envs
                ON params.[project_id] = envs.[project_id]) 
            INNER JOIN([internal].[environments] e 
            INNER JOIN [internal].[folders] f
                ON e.[folder_id] = f.[folder_id] 
            INNER JOIN [internal].[environment_variables] vars
                ON e.[environment_id] = vars.[environment_id])
                ON envs.[environment_folder_name] = f.[name] 
                AND envs.[environment_name] = e.[environment_name]
                AND params.[referenced_variable_name] = vars.[name] 
                AND [params].[data_type] <> vars.[type]
            WHERE params.[parameter_id] = @parameter_id 
                AND envs.[reference_type] = 'A'
            
            UNION 
            
            SELECT envs.[environment_name]
            FROM ([catalog].[object_parameters] params 
            INNER JOIN [internal].[environment_references] envs
                ON params.[project_id] = envs.[project_id] 
            INNER JOIN [catalog].[projects] projs 
                ON projs.[project_id] = params.[project_id]) 
            INNER JOIN ([internal].[environments] e  
                INNER JOIN [internal].[environment_variables] vars
                    ON e.[environment_id] = vars.[environment_id])
                ON e.[folder_id] = projs.[folder_id] 
                AND envs.[environment_name] = e.[environment_name]
                AND params.[referenced_variable_name] = vars.[name] 
                AND [params].[data_type] <> vars.[type]
            WHERE params.[parameter_id] = @parameter_id AND envs.[reference_type] = 'R'                     
            )
            BEGIN
                
                RAISERROR(27148 , 10 , 1) WITH NOWAIT 
            END
        END
        
        
        
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                              
              
        THROW 
    END CATCH
    
    RETURN 0      
    
GO
IF (OBJECT_ID('[catalog].[create_execution]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[create_execution]
END
GO

CREATE PROCEDURE [catalog].[create_execution]
        @folder_name nvarchar(128), 
        @project_name nvarchar(128), 
        @package_name nvarchar(260), 
        @reference_id bigint = null, 
        @use32bitruntime bit = 0, 
		@runinscaleout	bit = null,  
		@useanyworker	bit = 1,  
        @execution_id bigint output
WITH EXECUTE AS 'AllSchemaOwner'
AS
    SET NOCOUNT ON
    
    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
    END
    
    DECLARE @created_time datetimeoffset
    DECLARE @return_value   int
    DECLARE @operation_id  bigint
    DECLARE @result     bit
    DECLARE @environment_id bigint
    DECLARE @environment_found bit
    IF (@folder_name IS NULL OR @project_name IS NULL 
            OR @package_name IS NULL OR @use32bitruntime IS NULL)
    BEGIN
        RAISERROR(27138, 16 , 1) WITH NOWAIT 
        RETURN 1 
    END
     
    IF (@runinscaleout IS NULL)
    BEGIN
        SET @runinscaleout = 0
        IF (EXISTS(SELECT * FROM [SSISDB].[catalog].[catalog_properties] WHERE [property_name] = 'IS_SCALEOUT_ENABLED' AND [property_value]='TRUE')
            AND EXISTS(SELECT * FROM [SSISDB].[catalog].[catalog_properties] WHERE [property_name] = 'DEFAULT_EXECUTION_MODE' AND [property_value]='1'))
        BEGIN
            SET @runinscaleout = 1
        END
    END

    BEGIN TRY
        
        SET @created_time = SYSDATETIMEOFFSET()
        EXEC @return_value = [internal].[insert_operation] 
                        200,  
                        @created_time,          
                        20,    
                        NULL,                     
                        @project_name,          
                        1,  
                        null,                   
                        null,                   
                        @caller_sid,            
                        @caller_name,           
                        null,                   
                        null,                   
                        null,                   
                        @operation_id OUTPUT  
        IF @return_value <> 0
            RETURN 1;

        
        EXECUTE AS CALLER
            EXEC @return_value = [internal].[init_object_permissions] 4, @operation_id, @caller_id
        REVERT 
               
        IF @return_value <> 0
        BEGIN
            
            RAISERROR(27153, 16, 1) WITH NOWAIT
            RETURN 1
        END 
        
        SET @execution_id = @operation_id
 
    END TRY
    BEGIN CATCH
        UPDATE [internal].[operations] 
            SET 
                [end_time]  = SYSDATETIME(),
                [status]    = 4
            WHERE operation_id    = @operation_id;
        THROW;
    END CATCH

    DECLARE @sqlString              nvarchar(1024)
    DECLARE @key_name               [internal].[adt_name]
    DECLARE @certificate_name       [internal].[adt_name]
    DECLARE @encryption_algorithm   nvarchar(255)
    
    DECLARE @env_key_name               [internal].[adt_name]
    DECLARE @env_certificate_name       [internal].[adt_name]
    
    DECLARE @project_key_name               [internal].[adt_name]
    DECLARE @project_certificate_name       [internal].[adt_name]
    
    DECLARE @server_operation_encryption_level int
    
    SET @encryption_algorithm = (SELECT [internal].[get_encryption_algorithm]())
    
    IF @encryption_algorithm IS NULL
    BEGIN
        RAISERROR(27156, 16, 1, 'ENCRYPTION_ALGORITHM') WITH NOWAIT
    END


    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY

        
        DECLARE @lock_result int
        DECLARE @retry int
        SET @lock_result = -1
        SET @retry = 2
        WHILE @retry > 0 AND @lock_result < 0
        BEGIN
        EXEC @lock_result = sp_getapplock 
                @Resource = 'MS_ISServer_Create_Execution',
                @LockTimeOut= 5000, 
                @LockMode = 'Exclusive'

            SET @retry = @retry - 1
        END

        IF @lock_result < 0
        BEGIN
            RAISERROR(27195, 16, 1) WITH NOWAIT  
        END

        DECLARE @project_id bigint
        DECLARE @version_id bigint
        
        
        EXECUTE AS CALLER
            SELECT @project_id = projs.[project_id],  
                   @version_id = projs.[object_version_lsn]
                FROM [catalog].[projects] projs INNER JOIN [catalog].[folders] fds
                ON projs.[folder_id] = fds.[folder_id] INNER JOIN [catalog].[packages] pkgs
                ON projs.[project_id] = pkgs.[project_id] 
                WHERE fds.[name] = @folder_name AND projs.[name] = @project_name
                AND pkgs.[name] = @package_name
        REVERT
        
        IF (@project_id IS NULL)
        BEGIN
            RAISERROR(27146, 16, 1) WITH NOWAIT
        END
        
        
        EXECUTE AS CALLER   
            SET @result =  [internal].[check_permission] 
                (
                    2,
                    @project_id,
                    3
                 ) 
        REVERT
        
        IF @result = 0
        BEGIN
            RAISERROR(27178, 16, 1, @project_name) WITH NOWAIT
        END

		
		IF (0 = 0 and @runinscaleout = 1)		
		BEGIN
			EXECUTE AS CALLER
				IF NOT((IS_MEMBER('ssis_admin') = 1) OR (IS_SRVROLEMEMBER('sysadmin') = 1) OR (IS_MEMBER('ssis_cluster_executor') = 1))
				BEGIN
					RAISERROR(27178, 16, 1, @project_name) WITH NOWAIT
				END
			REVERT
		END
		
        SELECT @server_operation_encryption_level = CONVERT(int,property_value)  
            FROM [catalog].[catalog_properties]
            WHERE property_name = 'SERVER_OPERATION_ENCRYPTION_LEVEL'

        IF @server_operation_encryption_level NOT in (1, 2)              
        BEGIN
            RAISERROR(27163    ,16,1,'SERVER_OPERATION_ENCRYPTION_LEVEL')
        END
        
        
        
        DECLARE @environment_name nvarchar(128)
        DECLARE @environment_folder_name nvarchar(128)
        DECLARE @reference_type char(1)
        
        
        IF(@reference_id IS NOT NULL)
        BEGIN
            
            EXECUTE AS CALLER
                SELECT @environment_name = environment_name,
                       @environment_folder_name = environment_folder_name,
                       @reference_type = reference_type
                FROM [catalog].[environment_references]
                WHERE project_id = @project_id AND reference_id = @reference_id
            REVERT
            IF (@environment_name IS NULL)
            BEGIN
                RAISERROR(27208, 16, 1, @reference_id) WITH NOWAIT
            END                                                     
            
            
            SET @environment_found = 1
            IF (@reference_type = 'A')
            BEGIN
                SELECT @environment_id = envs.[environment_id]
                FROM [internal].[folders] fds INNER JOIN [internal].[environments] envs
                ON fds.[folder_id] = envs.[folder_id]
                WHERE envs.[environment_name] = @environment_name AND fds.[name] = @environment_folder_name
            END
            ELSE IF (@reference_type = 'R')
            BEGIN
                SELECT @environment_id = envs.[environment_id]
                FROM  [internal].[projects] projs INNER JOIN [internal].[environments] envs
                ON projs.[folder_id] = envs.[folder_id]
                WHERE envs.[environment_name] = @environment_name AND projs.[project_id] = @project_id
            END
            IF (@environment_id IS NULL)
            BEGIN
                SET @environment_found = 0
            END
            
            EXECUTE AS CALLER
                SET @result =  [internal].[check_permission]
                    (
                        3,
                        @environment_id,
                        1
                     )
            REVERT
            IF @result = 0
            BEGIN
                SET @environment_found = 0
            END
            IF @environment_found = 0
            BEGIN
                RAISERROR(27182 , 16 , 1, @environment_name) WITH NOWAIT
            END
            
            
            
            IF EXISTS 
            (
                SELECT params.[parameter_name]
                FROM [internal].[environments] envs INNER JOIN [internal].[environment_variables] vars
                ON envs.[environment_id] = vars.[environment_id] INNER JOIN [catalog].[object_parameters] params
                ON vars.[name] = params.[referenced_variable_name] 
                WHERE params.[value_type] = 'R' AND params.[data_type] <> vars.[type] 
                AND params.[project_id] = @project_id
                AND (params.[object_type] = 20
                OR (params.[object_name] = @package_name
                AND params.[object_type] = 30))
                AND envs.[environment_id] = @environment_id          
            )
            BEGIN
                RAISERROR(27148, 16, 1) WITH NOWAIT
            END 
            
            
            IF EXISTS 
            (
                SELECT params.[parameter_name]
                FROM [internal].[environment_variables] vars INNER JOIN [catalog].[object_parameters] params
                ON vars.[name] = params.[referenced_variable_name] 
                WHERE params.[value_type] = 'R' AND params.[data_type] = vars.[type] 
                AND params.[sensitive] =0 AND vars.[sensitive] = 1
                AND params.[project_id] = @project_id
                AND (params.[object_type] = 20
                OR (params.[object_name] = @package_name
                AND params.[object_type] = 30))
                AND vars.[environment_id] = @environment_id          
            )
            BEGIN
                RAISERROR(27221, 16, 1) WITH NOWAIT
            END 
        END   
        
        UPDATE [internal].[operations]
            SET [object_id] = @project_id
            WHERE [operation_id] = @operation_id
        IF @@ROWCOUNT <> 1
        BEGIN
            RAISERROR(27112, 16, 1, N'operations') WITH NOWAIT
        END    
        
		DECLARE	@job_id 		uniqueidentifier = NULL
		
		DECLARE @input_data nvarchar(255)
		SET @input_data = (select 'use32bitruntime' AS name, CONVERT(nvarchar(255), @use32bitruntime) AS value For JSON PATH)

		IF (0 = 1 OR @runinscaleout = 1)
		BEGIN
			
			EXEC [internal].[create_job]
					@job_id out,
					1,
					@input_data,
					@caller_name,
					@useanyworker
		END
			
        
        INSERT into [internal].[executions]
                   (
                      execution_id,
                      folder_name,
                      project_name,
                      package_name,
                      reference_id,
                      reference_type,
                      environment_folder_name,
                      environment_name,
                      project_lsn,
                      executed_as_sid,
                      executed_as_name,
                      use32bitruntime,
					  job_id
                   )
            VALUES (
                      @operation_id,              
                      @folder_name,               
                      @project_name,              
                      @package_name,              
                      @reference_id,
                      @reference_type,        
                      @environment_folder_name,
                      @environment_name,
                      @version_id,                
                      @caller_sid,            
                      @caller_name,           
                      @use32bitruntime,
					  @job_id
                   )
        
    IF @server_operation_encryption_level = 1
    BEGIN
        
        SET @key_name = 'MS_Enckey_Exec_'+CONVERT(varchar,@execution_id)
        SET @certificate_name = 'MS_Cert_Exec_'+CONVERT(varchar,@execution_id)
    END
    ELSE BEGIN
        
        SET @key_name = 'MS_Enckey_Proj_Param_'+CONVERT(varchar,@project_id)
        SET @certificate_name = 'MS_Cert_Proj_Param_'+CONVERT(varchar,@project_id)
    END
        SET @sqlString = 'CREATE CERTIFICATE ' + @certificate_name + ' WITH SUBJECT = ''ISServerCertificate'''
        
        IF  NOT EXISTS (SELECT [name] FROM [sys].[certificates] WHERE [name] = @certificate_name)
            EXECUTE sp_executesql @sqlString 
        
        SET @sqlString = 'CREATE SYMMETRIC KEY ' + @key_name +' WITH ALGORITHM = ' 
                            + @encryption_algorithm + ' ENCRYPTION BY CERTIFICATE ' + @certificate_name
        
        IF  NOT EXISTS (SELECT [name] FROM [sys].[symmetric_keys] WHERE [name] = @key_name)
            EXECUTE sp_executesql @sqlString 
        
        SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
                + ' DECRYPTION BY CERTIFICATE ' + @certificate_name  
            EXECUTE sp_executesql @sqlString
                   

        
        IF @environment_id IS NOT NULL
        BEGIN
            SET @env_key_name = 'MS_Enckey_Env_'+CONVERT(varchar,@environment_id)
            SET @env_certificate_name = 'MS_Cert_Env_'+CONVERT(varchar,@environment_id)
            
            SET @sqlString = 'OPEN SYMMETRIC KEY ' + @env_key_name 
                    + ' DECRYPTION BY CERTIFICATE ' + @env_certificate_name  
                EXECUTE sp_executesql @sqlString        
        END
        
        
        SET @project_key_name = 'MS_Enckey_Proj_'+CONVERT(varchar,@project_id)
        SET @project_certificate_name = 'MS_Cert_Proj_'+CONVERT(varchar,@project_id)

        SET @sqlString = 'OPEN SYMMETRIC KEY ' + @project_key_name 
                + ' DECRYPTION BY CERTIFICATE ' + @project_certificate_name  
            EXECUTE sp_executesql @sqlString  

        
            
        INSERT INTO [internal].[execution_parameter_values]
        (  
            [execution_id], 
            [object_type], 
            [parameter_data_type], 
            [parameter_name], 
            [parameter_value],
            [sensitive_parameter_value],
            [base_data_type], 
            [sensitive], 
            [required], 
            [value_set], 
            [runtime_override]
        )
        SELECT  @execution_id, 
                [object_type], 
                [parameter_data_type], 
                [parameter_name],
                [default_value], 
                NULL, 
                [base_data_type],
                [sensitive], 
                [required], 
                [value_set], 
                0
        FROM [internal].[object_parameters] 
        WHERE [project_id] = @project_id 
        AND ([object_type] = 20 
        OR ([object_name] = @package_name 
        AND [object_type] = 30))
        AND sensitive = 0 
        AND [value_type] = 'V' 
        AND [project_version_lsn] = @version_id
             
        INSERT INTO [internal].[execution_parameter_values]
        (  
            [execution_id], 
            [object_type], 
            [parameter_data_type], 
            [parameter_name], 
            [parameter_value],
            [sensitive_parameter_value],
            [base_data_type], 
            [sensitive], 
            [required], 
            [value_set], 
            [runtime_override]
        )
        SELECT  @execution_id, 
                [object_type], 
                [parameter_data_type], 
                [parameter_name], 
                NULL, 
                ENCRYPTBYKEY(KEY_GUID(@key_name), DECRYPTBYKEY(sensitive_default_value)),
                [base_data_type],
                [sensitive], 
                [required], 
                [value_set], 
                0
        FROM [internal].[object_parameters] 
        WHERE [project_id] = @project_id 
        AND ([object_type] = 20 
        OR ([object_name] = @package_name 
        AND [object_type] = 30))
        AND sensitive = 1 
        AND [value_type] = 'V' 
        AND [project_version_lsn] = @version_id    
        
        
        DECLARE @server_logging_level [NVARCHAR](256)

        SELECT @server_logging_level = [property_value] 
        FROM [internal].[catalog_properties]
        WHERE [property_name] = 'SERVER_LOGGING_LEVEL' 

        DECLARE @bitfalse bit
        SET @bitfalse = 0
        
        INSERT INTO [internal].[execution_parameter_values]
        (
            [execution_id], 
            [object_type], 
            [parameter_data_type], 
            [parameter_name], 
            [parameter_value],
            [base_data_type],
            [sensitive], 
            [required], 
            [value_set], 
            [runtime_override]
        )
        VALUES 
        (
            @execution_id,
            50,
            'Boolean',
            'DUMP_ON_ERROR',
            CONVERT(sql_variant,@bitfalse),
            'bit',
            0,
            0,
            1,
            0
        ),
        (
            @execution_id,
            50,
            'Boolean',
            'DUMP_ON_EVENT',
            CONVERT(sql_variant,@bitfalse),
            'bit',
            0,
            0,
            1,
            0
        ),
        (
            @execution_id,
            50,
            'String',
            'DUMP_EVENT_CODE',
            CONVERT(sql_variant,'0'),
            'nvarchar',
            0,
            0,
            1,
            0
        ),
        (
            @execution_id,
            50,
            'Int32',
            'LOGGING_LEVEL',
            CONVERT(sql_variant,CONVERT(INT,@server_logging_level)),
            'int',
            0,
            0,
            1,
            0
        ),
        (
            @execution_id,
            50,
            'String',
            'CALLER_INFO',
            null,
            'nvarchar',
            0,
            0,
            1,
            0
        ),
        (
            @execution_id,
            50,
            'Boolean',
            'SYNCHRONIZED',
            CONVERT(sql_variant,@bitfalse),
            'bit',
            0,
            0,
            1,
            0
        )

        
        DECLARE @converted_logging_level int
        SET @converted_logging_level = CONVERT(int,@server_logging_level)
        IF (@converted_logging_level = 100)
        BEGIN
            DECLARE @server_customized_logging_level [NVARCHAR](256)
        
            SELECT @server_customized_logging_level = [property_value] 
            FROM [internal].[catalog_properties]
            WHERE [property_name] = 'SERVER_CUSTOMIZED_LOGGING_LEVEL'
        
            INSERT INTO [internal].[execution_parameter_values]
            (
                [execution_id], 
                [object_type], 
                [parameter_data_type], 
                [parameter_name], 
                [parameter_value],
                [base_data_type],
                [sensitive], 
                [required], 
                [value_set], 
                [runtime_override]
            )
            VALUES 
            (
                @execution_id,
                50,
                'String',
                'CUSTOMIZED_LOGGING_LEVEL',
                CONVERT(sql_variant,@server_customized_logging_level),
                'nvarchar',
                0,
                0,
                1,
                0
            )
        END

        
        IF @environment_id IS NOT NULL
        BEGIN
            INSERT INTO [internal].[execution_parameter_values]
            (  
                [execution_id], 
                [object_type], 
                [parameter_data_type], 
                [parameter_name], 
                [parameter_value],
                [sensitive_parameter_value], 
                [base_data_type],
                [sensitive], 
                [required], 
                [value_set], 
                [runtime_override]
            )
            SELECT  @execution_id, 
                    params.[object_type], 
                    params.[parameter_data_type], 
                    params.[parameter_name],
                    vars.[value], 
                    NULL, 
                    vars.[base_data_type],
                    params.[sensitive], 
                    params.[required], 
                    params.[value_set], 
                    0
            FROM [internal].[object_parameters] params 
            INNER JOIN [internal].[environment_variables] vars
                ON params.[referenced_variable_name] = vars.[name] 
            WHERE params.[project_id] = @project_id 
            AND (params.[object_type] = 20
            OR (params.[object_name] = @package_name 
            AND params.[object_type] = 30))
            AND vars.[sensitive] = 0 
            AND params.[value_type] = 'R' 
            AND params.[project_version_lsn] = @version_id
            AND vars.[environment_id] = @environment_id
            
            INSERT INTO [internal].[execution_parameter_values]
            (  
                [execution_id], 
                [object_type], 
                [parameter_data_type], 
                [parameter_name], 
                [parameter_value],
                [sensitive_parameter_value],
                [base_data_type], 
                [sensitive], 
                [required], 
                [value_set], 
                [runtime_override]
            )
            SELECT  @execution_id, 
                    params.[object_type], 
                    params.[parameter_data_type], 
                    params.[parameter_name],
                    NULL, 
                    ENCRYPTBYKEY(KEY_GUID(@key_name), DECRYPTBYKEY(vars.[sensitive_value])), 
                    vars.[base_data_type],
                    vars.[sensitive], 
                    params.[required], 
                    params.[value_set], 
                    0
            FROM [internal].[object_parameters] params 
            INNER JOIN [internal].[environment_variables] vars
                ON params.[referenced_variable_name] = vars.[name] 
            WHERE params.[project_id] = @project_id 
            AND (params.[object_type] = 20
            OR (params.[object_name] = @package_name 
            AND params.[object_type] = 30))
            AND vars.[sensitive] = 1 
            AND params.[value_type] = 'R' 
            AND params.[project_version_lsn] = @version_id        
            AND vars.[environment_id] = @environment_id
            
            SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @env_key_name
                EXECUTE sp_executesql @sqlString
        END
        
        
        UPDATE [internal].[execution_parameter_values]
        SET [sensitive_parameter_value] = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(4000),CONVERT(datetime2,parameter_value))),
        [parameter_value] = NULL
        WHERE [execution_id] = @operation_id 
        AND [sensitive] = 1 
        AND [parameter_value] IS NOT NULL
        AND [sensitive_parameter_value] IS NULL 
        AND [parameter_data_type] = 'datetime'
        
        UPDATE [internal].[execution_parameter_values]
        SET [sensitive_parameter_value] = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(4000),CONVERT(decimal(38,18),parameter_value))),
        [parameter_value] = NULL
        WHERE [execution_id] = @operation_id 
        AND [sensitive] = 1 
        AND [parameter_value] IS NOT NULL
        AND [sensitive_parameter_value] IS NULL 
        AND ([parameter_data_type] = 'double' OR [parameter_data_type] = 'single' OR [parameter_data_type] = 'decimal')
        
        UPDATE [internal].[execution_parameter_values]
        SET [sensitive_parameter_value] = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(MAX),[parameter_value])),
        [parameter_value] = NULL
        WHERE [execution_id] = @operation_id 
        AND [sensitive] = 1 
        AND [parameter_value] IS NOT NULL
        AND [sensitive_parameter_value] IS NULL 
        AND [parameter_data_type] NOT IN ('datetime', 'double', 'single', 'decimal')  
        
              
        
        INSERT INTO [internal].[execution_parameter_values]
        (  
            [execution_id], 
            [object_type], 
            [parameter_data_type], 
            [parameter_name], 
            [parameter_value],
            [sensitive_parameter_value],
            [base_data_type], 
            [sensitive], 
            [required], 
            [value_set], 
            [runtime_override]
        )
       SELECT  @execution_id,
               objParams.[object_type], 
               objParams.[parameter_data_type], 
               objParams.[parameter_name],
               NULL, 
               NULL,
               NULL, 
               objParams.[sensitive], 
               objParams.[required], 
               0, 
               0
        FROM 
        (SELECT [object_type], 
                [parameter_data_type], 
                [parameter_name],
                [sensitive], 
                [required]
        FROM [internal].[object_parameters]
        WHERE [project_id] = @project_id 
        AND [object_type] = 20
        AND [value_type] = 'R' 
        AND [project_version_lsn] = @version_id) objParams
        LEFT JOIN
        (SELECT [object_type],[parameter_name]
         FROM [internal].[execution_parameter_values] 
         WHERE [execution_id] = @operation_id) exeParams
         ON objParams.[object_type] = exeParams.[object_type]
         AND objParams.[parameter_name] = exeParams.[parameter_name] COLLATE SQL_Latin1_General_CP1_CS_AS
        WHERE exeParams.[parameter_name] IS NULL
        
            
        INSERT INTO [internal].[execution_parameter_values]
        (  
            [execution_id], 
            [object_type], 
            [parameter_data_type], 
            [parameter_name], 
            [parameter_value],
            [sensitive_parameter_value],
            [base_data_type], 
            [sensitive], 
            [required], 
            [value_set], 
            [runtime_override]
        )
        SELECT  @execution_id,
               objParams.[object_type], 
               objParams.[parameter_data_type], 
               objParams.[parameter_name],
               NULL, 
               NULL,
               NULL, 
               objParams.[sensitive], 
               objParams.[required], 
               0, 
               0
        FROM 
        (SELECT [object_type], 
                [parameter_data_type], 
                [parameter_name],
                [sensitive], 
                [required]
        FROM [internal].[object_parameters]
        WHERE [project_id] = @project_id 
        AND [object_type] = 30
        AND [value_type] = 'R' 
        AND [project_version_lsn] = @version_id
        AND [object_name] = @package_name) objParams
        LEFT JOIN
        (SELECT [object_type],[parameter_name]
         FROM [internal].[execution_parameter_values] 
         WHERE [execution_id] = @operation_id) exeParams
         ON objParams.[object_type] = exeParams.[object_type]
         AND objParams.[parameter_name] = exeParams.[parameter_name] COLLATE SQL_Latin1_General_CP1_CS_AS
        WHERE exeParams.[parameter_name] IS NULL;
        
        
        
        WITH UnsetParameters AS
        (
            SELECT [execution_id],[object_type],[parameter_name],[parameter_value],[value_set]
            FROM [internal].[execution_parameter_values] 
            WHERE [sensitive] = 0 AND [required] = 0 AND [value_set] = 0
            AND [execution_id] = @operation_id
        )
        UPDATE exeparams
            SET [parameter_value] = objparams.[design_default_value]
            FROM UnsetParameters AS exeparams INNER JOIN [internal].[object_parameters] objparams
            ON exeparams.[parameter_name] = objparams.[parameter_name] COLLATE SQL_Latin1_General_CP1_CS_AS
            AND exeparams.[object_type] = objparams.[object_type] 
            AND exeparams.[value_set] = objparams.[value_set]
            WHERE 
            (objparams.[object_type] = 20 OR 
            (objparams.[object_type] = 30 AND objparams.[object_name] = @package_name))
            AND objparams.[project_id] = @project_id 
            AND objparams.[project_version_lsn] = @version_id;

        
               
        
        SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString
        
        SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @project_key_name
            EXECUTE sp_executesql @sqlString       
        
    
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        UPDATE [internal].[operations] SET 
            [end_time]  = SYSDATETIMEOFFSET(),
            [status]    = 4
            WHERE operation_id    = @operation_id;
        THROW;
    END CATCH
    
    RETURN 0    
GO
IF (OBJECT_ID('[catalog].[set_execution_parameter_value]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[set_execution_parameter_value]
END
GO

CREATE PROCEDURE [catalog].[set_execution_parameter_value]
        @execution_id       bigint,   
        @object_type        smallint, 
        @parameter_name     nvarchar(128), 
        @parameter_value    sql_variant 
WITH EXECUTE AS 'AllSchemaOwner'
AS 
    SET NOCOUNT ON
    
    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
    END
    
    DECLARE @result int
    DECLARE @execution_parameter_id bigint
    DECLARE @sensitive bit
    DECLARE @data_type  nvarchar(128)
    DECLARE @value varbinary(MAX)
    DECLARE @parameter_type nvarchar(128)
    DECLARE @return_value           bit = 1
    
    IF (@execution_id IS NULL OR @object_type IS NULL 
        OR @parameter_name IS NULL OR @parameter_value IS NULL)
    BEGIN
        RAISERROR(27138, 16 , 1) WITH NOWAIT 
        RETURN 1 
    END   
    
    IF (@object_type NOT IN(20, 30, 50))
    BEGIN
        RAISERROR(27101, 16 , 1, N'object type') WITH NOWAIT
        RETURN 1;
    END

    IF @execution_id <= 0
    BEGIN
        RAISERROR(27101, 16 , 1, N'execution_id') WITH NOWAIT
        RETURN 1;
    END
    
    SET @parameter_type = CONVERT(nvarchar(128), SQL_VARIANT_PROPERTY(@parameter_value, 'BaseType'));
    
    DECLARE @sqlString              nvarchar(1024)
    DECLARE @key_name               [internal].[adt_name]
    DECLARE @certificate_name       [internal].[adt_name]
    DECLARE @encryption_algorithm   nvarchar(255)    

    
    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY 
        EXECUTE AS CALLER   
            SET @result = [internal].[check_permission] 
                (
                    4,
                    @execution_id,
                    2
                ) 
        REVERT
        
        IF @result = 0
        BEGIN
            RAISERROR(27103 , 16 , 1, @execution_id) WITH NOWAIT        
        END  
        
        DECLARE @project_id bigint
        DECLARE @status int
        EXECUTE AS CALLER
            SELECT @project_id = [object_id], @status = [status]
            FROM [catalog].[operations]
            WHERE [operation_id] = @execution_id 
                  AND [object_type] = 20
                  AND [operation_type] = 200
        REVERT
        
        IF (@project_id IS NULL)
        BEGIN
            RAISERROR(27103 , 16 , 1, @execution_id) WITH NOWAIT
        END
       
        IF  @status <> 1
        BEGIN
            RAISERROR(27224 , 16 , 1) WITH NOWAIT
        END
       
       
        SELECT @execution_parameter_id = [execution_parameter_id],
               @sensitive = [sensitive],
               @data_type = [parameter_data_type]
        FROM   [internal].[execution_parameter_values]
        WHERE  [execution_id] = @execution_id AND [object_type] = @object_type 
               AND [parameter_name] = @parameter_name COLLATE SQL_Latin1_General_CP1_CS_AS
       
        IF @execution_parameter_id IS NULL 
        BEGIN
            RAISERROR(27176 , 16 , 1, @parameter_name) WITH NOWAIT        
        END  
        
        ELSE IF @sensitive IS NULL OR @data_type IS NULL
        BEGIN
            RAISERROR(27205, 16 , 1) WITH NOWAIT        
        END          
        
        IF NOT EXISTS (SELECT [ssis_data_type] FROM [internal].[data_type_mapping]
              WHERE [ssis_data_type] = @data_type)
        BEGIN
            RAISERROR(27159, 16 , 1) WITH NOWAIT        
        END        
        
        EXEC @return_value = [internal].[check_data_type_value] 
            @parameter_value, @data_type
     
        IF (@return_value <> 0)         
        
        BEGIN
            RAISERROR(27147, 16 , 1, @data_type) WITH NOWAIT
        END 

        EXEC @return_value = [internal].[check_parameter_value_by_name] 
            @parameter_value, @parameter_name
     
        IF (@return_value <> 0)         
        
        BEGIN
            RETURN 1
        END 
       
        DECLARE @server_operation_encryption_level int
        SELECT @server_operation_encryption_level = CONVERT(int,property_value)  
                FROM [catalog].[catalog_properties]
                WHERE property_name = 'SERVER_OPERATION_ENCRYPTION_LEVEL'

        IF @server_operation_encryption_level NOT in (1, 2)       
        BEGIN
            RAISERROR(27163    ,16,1,'SERVER_OPERATION_ENCRYPTION_LEVEL') WITH NOWAIT
        END
       
        IF @sensitive = 1
        BEGIN
            IF @server_operation_encryption_level = 1
            BEGIN
            SET @key_name = 'MS_Enckey_Exec_'+CONVERT(varchar,@execution_id)
            SET @certificate_name = 'MS_Cert_Exec_'+CONVERT(varchar,@execution_id) 
            END
            ELSE BEGIN
                SET @key_name = 'MS_Enckey_Proj_Param_'+CONVERT(varchar,@project_id)
                SET @certificate_name = 'MS_Cert_Proj_Param_'+CONVERT(varchar,@project_id) 
            END
     
            SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
                + ' DECRYPTION BY CERTIFICATE ' + @certificate_name  
            EXECUTE sp_executesql @sqlString
            
            IF @data_type = 'datetime'
            BEGIN
                SET @value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(4000),CONVERT(datetime2,@parameter_value)))
            END
            
            ELSE IF @data_type = 'single' OR @data_type = 'double' OR @data_type = 'decimal'
            BEGIN
                SET @value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(4000),CONVERT(decimal(38,18),@parameter_value)))
            END
                                 
            ELSE
            BEGIN
                SET @value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(MAX),@parameter_value))   
            END
            
            SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString  
            
            UPDATE [internal].[execution_parameter_values]
            SET [runtime_override] = 1,
                [value_set] = 1,
                [base_data_type] = @parameter_type,
                [sensitive_parameter_value] = @value
            WHERE [execution_parameter_id] = @execution_parameter_id           
            
        END
        
        ELSE
        BEGIN
            UPDATE [internal].[execution_parameter_values]
            SET [runtime_override] = 1,
                [value_set] = 1,
                [base_data_type] = @parameter_type,
                [parameter_value] = @parameter_value
            WHERE [execution_parameter_id] = @execution_parameter_id

            
            

            IF @parameter_name = 'LOGGING_LEVEL'
            BEGIN
                DECLARE @converted_value int
                SET @converted_value = CONVERT(int,@parameter_value)

                IF (@converted_value = 100)
                BEGIN
                    IF NOT EXISTS
                    (
                        SELECT 1 
                        FROM [internal].[execution_parameter_values]
                        WHERE [parameter_name] = 'CUSTOMIZED_LOGGING_LEVEL'
                              AND [execution_id] = @execution_id 
                    )
                    BEGIN
                        INSERT INTO [internal].[execution_parameter_values]
                        (
                            [execution_id],
                            [object_type],
                            [parameter_data_type],
                            [parameter_name],
                            [parameter_value],
                            [base_data_type],
                            [sensitive], 
                            [required],
                            [value_set],
                            [runtime_override]
                        )
                        VALUES 
                        (
                            @execution_id,
                            50,
                            'String',
                            'CUSTOMIZED_LOGGING_LEVEL',
                            CONVERT(sql_variant,''),
                            'sysname',
                            0,
                            0,
                            1,
                            0
                        )
                    END
                END
                ELSE
                BEGIN
                    DELETE FROM [internal].[execution_parameter_values]
                    WHERE [parameter_name] = 'CUSTOMIZED_LOGGING_LEVEL'
                          AND [execution_id] = @execution_id
                END
            END
        END

        
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        THROW;
    END CATCH
     
    RETURN 0
GO
IF (OBJECT_ID('[internal].[check_parameter_value_by_name]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [internal].[check_parameter_value_by_name]
END
GO

CREATE PROCEDURE [internal].[check_parameter_value_by_name]
    @value      sql_variant,   
    @parameter_name     sysname 
AS
BEGIN
    IF @parameter_name = 'LOGGING_LEVEL'
    BEGIN
        DECLARE @converted_value int
        SET @converted_value = CONVERT(int,@value)
        IF ((@converted_value < 0 OR @converted_value > 4)
            AND @converted_value <> 100)
        BEGIN
            RAISERROR(27217, 16 , 1, @converted_value) WITH NOWAIT
            RETURN 1 
        END
    END
    
    IF @parameter_name = 'CUSTOMIZED_LOGGING_LEVEL'
    BEGIN
        DECLARE @level_name NVARCHAR(128)
        SET @level_name = CONVERT(NVARCHAR(128),@value)
        IF NOT EXISTS 
        (
            SELECT [name]
            FROM [internal].[customized_logging_levels]
            WHERE [name] = @level_name
        )
        BEGIN
            RAISERROR(27237, 16, 1, @level_name) WITH NOWAIT
            RETURN 1
        END
    END

    RETURN 0
END

GO
IF (OBJECT_ID('[catalog].[set_execution_property_override_value]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[set_execution_property_override_value]
END
GO

CREATE PROCEDURE [catalog].[set_execution_property_override_value]
        @execution_id       bigint,   
        @property_path      nvarchar(4000), 
        @property_value     nvarchar(max),  
	    @sensitive			bit

WITH EXECUTE AS 'AllSchemaOwner'
AS 
    SET NOCOUNT ON
    
    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
    END
    
    DECLARE @result int
    DECLARE @id bigint
    DECLARE @sensitive_value varbinary(MAX)
    DECLARE @calculated_property_value nvarchar(MAX)
    DECLARE @return_value           bit = 1
    
    IF (@execution_id IS NULL OR @property_path IS NULL OR @property_value IS NULL)
    BEGIN
        RAISERROR(27138, 16 , 1) WITH NOWAIT 
        RETURN 1 
    END   
    
    IF @execution_id <= 0
    BEGIN
        RAISERROR(27101, 16 , 1, N'execution_id') WITH NOWAIT
        RETURN 1;
    END
    
    DECLARE @sqlString              nvarchar(1024)
    DECLARE @key_name               [internal].[adt_name]
    DECLARE @certificate_name       [internal].[adt_name]
    DECLARE @encryption_algorithm   nvarchar(255)    
    DECLARE @server_operation_encryption_level int

    
    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY 
        EXECUTE AS CALLER   
            SET @result = [internal].[check_permission] 
                (
                    4,
                    @execution_id,
                    2
                ) 
        REVERT
        
        IF @result = 0
        BEGIN
            RAISERROR(27103 , 16 , 1, @execution_id) WITH NOWAIT        
        END  
        
        DECLARE @project_id bigint
        DECLARE @status int
        EXECUTE AS CALLER
            SELECT @project_id = [object_id], @status = [status]
            FROM [catalog].[operations]
            WHERE [operation_id] = @execution_id 
                  AND [object_type] = 20
                  AND [operation_type] = 200
        REVERT
        
        IF (@project_id IS NULL)
        BEGIN
            RAISERROR(27103 , 16 , 1, @execution_id) WITH NOWAIT
        END
       
        IF  @status <> 1
        BEGIN
            RAISERROR(27225 , 16 , 1) WITH NOWAIT
        END
       
        SELECT @server_operation_encryption_level = CONVERT(int,property_value)  
                FROM [catalog].[catalog_properties]
                WHERE property_name = 'SERVER_OPERATION_ENCRYPTION_LEVEL'

        IF @server_operation_encryption_level NOT in (1, 2)        
        BEGIN
            RAISERROR(27163    ,16,1,'SERVER_OPERATION_ENCRYPTION_LEVEL')
        END
       
        IF @sensitive = 1
        BEGIN
            IF @server_operation_encryption_level = 1
            BEGIN
            SET @key_name = 'MS_Enckey_Exec_'+CONVERT(varchar,@execution_id)
            SET @certificate_name = 'MS_Cert_Exec_'+CONVERT(varchar,@execution_id) 
            END
            ELSE BEGIN
                SET @key_name = 'MS_Enckey_Proj_Param_'+CONVERT(varchar,@project_id)
                SET @certificate_name = 'MS_Cert_Proj_Param_'+CONVERT(varchar,@project_id)
            END
     
            SET @sqlString = 'OPEN SYMMETRIC KEY ' + @key_name 
                + ' DECRYPTION BY CERTIFICATE ' + @certificate_name  
            EXECUTE sp_executesql @sqlString
            
            SET @sensitive_value = EncryptByKey(KEY_GUID(@key_name),CONVERT(varbinary(MAX),@property_value))
			SET @calculated_property_value = NULL
            
            SET @sqlString = 'CLOSE SYMMETRIC KEY '+ @key_name
            EXECUTE sp_executesql @sqlString  
		END

		ELSE

		BEGIN
            SET @sensitive_value = NULL
			SET @calculated_property_value = @property_value
		END
            
		IF EXISTS 
		(
			SELECT 1
			FROM [internal].[execution_property_override_values]
			WHERE execution_id = @execution_id
			AND property_path = @property_path
		)
		BEGIN
			UPDATE [internal].[execution_property_override_values]
			SET
				property_value = @calculated_property_value,
				sensitive_property_value = @sensitive_value,
				sensitive = @sensitive
		END

		ELSE

		BEGIN
			INSERT INTO [internal].[execution_property_override_values]
			(
				execution_id,
				property_path,
				sensitive,
				property_value,
				sensitive_property_value
			)
			VALUES
			(
				@execution_id,
				@property_path,
				@sensitive,
				@calculated_property_value,
				@sensitive_value
			)
        END
               
        
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
    END TRY
    
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        THROW;
    END CATCH
     
    RETURN 0
GO
IF (OBJECT_ID('[catalog].[add_execution_worker]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[add_execution_worker]
END
GO

CREATE PROCEDURE [catalog].[add_execution_worker]
        @execution_id       bigint,   
		@workeragent_id		uniqueIdentifier 
		
WITH EXECUTE AS 'AllSchemaOwner'
AS 
    SET NOCOUNT ON
	
	
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
    END
	
	DECLARE @result int
	EXECUTE AS CALLER   
		SET @result = [internal].[check_permission] 
			(
				4,
				@execution_id,
				2
			) 
	REVERT
	
	IF @result = 0
	BEGIN
		RAISERROR(27103 , 16 , 1, @execution_id) WITH NOWAIT    
		RETURN 1
	END  
    
    IF (@execution_id IS NULL)
    BEGIN
        RAISERROR(27138, 16 , 1) WITH NOWAIT 
        RETURN 1 
    END   

    IF @execution_id <= 0
    BEGIN
        RAISERROR(27101, 16 , 1, N'execution_id') WITH NOWAIT
        RETURN 1
    END
	
	DECLARE @return_value 		int
	DECLARE @job_id 			uniqueIdentifier = NULL
	
	SELECT @job_id = [job_id] FROM [internal].[executions] WHERE [execution_id]=@execution_id
	
	
	IF @@ROWCOUNT = 0
	BEGIN
		RAISERROR(27103, 16, 1, @execution_id) WITH NOWAIT
		RETURN 1
	END
	ELSE IF @job_id IS NULL
	BEGIN
		RAISERROR(27255, 16, 1) WITH NOWAIT
		RETURN 1
	END	
      
    EXEC @return_value = [internal].[append_job_worker_agent] 
		@job_id,
        @workeragent_id
 
    IF (@return_value <> 0)         
    
    BEGIN
        RETURN 1               
    END     

	RETURN 0
GO
IF (OBJECT_ID('[catalog].[start_execution]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[start_execution]
END
GO

CREATE PROCEDURE [catalog].[start_execution]
        @execution_id       	bigint,   
		@retry_count			int = 0	 
AS 
    SET NOCOUNT ON
	
	 


    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
    END
    
    DECLARE @return_value int
    
    IF (@execution_id IS NULL)
    BEGIN
        RAISERROR(27138, 16 , 1) WITH NOWAIT 
        RETURN 1 
    END   

    IF @execution_id <= 0
    BEGIN
        RAISERROR(27101, 16 , 1, N'execution_id') WITH NOWAIT
        RETURN 1;
    END
	
	IF @retry_count < 0
	BEGIN
		RAISERROR(27101, 16, 1, N'retry_count') WITH NOWAIT
		RETURN 1
	END		
    
    DECLARE @project_id bigint
    DECLARE @version_id bigint
    DECLARE @use32bitruntime bit
      
    EXEC @return_value = [internal].[prepare_execution] 
        @execution_id,
        @project_id output,
        @version_id output,
        @use32bitruntime output
 
    IF (@return_value <> 0)         
    
    BEGIN
        RETURN 1               
    END  
    
	
	IF (0 = 0 AND EXISTS (SELECT 1 FROM [internal].[executions] WHERE [execution_id]=@execution_id AND [job_id] IS NULL))
	BEGIN
		BEGIN TRY        
            UPDATE [internal].[operations] 
            SET [executed_count] = 1
            WHERE operation_id = @execution_id;

			EXEC @return_value = 
						[internal].[start_execution_internal] 
								@project_id,
								@execution_id,
								@version_id, 
								@use32bitruntime 
		END TRY
		
		BEGIN CATCH           
			UPDATE [internal].[operations] SET 
				[end_time]  = SYSDATETIMEOFFSET(),
				[status]    = 4
				WHERE operation_id    = @execution_id;
			THROW;
		END CATCH
    END
	
	
	ELSE
	BEGIN
		BEGIN TRY
			EXEC @return_value = 
					[internal].[create_execution_job]
						@execution_id,
						@caller_name,
						@retry_count
		END TRY
		
		BEGIN CATCH           
			UPDATE [internal].[operations] SET 
				[end_time]  = SYSDATETIMEOFFSET(),
				[status]    = 4
				WHERE operation_id    = @execution_id;
			THROW;
		END CATCH

		IF EXISTS(SELECT * FROM [internal].[execution_parameter_values] WHERE execution_id = @execution_id AND parameter_name = 'SYNCHRONIZED' AND parameter_value = 1 AND object_type = 50)
		BEGIN
			DECLARE @status int = NULL
			WHILE @status IS NULL
			BEGIN        WAITFOR DELAY '00:00:02'  
				
				SELECT @status = [status] FROM [catalog].[operations] 
				WHERE operation_id = @execution_id AND [status] <> 5 AND [status] <> 2
			END
		END
	END
	
    
    IF (@return_value <> 0) 
    BEGIN
        UPDATE [internal].[operations] 
           SET [status] = 4,
           [end_time]  = SYSDATETIMEOFFSET()
           WHERE [operation_id] = @execution_id 
        RETURN 1               
    END

    RETURN (@return_value)      
GO
IF (OBJECT_ID('[catalog].[stop_operation]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[stop_operation]
END
GO

CREATE PROCEDURE [catalog].[stop_operation]
    @operation_id bigint                                
AS
BEGIN
    SET NOCOUNT ON
    
    DECLARE @operation_guid uniqueIdentifier   
    DECLARE @process_id bigint
    DECLARE @return_value int
    DECLARE @stop_id bigint
    DECLARE @status int
    
    IF @operation_id IS NULL 
    BEGIN
       RAISERROR(27100, 16 , 11, 'operation_id') WITH NOWAIT
       RETURN 1
    END
    
    EXEC @return_value = [internal].[prepare_stop] 
                            @operation_id,
                            @process_id output, 
                            @operation_guid output,
                            @stop_id output

	DECLARE @job_id uniqueIdentifier = NULL
	
    IF @return_value = 0
    BEGIN
		DECLARE @operation_type int
		SELECT @operation_type = [operation_type] FROM [catalog].[operations] WHERE [operation_id] = @operation_id
		IF @operation_type = 200
		BEGIN
		SELECT @job_id = [job_id] FROM [internal].[executions] WHERE [execution_id]=@operation_id
		END
		ELSE IF (@operation_type = 300 OR @operation_type = 301)
		BEGIN
		SELECT @job_id = [job_id] FROM [internal].[validations] WHERE [validation_id]=@operation_id
		END
        BEGIN TRY
			
			IF (0 = 0 AND @job_id IS NULL)
			BEGIN
            EXEC @return_value=[internal].[stop_operation_internal] 
                    @operation_id, 
                    @process_id,
                    @operation_guid
				SET @status =
				CASE
					WHEN (@return_value = 0) THEN 7
					ELSE 4
				END

				UPDATE [internal].[operations] SET 
					[end_time]  = SYSDATETIMEOFFSET(),
					[status]    = @status
					WHERE operation_id    = @stop_id
			END
			
			ELSE
			BEGIN
				EXEC @return_value=[internal].[cancel_job] @job_id
				IF (@return_value = 0) 
				BEGIN
					DECLARE @input_data nvarchar(max)
					SET @input_data = (SELECT [InputData] FROM [internal].[tasks] WHERE [JobId] = @job_id)
					SET @input_data = JSON_MODIFY(@input_data, 'append $', 
						JSON_QUERY((SELECT 'stop_id' AS name, CONVERT(nvarchar(256), @stop_id) AS value For JSON PATH, WITHOUT_ARRAY_WRAPPER)))			
					UPDATE [internal].[tasks] SET [InputData] =  @input_data WHERE [JobId] = @job_id
				END
			END
        END TRY
        BEGIN CATCH         
            UPDATE [internal].[operations] SET 
                [end_time]  = SYSDATETIMEOFFSET(),
                [status]    = 4
                WHERE operation_id    = @stop_id;
            THROW;
        END CATCH
    END
    
    RETURN @return_value       
END
GO
IF (OBJECT_ID('[catalog].[configure_catalog]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[configure_catalog]
END
GO

CREATE PROCEDURE [catalog].[configure_catalog] 
        @property_name           nvarchar(255), 
        @property_value          nvarchar(255)
AS
BEGIN
    SET NOCOUNT ON
    
    
    
    DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
          
          
        IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
        BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
        END
    REVERT
    
    IF(
            EXISTS(SELECT [name]
                    FROM sys.server_principals
                    WHERE [sid] = @suser_sid AND [type] = 'S')  
            OR
            EXISTS(SELECT [name]
                    FROM sys.database_principals
                    WHERE ([sid] = @caller_sid AND [type] = 'S')) 
            )
    BEGIN
            RAISERROR(27123, 16, 1) WITH NOWAIT
            RETURN 1
    END

    EXECUTE AS CALLER
        IF ((IS_MEMBER('ssis_admin') <> 1) AND (IS_SRVROLEMEMBER('sysadmin') <> 1))
            BEGIN
               RAISERROR(27140, 16, 1, N'ssis_admin', N'sysadmin') WITH NOWAIT
               RETURN 1 
            END
    REVERT
    
    IF @property_name IS NULL OR @property_value IS NULL
    BEGIN
        RAISERROR(27138, 16 , 6) WITH NOWAIT 
        RETURN 1 
    END
    
    IF @property_name NOT IN ('ENCRYPTION_ALGORITHM', 'RETENTION_WINDOW', 
            'MAX_PROJECT_VERSIONS', 'VERSION_CLEANUP_ENABLED', 'OPERATION_CLEANUP_ENABLED', 'SERVER_LOGGING_LEVEL','SERVER_CUSTOMIZED_LOGGING_LEVEL','SERVER_OPERATION_ENCRYPTION_LEVEL', 'DEFAULT_EXECUTION_MODE')
    BEGIN
        RAISERROR(27101, 16 , 1, N'property_name') WITH NOWAIT
        RETURN 1
    END
    
    DECLARE @operation_id       bigint
    DECLARE @return_value       int
    DECLARE @status             int
    DECLARE @result             bit
    DECLARE @ret                int
    DECLARE @old_algorithm_name nvarchar(255)
    
    SET @result = 1
    
    INSERT INTO [internal].[operations] (
        [operation_type],
        [created_time],
        [object_type],
        [object_id],
        [object_name] ,
        [status], 
        [start_time],
        [caller_sid], 
        [caller_name]
        )
    VALUES (
        1000,
        SYSDATETIMEOFFSET(),
        NULL,
        NULL,
        @property_name,
        2,
        SYSDATETIMEOFFSET(), 
        @caller_sid,            
        @caller_name  
        )
        
    IF @@ROWCOUNT <> 1
    BEGIN
      RETURN 1;
    END

    SET @operation_id = SCOPE_IDENTITY();
    
    EXECUTE AS CALLER
        EXEC @return_value = [internal].[init_object_permissions] 
                    4, @operation_id, @caller_id 
    REVERT            
    IF @return_value <> 0
    BEGIN
        
        RAISERROR(27153, 16, 1) WITH NOWAIT
        RETURN 1
    END   
    
    
    IF (UPPER(@property_name) = 'ENCRYPTION_ALGORITHM')
    BEGIN
                
        
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                                  
        BEGIN TRY

            IF NOT EXISTS (SELECT [user_access] FROM sys.databases 
                WHERE name = 'SSISDB' and [user_access] = 1 )
            BEGIN
                RAISERROR(27162, 16 , 1, N'ENCRYPTION_ALGORITHM' ) WITH NOWAIT
            END
            
            SELECT @old_algorithm_name = property_value
                FROM [internal].[catalog_properties] 
                WHERE property_name = 'ENCRYPTION_ALGORITHM'
            
            SELECT @ret = [internal].[validate_encryption_algorithm](@property_value)
            IF @ret <> 0
            BEGIN
               RAISERROR(27101, 16, 12, N'property_value') WITH NOWAIT
            END
            
            
            
            EXEC @ret = [internal].[configure_environment_encryption_algorithm] @property_value, @operation_id
            IF @ret <> 0
            BEGIN
                RAISERROR (27167, 16, 1, @property_value,@property_value ) WITH NOWAIT
            END
            
            EXEC @ret = [internal].[configure_execution_encryption_algorithm] @property_value, @operation_id
            IF @ret <> 0
            BEGIN
                RAISERROR (27168,16,1, @property_value,@property_value) WITH NOWAIT
            END           
            
            EXEC @ret = [internal].[configure_project_encryption_algorithm] @property_value,@old_algorithm_name, @operation_id
            IF @ret <> 0
            BEGIN
                RAISERROR (27168,16,1, @property_value,@property_value) WITH NOWAIT
            END 
            
            
            UPDATE [internal].[catalog_properties] 
                SET property_value = @property_value
                WHERE property_name = 'ENCRYPTION_ALGORITHM'                    
            
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                    
        END TRY
        BEGIN CATCH 
            
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                            
            UPDATE [internal].[operations] SET 
                [end_time]  = SYSDATETIMEOFFSET(),
                [status]    = 4
                WHERE operation_id    = @operation_id;    
            THROW;
        END CATCH      
    END
    
    ELSE IF (UPPER(@property_name) = 'RETENTION_WINDOW')
        BEGIN
            
            BEGIN TRY
                DECLARE @retention_window INT
                SET @retention_window = CONVERT(INT, @property_value)
                
                IF @retention_window <= 0 OR @retention_window >3650
                BEGIN
                    RAISERROR(27101, 16, 14, N'property_value') WITH NOWAIT
                END
                    
                UPDATE [internal].[catalog_properties] 
                    SET property_value = @property_value
                    WHERE property_name = 'RETENTION_WINDOW'

                IF @@ROWCOUNT <> 1
                BEGIN
                    RAISERROR(27112, 16, 8, N'isserver_property') WITH NOWAIT;
                END                             
            END TRY
            BEGIN CATCH
                UPDATE [internal].[operations] SET 
                    [end_time]  = SYSDATETIMEOFFSET(),
                    [status]    = 4
                    WHERE operation_id    = @operation_id;  
                THROW;
            END CATCH
        END
    ELSE IF (UPPER(@property_name) = 'MAX_PROJECT_VERSIONS')
        BEGIN
            
            BEGIN TRY
                DECLARE @version_count INT
                SET @version_count = CONVERT(INT, @property_value)

                IF @version_count <= 0 OR @version_count > 9999
                BEGIN
                    RAISERROR(27101, 16, 16, N'property_value') WITH NOWAIT
                END
                
                UPDATE [internal].[catalog_properties] 
                    SET property_value = @property_value
                    WHERE property_name = 'MAX_PROJECT_VERSIONS'

                IF @@ROWCOUNT <> 1
                BEGIN
                    RAISERROR(27112, 16, 9, N'isserver_property') WITH NOWAIT;
                END       
            END TRY
            
            BEGIN CATCH
                UPDATE [internal].[operations] SET 
                    [end_time]  = SYSDATETIMEOFFSET(),
                    [status]    = 4
                    WHERE operation_id    = @operation_id;
                    THROW;
            END CATCH
        END
           
        ELSE IF (UPPER(@property_name) = 'OPERATION_CLEANUP_ENABLED')
        BEGIN
            
            BEGIN TRY

                IF @property_value NOT IN ('TRUE', 'FALSE')
                BEGIN
                    RAISERROR(27101, 16, 16, N'property_value') WITH NOWAIT
                END
                
                UPDATE [internal].[catalog_properties] 
                    SET property_value = @property_value
                    WHERE property_name = 'OPERATION_CLEANUP_ENABLED'

                IF @@ROWCOUNT <> 1
                BEGIN
                    RAISERROR(27112, 16, 9, N'isserver_property') WITH NOWAIT;
                END       
            END TRY
            
            BEGIN CATCH
                UPDATE [internal].[operations] SET 
                    [end_time]  = SYSDATETIMEOFFSET(),
                    [status]    = 4
                    WHERE operation_id    = @operation_id;
                    THROW;
            END CATCH
        END 

        ELSE IF (UPPER(@property_name) = 'VERSION_CLEANUP_ENABLED')
        BEGIN
            
            BEGIN TRY
            
                IF @property_value NOT IN ('TRUE', 'FALSE')
                BEGIN
                    RAISERROR(27101, 16, 16, N'property_value') WITH NOWAIT
                END
                
                UPDATE [internal].[catalog_properties] 
                    SET property_value = @property_value
                    WHERE property_name = 'VERSION_CLEANUP_ENABLED'

                IF @@ROWCOUNT <> 1
                BEGIN
                    RAISERROR(27112, 16, 9, N'isserver_property') WITH NOWAIT;
                END       
            END TRY
            
            BEGIN CATCH
                UPDATE [internal].[operations] SET 
                    [end_time]  = SYSDATETIMEOFFSET(),
                    [status]    = 4
                    WHERE operation_id    = @operation_id;
                    THROW;
            END CATCH
        END 

        ELSE IF (UPPER(@property_name) = 'SERVER_LOGGING_LEVEL')
        BEGIN
            
            BEGIN TRY
                DECLARE @server_logging_level INT
				DECLARE @server_custom_logging_level_name NVARCHAR(128)
                SET @server_logging_level = CONVERT(INT, @property_value)
                IF ((@server_logging_level < 0 OR @server_logging_level > 4)
					AND @server_logging_level <> 100)
                BEGIN
                    RAISERROR(27217, 16 , 1, @server_logging_level) WITH NOWAIT
                END
				
				
				IF (@server_logging_level = 100)
				BEGIN
				   SELECT @server_custom_logging_level_name = property_value
                   FROM [internal].[catalog_properties] 
                   WHERE property_name = 'SERVER_CUSTOMIZED_LOGGING_LEVEL'
				   
				   IF (@server_custom_logging_level_name is null or @server_custom_logging_level_name = '') 
				   BEGIN
				      RAISERROR(27241, 16 , 1, @server_logging_level) WITH NOWAIT
				   END
				END
                    
                UPDATE [internal].[catalog_properties] 
                    SET property_value = @property_value
                    WHERE property_name = 'SERVER_LOGGING_LEVEL'

                IF @@ROWCOUNT <> 1
                BEGIN
                    RAISERROR(27112, 16, 8, N'isserver_property') WITH NOWAIT;
                END                             
            END TRY
            BEGIN CATCH
                UPDATE [internal].[operations] SET 
                    [end_time]  = SYSDATETIMEOFFSET(),
                    [status]    = 4
                    WHERE operation_id    = @operation_id;  
                THROW;
            END CATCH
        END  

		ELSE IF (UPPER(@property_name) = 'SERVER_CUSTOMIZED_LOGGING_LEVEL')
		BEGIN
			
			BEGIN TRY
				DECLARE @level_name NVARCHAR(128)
				SET @level_name = CONVERT(NVARCHAR(128),@property_value)
				IF NOT EXISTS 
				(
					SELECT [name]
					FROM [internal].[customized_logging_levels]
					WHERE [name] = @level_name
				) AND
				@level_name <> ''
				BEGIN
					RAISERROR(27237, 16, 1, @level_name) WITH NOWAIT
				END
							
				
				
				IF EXISTS 
				(
					SELECT [name]
					FROM [internal].[customized_logging_levels]
					WHERE [name] = @level_name
				)
				BEGIN
				   UPDATE [internal].[catalog_properties] 
                      SET property_value = 100
                      WHERE property_name = 'SERVER_LOGGING_LEVEL'
                END

				UPDATE [internal].[catalog_properties] 
                    SET property_value = @property_value
                    WHERE property_name = 'SERVER_CUSTOMIZED_LOGGING_LEVEL'

                IF @@ROWCOUNT <> 1
                BEGIN
                    RAISERROR(27112, 16, 8, N'isserver_property') WITH NOWAIT;
                END                             
            END TRY
            BEGIN CATCH
                UPDATE [internal].[operations] SET 
                    [end_time]  = SYSDATETIMEOFFSET(),
                    [status]    = 4
                    WHERE operation_id    = @operation_id;  
                THROW;
            END CATCH

        END

        ELSE IF (UPPER(@property_name) = 'SERVER_OPERATION_ENCRYPTION_LEVEL')
        BEGIN
            SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
            SET @tran_count = @@TRANCOUNT;
                    
            IF @tran_count > 0
            BEGIN
                SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
                SAVE TRANSACTION @savepoint_name;
            END
            ELSE
                BEGIN TRANSACTION;
            BEGIN TRY
                DECLARE @server_operation_encryption_level int
                DECLARE @curr_level int

                
                IF EXISTS (SELECT operation_id FROM [internal].[operations]
                        WHERE [status] IN (2, 5)
                        AND   [operation_id] <> @operation_id )
                BEGIN    
                    RAISERROR(27139, 16, 1) WITH NOWAIT
                    RETURN 1
                END

                IF NOT EXISTS (SELECT [user_access] FROM sys.databases 
                    WHERE name = 'SSISDB' and [user_access] = 1 )
                BEGIN
                    RAISERROR(27162, 16 , 1, N'SERVER_OPERATION_ENCRYPTION_LEVEL' ) WITH NOWAIT
                END

                SET @server_operation_encryption_level = CONVERT(int, @property_value)
                IF @server_operation_encryption_level NOT in (1, 2)      
                BEGIN
                    RAISERROR(27101,16,1,'propery_value')
                END

                SELECT @curr_level = CONVERT(int, property_value)
                    FROM [internal].[catalog_properties] WITH (UPDLOCK)
                    WHERE property_name = 'SERVER_OPERATION_ENCRYPTION_LEVEL'

                IF @curr_level NOT in (1, 2)         
                BEGIN
                    RAISERROR(27163    ,16,1,'SERVER_OPERATION_ENCRYPTION_LEVEL')
                END
		  
                IF (@server_operation_encryption_level <> @curr_level) 
                BEGIN
                    IF (EXISTS(SELECT [operation_id]
                        FROM [internal].[operations]
                        WHERE operation_type = 200))
                    BEGIN
                        RAISERROR(27141, 16, 1) WITH NOWAIT;
                    END

                    UPDATE [internal].[catalog_properties] 
                        SET property_value = @property_value
                        WHERE property_name = 'SERVER_OPERATION_ENCRYPTION_LEVEL'

                    IF @@ROWCOUNT <> 1
                    BEGIN
                        RAISERROR(27112, 16, 8, N'isserver_property') WITH NOWAIT;
                    END
                END
                
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
            END TRY
            BEGIN CATCH
                 
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
                UPDATE [internal].[operations] SET 
                    [end_time]  = SYSDATETIMEOFFSET(),
                    [status]    = 4
                    WHERE operation_id    = @operation_id;  
                THROW;
            END CATCH
        END  
        ELSE IF (UPPER(@property_name) = 'DEFAULT_EXECUTION_MODE')
        BEGIN
            
            BEGIN TRY

                IF @property_value NOT IN ('0', '1')
                BEGIN
                    RAISERROR(27101, 16, 16, N'property_value') WITH NOWAIT
                END
                
                UPDATE [internal].[catalog_properties] 
                    SET property_value = @property_value
                    WHERE property_name = 'DEFAULT_EXECUTION_MODE'

                IF @@ROWCOUNT <> 1
                BEGIN
                    RAISERROR(27112, 16, 9, N'default exection mode') WITH NOWAIT;
                END       
            END TRY
            
            BEGIN CATCH
                UPDATE [internal].[operations] SET 
                    [end_time]  = SYSDATETIMEOFFSET(),
                    [status]    = 4
                    WHERE operation_id    = @operation_id;
                    THROW;
            END CATCH
        END
    ELSE
        BEGIN
            
            RAISERROR(27101, 16, 13, N'property_name') WITH NOWAIT
            RETURN 1  
        END
    
    UPDATE [internal].[operations] SET 
        [end_time]  = SYSDATETIMEOFFSET(),
        [status]    = 7
        WHERE operation_id    = @operation_id 
    RETURN 0          
END
GO
IF (OBJECT_ID('[catalog].[create_customized_logging_level]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[create_customized_logging_level]
END
GO

CREATE PROCEDURE [catalog].[create_customized_logging_level]
    @level_name NVARCHAR(128),
    @level_description NVARCHAR(MAX) = NULL,
    @profile_value BIGINT = 0,
    @events_value BIGINT = 0,
    @level_id BIGINT = NULL OUTPUT
AS
BEGIN
    SET NOCOUNT ON
    
	DECLARE @caller_id     int
    DECLARE @caller_name   [internal].[adt_sname]
    DECLARE @caller_sid    [internal].[adt_sid]
    DECLARE @suser_name    [internal].[adt_sname]
    DECLARE @suser_sid     [internal].[adt_sid]
    
    EXECUTE AS CALLER
        EXEC [internal].[get_user_info]
            @caller_name OUTPUT,
            @caller_sid OUTPUT,
            @suser_name OUTPUT,
            @suser_sid OUTPUT,
            @caller_id OUTPUT;
    REVERT

    
    IF (IS_MEMBER('ssis_admin') = 0)
        AND (IS_SRVROLEMEMBER('sysadmin') = 0)
    BEGIN
        RAISERROR(27232, 16, 1, @level_name) WITH NOWAIT 
        RETURN 1
    END
    
    IF @level_name IS NULL
    BEGIN
        RAISERROR(27233, 16, 1) WITH NOWAIT
        RETURN 1
    END
    
    IF [internal].[is_valid_name](@level_name) = 0
    BEGIN
        RAISERROR(27234, 16, 1, @level_name) WITH NOWAIT
        RETURN 1
    END
    
    IF @profile_value < 0 OR @events_value < 0
    BEGIN
        RAISERROR(27240, 16, 1) WITH NOWAIT
        RETURN 1
    END

    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY
        IF EXISTS 
        (
            SELECT [name]
            FROM [internal].[customized_logging_levels]
            WHERE [name] = @level_name
        )
        BEGIN
            RAISERROR(27235, 16, 1, @level_name)
            RETURN 1
        END
    
        INSERT INTO [internal].[customized_logging_levels]
        (
            [name],
            [description],
            [profile_value],
            [events_value],
            [created_by_sid],
            [created_by_name],
            [created_time]
        )
        VALUES
        (
            @level_name,
            @level_description,
            @profile_value,
            @events_value,
            @caller_sid,
            @caller_name,
            SYSDATETIMEOFFSET()
        )
        
        SET @level_id = SCOPE_IDENTITY()
        
        
        
        
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
        RETURN 0
    END TRY
    
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        THROW
    END CATCH
END
GO
IF (OBJECT_ID('[catalog].[delete_customized_logging_level]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[delete_customized_logging_level]
END
GO

CREATE PROCEDURE [catalog].[delete_customized_logging_level]
    @level_name NVARCHAR(128)
AS
BEGIN
    SET NOCOUNT ON
    
    
    IF (IS_MEMBER('ssis_admin') = 0) 
        AND (IS_SRVROLEMEMBER('sysadmin') = 0)
    BEGIN
        RAISERROR(27236, 16, 1, @level_name) WITH NOWAIT
        RETURN 1
    END
    
    IF @level_name IS NULL
    BEGIN
        RAISERROR(27233, 16, 1) WITH NOWAIT
        RETURN 1
    END
    
    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY 
        IF NOT EXISTS 
        (
            SELECT [name]
            FROM [internal].[customized_logging_levels]
            WHERE [name] = @level_name
        )
        BEGIN
            RAISERROR(27237, 16, 1, @level_name) WITH NOWAIT
            RETURN 1
        END
    
        DELETE FROM [internal].[customized_logging_levels]
        WHERE [name] = @level_name
        
        
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
        RETURN 0
    END TRY
     
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        THROW
    END CATCH
END
GO
IF (OBJECT_ID('[catalog].[rename_customized_logging_level]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[rename_customized_logging_level]
END
GO

CREATE PROCEDURE [catalog].[rename_customized_logging_level]
    @old_name NVARCHAR(128),
    @new_name NVARCHAR(128)
AS
BEGIN
    SET NOCOUNT ON
    
    
    IF (IS_MEMBER('ssis_admin') = 0)
        AND (IS_SRVROLEMEMBER('sysadmin') = 0)
    BEGIN
        RAISERROR(27238, 16, 1, @old_name, @new_name) WITH NOWAIT
        RETURN 1
    END
    
    IF (@old_name IS NULL) OR (@new_name IS NULL)
    BEGIN
        RAISERROR(27233, 16, 1) WITH NOWAIT
        RETURN 1
    END
    
    IF [internal].[is_valid_name](@new_name) = 0
    BEGIN
        RAISERROR(27234, 16, 1, @new_name) WITH NOWAIT
        RETURN 1
    END
    
    
    IF @new_name = @old_name
    BEGIN
        RETURN 0
    END
    
    
    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
    
    
    
    DECLARE @tran_count INT = @@TRANCOUNT;
    DECLARE @savepoint_name NCHAR(32);
    IF @tran_count > 0
    BEGIN
        SET @savepoint_name = REPLACE(CONVERT(NCHAR(36), NEWID()), N'-', N'');
        SAVE TRANSACTION @savepoint_name;
    END
    ELSE
        BEGIN TRANSACTION;                                                                                      
    BEGIN TRY 
        IF NOT EXISTS 
        (
            SELECT [name]
            FROM [internal].[customized_logging_levels]
            WHERE [name] = @old_name
        )
        BEGIN
            RAISERROR(27237, 16, 1, @old_name) WITH NOWAIT
            RETURN 1
        END
        
        IF EXISTS 
        (
            SELECT [name]
            FROM [internal].[customized_logging_levels]
            WHERE [name] = @new_name
        )
        BEGIN
            RAISERROR(27235, 16, 1, @new_name) WITH NOWAIT
            RETURN 1
        END
        
        UPDATE [internal].[customized_logging_levels]
        SET [name] = @new_name
        WHERE [name] = @old_name
        
        IF @@ROWCOUNT = 0
        BEGIN
             RAISERROR(27112, 16, 10, N'customized_logging_levels') WITH NOWAIT
             RETURN 1
        END
        ELSE 
        BEGIN
            
        IF @tran_count = 0
            COMMIT TRANSACTION;                                                                                 
            RETURN 0
        END
    END TRY
     
    BEGIN CATCH
        
        IF @tran_count = 0 
            ROLLBACK TRANSACTION;
        
        ELSE IF XACT_STATE() <> -1
            ROLLBACK TRANSACTION @savepoint_name;                                                                           
        THROW
    END CATCH
END
GO
IF (OBJECT_ID('[catalog].[set_customized_logging_level_description]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[set_customized_logging_level_description]
END
GO

CREATE PROCEDURE [catalog].[set_customized_logging_level_description]
    @level_name NVARCHAR(128),
    @level_description NVARCHAR(MAX)
AS
BEGIN
    SET NOCOUNT ON
    
    
    IF (IS_MEMBER('ssis_admin') = 0)
        AND (IS_SRVROLEMEMBER('sysadmin') = 0)
    BEGIN
        RAISERROR(27239, 16, 1) WITH NOWAIT
        RETURN 1
    END
    
    IF @level_name IS NULL
    BEGIN
        RAISERROR(27233, 16, 1) WITH NOWAIT
        RETURN 1
    END
    
    UPDATE [internal].[customized_logging_levels]
    SET [description] = @level_description
    WHERE [name] = @level_name
        
    IF @@ROWCOUNT = 0
    BEGIN
        RAISERROR(27237, 16, 1, @level_name) WITH NOWAIT
        RETURN 1
    END
    ELSE 
    BEGIN
        RETURN 0
    END
END
GO
IF (OBJECT_ID('[catalog].[set_customized_logging_level_value]', 'P') IS NOT NULL)
BEGIN
DROP PROCEDURE  [catalog].[set_customized_logging_level_value]
END
GO

CREATE PROCEDURE [catalog].[set_customized_logging_level_value]
    @level_name NVARCHAR(128),
    @property_name NVARCHAR(128),
    @property_value bigint
AS
BEGIN
    SET NOCOUNT ON

    
    IF (IS_MEMBER('ssis_admin') = 0)
        AND (IS_SRVROLEMEMBER('sysadmin') = 0)
    BEGIN
        RAISERROR(27239, 16, 1) WITH NOWAIT
        RETURN 1
    END

    IF @level_name IS NULL
    BEGIN
        RAISERROR(27233, 16, 1) WITH NOWAIT
        RETURN 1
    END
    
    IF @property_value < 0
    BEGIN
        RAISERROR(27240, 16, 1) WITH NOWAIT
        RETURN 1
    END

    IF (@property_name = 'PROFILE')
    BEGIN
        UPDATE [internal].[customized_logging_levels]
        SET [profile_value] = @property_value
        WHERE [name] = @level_name
        
        IF @@ROWCOUNT = 0
        BEGIN
            RAISERROR(27237, 16, 1, @level_name) WITH NOWAIT
            RETURN 1
        END
    END
    ELSE IF (@property_name = 'EVENTS')
    BEGIN
        UPDATE [internal].[customized_logging_levels]
        SET [events_value] = @property_value
        WHERE [name] = @level_name
        
        IF @@ROWCOUNT = 0
        BEGIN
            RAISERROR(27237, 16, 1, @level_name) WITH NOWAIT
            RETURN 1
        END
    END
    ELSE 
    BEGIN
        RAISERROR(27101, 16 , 1, @property_name) WITH NOWAIT
        RETURN 1
    END

    RETURN 0
END
GO

GRANT EXECUTE ON [catalog].[create_environment_variable] TO [PUBLIC]
GO

GRANT EXECUTE ON [catalog].[set_environment_variable_value] TO [PUBLIC]
GO

GRANT EXECUTE ON [catalog].[set_environment_variable_protection] TO [PUBLIC]
GO

GRANT EXECUTE ON [catalog].[set_environment_variable_property] TO [PUBLIC]
GO

GRANT EXECUTE ON [catalog].[deploy_packages] TO [PUBLIC]
GO

GRANT EXECUTE ON [catalog].[create_execution] TO [PUBLIC]
GO

GRANT EXECUTE ON [catalog].[start_execution] TO [PUBLIC]
GO

GRANT EXECUTE ON [catalog].[stop_operation] TO [PUBLIC]
GO

GRANT EXECUTE ON [catalog].[set_object_parameter_value] TO [PUBLIC]
GO

GRANT EXECUTE ON [catalog].[set_execution_parameter_value] TO [PUBLIC]
GO

GRANT EXECUTE ON [catalog].[set_execution_property_override_value] TO [PUBLIC]
GO

GRANT EXECUTE ON [catalog].[configure_catalog] TO [SSIS_ADMIN]
GO

GRANT EXECUTE ON [catalog].[create_customized_logging_level] TO [SSIS_ADMIN]
GO

GRANT EXECUTE ON [catalog].[delete_customized_logging_level] TO [SSIS_ADMIN]
GO

GRANT EXECUTE ON [catalog].[rename_customized_logging_level] TO [SSIS_ADMIN]
GO

GRANT EXECUTE ON [catalog].[set_customized_logging_level_description] TO [SSIS_ADMIN]
GO

GRANT EXECUTE ON [catalog].[set_customized_logging_level_value] TO [SSIS_ADMIN]
GO

GRANT EXECUTE ON [internal].[update_cancelled_operation_status] TO [SSIS_ADMIN]
GO

GRANT EXECUTE ON [internal].[get_decrypted_parameter_values] TO [PUBLIC]
GO

GRANT EXECUTE ON [internal].[update_package_deployment_status] TO [PUBLIC]
GO

GRANT EXECUTE ON [internal].[clean_update_packages] TO [PUBLIC]
GO

GRANT EXECUTE ON [internal].[update_project_object] TO [PUBLIC]
GO

GRANT EXECUTE ON [internal].[get_updatedpackages] TO [PUBLIC]
GO

GRANT EXECUTE ON [internal].[append_event_message] TO [PUBLIC]
GO

GRANT EXECUTE ON [internal].[append_message_context] TO [PUBLIC]
GO

GRANT EXECUTE ON [internal].[get_execution_values] TO [PUBLIC]
GO

GRANT EXECUTE ON [internal].[get_execution_property_override_values] TO [PUBLIC]
GO

GRANT EXECUTE ON [internal].[cleanup_server_log] TO [SSIS_ADMIN]
GO

GRANT EXECUTE ON [internal].[cleanup_server_execution_keys] TO [SSIS_ADMIN]
GO

GRANT SELECT ON [internal].[get_database_principals] TO [PUBLIC]
GO

GRANT EXECUTE ON [catalog].[startup] TO [SSIS_ADMIN]
GO

GRANT EXECUTE ON [catalog].[startup] TO [SSIS_FAILOVER_MONITORING_AGENT]
GO

GRANT EXECUTE ON [internal].[get_log_dbconnection] TO [SSIS_ADMIN]
GO

GRANT EXECUTE ON [internal].[update_master_settings] TO [SSIS_ADMIN]
GO

GRANT EXECUTE ON [internal].[update_worker_agent_status] TO [SSIS_ADMIN]
GO

GRANT EXECUTE ON [internal].[master_heartbeat] to [SSIS_ADMIN]
GO
--##END



------------------------ASSEMBLY UPGRADE---------------------------------------
PRINT '---------------------------------'
PRINT 'Starting assembly upgrade'
PRINT '---------------------------------'


IF (OBJECT_ID('[internal].[convert_value]', 'FS') IS NOT NULL) 
BEGIN
	PRINT 'Dropping function [internal].[convert_value]'
	DROP FUNCTION [internal].[convert_value]
END

IF (OBJECT_ID('[internal].[Create_Key_Information]', 'PC') IS NOT NULL) 
BEGIN
	PRINT 'Dropping procedure [internal].[Create_Key_Information]'
	DROP PROCEDURE [internal].[Create_Key_Information]
END

IF (OBJECT_ID('[internal].[encrypt_binarydata]', 'FS') IS NOT NULL) 
BEGIN
	PRINT 'Dropping function [internal].[encrypt_binarydata]'
	DROP FUNCTION [internal].[encrypt_binarydata]
END

IF (OBJECT_ID('[internal].[decrypt_binarydata]', 'FS') IS NOT NULL) 
BEGIN
	PRINT 'Dropping function [internal].[decrypt_binarydata]'
	DROP FUNCTION  [internal].[decrypt_binarydata]
END

IF (OBJECT_ID('[internal].[get_isserver_processes]', 'FT') IS NOT NULL) 
BEGIN
	PRINT 'Dropping procedure [internal].[get_isserver_processes]'
	DROP FUNCTION [internal].[get_isserver_processes]
END

IF (OBJECT_ID('[internal].[deploy_project_internal]', 'PC') IS NOT NULL) 
BEGIN
	PRINT 'Dropping procedure [internal].[deploy_project_internal]'
	DROP PROCEDURE [internal].[deploy_project_internal]
END

IF (OBJECT_ID('[internal].[deploy_packages_internal]', 'PC') IS NOT NULL) 
BEGIN
	PRINT 'Dropping procedure [internal].[deploy_packages_internal]'
	DROP PROCEDURE [internal].[deploy_packages_internal]
END

IF (OBJECT_ID('[internal].[set_system_informations]', 'PC') IS NOT NULL) 
BEGIN
	PRINT 'Dropping procedure [internal].[set_system_informations]'
	DROP PROCEDURE  [internal].[set_system_informations]
END

IF (OBJECT_ID('[internal].[start_execution_internal]', 'PC') IS NOT NULL) 
BEGIN
	PRINT 'Dropping procedure [internal].[start_execution_internal]'
	DROP PROCEDURE [internal].[start_execution_internal]
END

IF (OBJECT_ID('[internal].[stop_operation_internal]', 'PC') IS NOT NULL) 
BEGIN
	PRINT 'Dropping procedure [internal].[stop_operation_internal]'
	DROP PROCEDURE [internal].[stop_operation_internal]
END

IF (OBJECT_ID('[internal].[create_execution_dump_internal]', 'PC') IS NOT NULL) 
BEGIN
	PRINT 'Dropping procedure [internal].[create_execution_dump_internal]'
	DROP PROCEDURE [internal].[create_execution_dump_internal]
END

IF (OBJECT_ID('[internal].[get_execution_perf_counters]', 'FT') IS NOT NULL) 
BEGIN
	PRINT 'Dropping function [internal].[get_execution_perf_counters]'
	DROP FUNCTION  [internal].[get_execution_perf_counters]
END

IF (OBJECT_ID('[internal].[get_package_data]', 'FT') IS NOT NULL) 
BEGIN
	PRINT 'Dropping function [internal].[get_package_data]'
	DROP FUNCTION  [internal].[get_package_data]
END

IF (OBJECT_ID('[internal].[validate_package_internal]', 'PC') IS NOT NULL) 
BEGIN
	PRINT 'Dropping procedure [internal].[validate_package_internal]'
	DROP PROCEDURE [internal].[validate_package_internal]
END

IF (OBJECT_ID('[internal].[validate_project_internal]', 'PC') IS NOT NULL) 
BEGIN
	PRINT 'Dropping procedure [internal].[validate_project_internal]'
	DROP PROCEDURE [internal].[validate_project_internal]
END

IF (OBJECT_ID('[internal].[get_server_account]', 'PC') IS NOT NULL) 
BEGIN
	PRINT 'Dropping procedure [internal].[get_server_account]'
	DROP PROCEDURE  [internal].[get_server_account]
END

IF (OBJECT_ID('[internal].[check_schema_version_internal]', 'PC') IS NOT NULL) 
BEGIN
	PRINT 'Dropping procedure [internal].[check_schema_version_internal]'
	DROP PROCEDURE [internal].[check_schema_version_internal]
END

IF (OBJECT_ID('[internal].[is_valid_name]', 'FS') IS NOT NULL) 
BEGIN
	PRINT 'Dropping function [internal].[is_valid_name]'
	DROP FUNCTION [internal].[is_valid_name]
END

GO

PRINT 'Altering assemly [ISSERVER]'
GO


--TODO: Alter assembly
USE SSISDB

--TODO: Alter assembly
	DECLARE @architecture nvarchar(255)
	DECLARE @keypath nvarchar(1024)
	DEClARE @path nvarchar(255)

    /*EXEC master..xp_regread 'HKEY_LOCAL_MACHINE',
                   'System\CurrentControlSet\Control\Session Manager\Environment',
                   'PROCESSOR_ARCHITECTURE',
                   @architecture  OUTPUT
        
    IF @architecture = 'x86'
	BEGIN
		SET @keypath = N'SOFTWARE\Microsoft\Microsoft SQL Server\150\SSIS\Setup\DTSPath'    
		EXEC master..xp_regread 'HKEY_LOCAL_MACHINE',
                   @keypath,
                   N'',
                   @path  OUTPUT
	END
	ELSE
	BEGIN
		SET @keypath = N'SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server\150\SSIS\Setup\DTSPath'
		EXEC master..xp_regread 'HKEY_LOCAL_MACHINE',
                   @keypath,
                   N'',
                   @path  OUTPUT

		IF @keypath IS NULL
		BEGIN*/
		SET @keypath = N'SOFTWARE\Microsoft\Microsoft SQL Server\150\SSIS\Setup\DTSPath'    
		EXEC master..xp_regread 'HKEY_LOCAL_MACHINE',
                   @keypath,
                   N'',
                   @path  OUTPUT			
		/*END
	END*/

	DECLARE @fullPath nvarchar(255)
	DECLARE @oldISServerHashCode varbinary(64)
	DECLARE @oldISServerAssemblyId int

	IF @path IS NOT NULL
		BEGIN
			SET @fullPath = REPLACE(@path,'''','''''') + N'Binn\Microsoft.SqlServer.IntegrationServices.Server.dll'
			BEGIN TRY
				SELECT @oldISServerAssemblyId=assembly_id FROM sys.assemblies WHERE name = 'ISSERVER'
				IF @oldISServerAssemblyId IS NOT NULL
					BEGIN
						SELECT @oldISServerHashCode=HASHBYTES('SHA2_512', content) FROM sys.assembly_files WHERE assembly_id = @oldISServerAssemblyId
						IF @oldISServerHashCode is NOT NULL
							BEGIN
								IF EXISTS(SELECT * FROM sys.trusted_assemblies WHERE hash=@oldISServerHashCode)
									EXEC sys.sp_drop_trusted_assembly @oldISServerHashCode
							END
				DROP ASSEMBLY ISSERVER
					END
				-- Use OpenRowset to read the binary bits into @asm_bin
				EXECUTE ('DECLARE @asm_bin VARBINARY(max);
				DECLARE @ISServerHashCode VARBINARY(64)
				SELECT @asm_bin = BulkColumn FROM OPENROWSET (BULK N''' +@fullPath +''',SINGLE_BLOB) AS dll
				SELECT @ISServerHashCode=HASHBYTES(''SHA2_512'', @asm_bin)
				IF EXISTS(SELECT * FROM sys.trusted_assemblies WHERE hash = @ISServerHashCode)
				    EXEC sys.sp_drop_trusted_assembly @ISServerHashCode
				EXEC sys.sp_add_trusted_assembly @ISServerHashCode, N''' +@fullPath +'''
				CREATE ASSEMBLY ISSERVER FROM  @asm_bin  WITH PERMISSION_SET = UNSAFE')

			END TRY
			BEGIN CATCH
				DECLARE @ErrorMessage nvarchar(MAX)
				SELECT @ErrorMessage = ERROR_MESSAGE()
				RAISERROR(@ErrorMessage, 16, 127) WITH LOG
			END CATCH
		END
	ELSE
	BEGIN
		RAISERROR('We could not get the full path of Microsoft.SqlServer.IntegrationServices.Server.dll', 20, 127) WITH LOG
	END 
GO

-- Remove the login and asymmetric key for the ISSERVER assembly.
USE master

IF EXISTS(SELECT [name] FROM sys.server_principals where name = '##MS_SQLEnableSystemAssemblyLoadingUser##')
    DROP LOGIN ##MS_SQLEnableSystemAssemblyLoadingUser##
IF EXISTS(SELECT * FROM sys.asymmetric_keys WHERE name = 'MS_SQLEnableSystemAssemblyLoadingKey') 
    DROP ASYMMETRIC KEY MS_SQLEnableSystemAssemblyLoadingKey
IF EXISTS(SELECT * FROM sys.certificates WHERE name = 'MS_SSISEnableAssemblyLoadingCert')
    DROP Certificate MS_SSISEnableAssemblyLoadingCert

GO

-- Recreate all the CLR function and procedures
USE SSISDB

PRINT ''
PRINT 'Creating function internal.is_valid_name'
GO

CREATE FUNCTION [internal].[is_valid_name]
(
    @object_name NVARCHAR(MAX)
)
RETURNS BIT
AS 
EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.ServerApi].IsValidName
GO


PRINT ''
PRINT 'CREATE PROCEDURE [internal].[convert_value]'
GO 
CREATE FUNCTION [internal].[convert_value]
(
        @origin_value       sql_variant,         
        @data_type          nvarchar(128)        
)
RETURNS sql_variant
AS EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.ServerApi].ConvertValue
GO

PRINT ''
PRINT 'CREATE PROCEDURE [internal].[create_key_information]'
GO 

CREATE PROCEDURE [internal].[create_key_information]
        @algorithm_name nvarchar(255),
        @key         varbinary(8000) output,         
        @IV          varbinary(8000) output       
AS EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.Security.CryptoGraphy].CreateKeyInformation
GO

PRINT ''
PRINT 'CREATE PROCEDURE [internal][encrypt_binarydata]'
GO 

CREATE FUNCTION [internal].[encrypt_binarydata] 
        (
            @algorithmName nvarchar(255),
            @key       varbinary(8000) ,         
            @IV          varbinary(8000),
            @binary_value  varbinary(Max)
        )
returns varbinary(Max)
AS EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.Security.CryptoGraphy].EncryptBinaryData
GO


CREATE FUNCTION [internal].[get_isserver_processes]()
RETURNS TABLE 
(process_id bigint)
AS 
EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.StartupApi].GetISServerProcesses
GO


PRINT ''
PRINT 'CREATE PROCEDURE [internal][decrypt_binarydata]'
GO 
CREATE FUNCTION [internal].[decrypt_binarydata] 
        (
            @algorithmName nvarchar(255),
            @key       varbinary(8000) ,         
            @IV          varbinary(8000),
            @binary_value  varbinary(Max)
        )
returns varbinary(Max)
AS EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.Security.CryptoGraphy].DecryptBinaryData
GO

PRINT ''
PRINT 'CREATE Function [internal][get_package_data]'
GO 
CREATE FUNCTION [internal].[get_package_data]
( 
    @key_name nvarchar(255), 
    @KEY varbinary(8000),
    @IV varbinary(8000),
    @project_version_lsn bigint,
    @project_id bigint
)
RETURNS TABLE
(name nvarchar(260), package_data varbinary(Max))
WITH EXECUTE AS 'AllSchemaOwner'
AS EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.Security.CryptoGraphy].GetDecryptedPackageData
GO

PRINT ''
PRINT 'CREATE PROCEDURE [internal].[deploy_project_internal]'
GO 
CREATE PROCEDURE [internal].[deploy_project_internal]
        @deploy_id           bigint,         
        @version_id          bigint,         
        @project_id          bigint,         
        @project_name        nvarchar(128)   
AS EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.ServerApi].DeployProjectInternal
GO

PRINT ''
PRINT 'CREATE PROCEDURE [internal].[deploy_packages_internal]'
GO 
CREATE PROCEDURE [internal].[deploy_packages_internal]
        @deploy_id           bigint,         
        @version_id          bigint,         
        @project_id          bigint,         
        @project_name        nvarchar(128),  
        @folder_name        nvarchar(128)    
AS EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.ServerApi].DeployPackagesInternal
GO

----------------------------set_system_informations------------------------
PRINT ''
PRINT 'CREATE PROCEDURE [internal].[set_system_informations]'
GO 
CREATE PROCEDURE [internal].[set_system_informations]
        @operation_id          bigint     --Id of the operation
AS EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.SystemInformations].SetSystemInformations
GO


PRINT ''
PRINT 'CREATE PROCEDURE [internal].[start_execution_internal]'
GO 
-------------------CLR Stored Procedure start_package_internal--------------
CREATE PROCEDURE [internal].[start_execution_internal]
        @project_id     bigint,         --ID of the project
        @execution_id   bigint,     --ID of the execution
        @version_id      bigint,         --ID of the version
        @use32BitRuntime smallint       --Check whether using 32bit binary in 64bit machine
AS EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.ServerApi].StartExecutionInternal
GO

-- ================================================
-- [internal].[stop_operation_internal]
-- ================================================
PRINT ''
PRINT 'CREATE PROCEDURE [internal].[stop_operation_internal]'
GO 
CREATE PROCEDURE [internal].[stop_operation_internal]
        @operation_id bigint,              --ID of the operation
        @process_id   int   ,              --ID of the external execution process
        @operation_guid UniqueIdentifier   --Unique ID of the operation
WITH EXECUTE AS CALLER
AS EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.ServerApi].StopOperationInternal
GO

PRINT ''
PRINT 'CREATE PROCEDURE [internal].[create_execution_dump_internal]'
GO 
CREATE PROCEDURE [internal].[create_execution_dump_internal]
        @execution_id bigint                        --ID of the execution
WITH EXECUTE AS CALLER
AS EXTERNAL NAME [ISSERVER].[Microsoft.SqlServer.IntegrationServices.Server.ServerApi].CreateExecutionDumpInternal
GO


PRINT ''
PRINT 'CREATE FUNCTION [internal].[get_execution_perf_counters]'
GO 
CREATE FUNCTION [internal].[get_execution_perf_counters](@execution_id bigint, @execution_guid uniqueidentifier)
RETURNS TABLE 
(execution_id bigint,
 counter_name nvarchar(128),
 counter_value bigint)
WITH EXECUTE AS CALLER
AS EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.ExecPerfCounterApi].GetExecPerfCounters
GO

DECLARE @CreateViewStatement nvarchar(max) 
set @CreateViewStatement = N'
CREATE VIEW [internal].[current_user_object_permissions]
AS
SELECT     obj.[object_type],
           obj.[object_id],
           obj.[permission_type], 
           obj.[sid],
           obj.[is_role],
           obj.[is_deny]
FROM       [internal].[object_permissions] AS obj INNER JOIN [sys].[database_principals] AS pri
ON obj.[sid] = pri.[sid]
WHERE     
((pri.[type] = ''S'' OR pri.[type] = ''U'') AND obj.[sid] = USER_SID (DATABASE_PRINCIPAL_ID()))
OR
((pri.[type] = ''G'' OR pri.[type] = ''R'') AND IS_MEMBER(pri.name)=1)
	
'

IF (OBJECT_ID('[internal].[current_user_object_permissions]', 'V') IS NOT NULL)
BEGIN
set @CreateViewStatement = replace (@CreateViewStatement, N'CREATE', N'ALTER')
END
EXEC(@CreateViewStatement)
GO

----------------------------validate_package_internal------------------------
PRINT ''
PRINT 'CREATE PROCEDURE [internal].[validate_package_internal]'
GO 
CREATE PROCEDURE [internal].[validate_package_internal]
        @project_id         bigint,         --ID of the project
        @package_id         bigint,         -- ID of the package
        @version_id         bigint,         --ID of the version
        @validation_id      bigint,        -- Id of the operation
        @environment_scope nchar(1),           -- 'A','S' or 'D'
        @use32bitruntime    smallint          --Check whether use 32 bit runtime
AS EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.ServerApi].ValidatePackageInternal
GO

PRINT ''
PRINT 'CREATE PROCEDURE [internal].[validate_project_internal]'
GO 
CREATE PROCEDURE [internal].[validate_project_internal]
        @project_id         bigint,         --ID of the project
        @version_id         bigint,         --ID of the version
        @validation_id      bigint,        -- Id of the operation
        @environment_scope nchar(1),           -- 'A','S' or 'D'
        @use32bitruntime    smallint          --Check whether use 32 bit runtime
AS EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.ServerApi].ValidateProjectInternal
GO

PRINT ''
PRINT 'CREATE PROCEDURE [internal].[get_server_account]'
GO 
---------------------Get SQL Server Process Account: get_server_account------
CREATE PROCEDURE [internal].[get_server_account]
        @account_name   [internal].[adt_name]  OUTPUT  -- the returned account name
AS EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.ServerApi].GetServerAccount
GO

PRINT ''
PRINT 'CREATE PROCEDURE [internal].[check_schema_version_internal]'
GO 
--------------------- check schema version internal ---------------------------------
CREATE PROCEDURE [internal].[check_schema_version_internal]
        @operationId			bigint,				-- ID of the operation
		@use32bitruntime		smallint,			-- Check whether use 32 bit runtime
		@serverBuild			nvarchar(1024) OUTPUT,
        @schemaVersion			int OUTPUT,
		@schemaBuild			nvarchar(1024) OUTPUT,
		@assemblyBuild			nvarchar(1024) OUTPUT,
		@componentVersion		nvarchar(1024) OUTPUT,
		@compatibilityStatus	smallint OUTPUT	
AS EXTERNAL NAME ISSERVER.[Microsoft.SqlServer.IntegrationServices.Server.ServerApi].CheckSchemaVersionInternal
GO

-- CLR stored procedure permission setting
PRINT 'Drop the certificate the user for module signing'
DROP USER ModuleSigner
GO

DROP CERTIFICATE MS_SQLISSigningCertificate
GO


USE SSISDB

	PRINT  'Alter assembly SSISDB'
	DECLARE @architecture nvarchar(255)
	DECLARE @keypath nvarchar(1024)
	DECLARE @cert_path  nvarchar(1024)
	DECLARE @signature_path  nvarchar(1024)
	DECLARE @path nvarchar(255)

    /*EXEC master..xp_regread 'HKEY_LOCAL_MACHINE',
                   'System\CurrentControlSet\Control\Session Manager\Environment',
                   'PROCESSOR_ARCHITECTURE',
                   @architecture  OUTPUT
        
    IF @architecture = 'x86'
	BEGIN
		SET @keypath = N'SOFTWARE\Microsoft\Microsoft SQL Server\150\SSIS\Setup\DTSPath'    
		EXEC master..xp_regread 'HKEY_LOCAL_MACHINE',
                   @keypath,
                   N'',
                   @path  OUTPUT
	END
	ELSE
	BEGIN
		SET @keypath = N'SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server\150\SSIS\Setup\DTSPath'
		EXEC master..xp_regread 'HKEY_LOCAL_MACHINE',
                   @keypath,
                   N'',
                   @path  OUTPUT

		IF @keypath IS NULL
		BEGIN*/
		SET @keypath = N'SOFTWARE\Microsoft\Microsoft SQL Server\150\SSIS\Setup\DTSPath'    
		EXEC master..xp_regread 'HKEY_LOCAL_MACHINE',
                   @keypath,
                   N'',
                   @path  OUTPUT			
		/*END
	END*/



	SET @cert_path = REPLACE(@path, '''', '''''') + N'Binn\MS_SQLISUpgradeCertificate.cer' 
	SET @signature_path = REPLACE(@path, '''', '''''') + N'Binn\MS_SQLISUpgradeSignature.xml'

	PRINT @cert_path
	PRINT @signature_path

	EXEC ('CREATE CERTIFICATE MS_SQLISSigningCertificate FROM FILE= ''' + @cert_path + '''')

	CREATE USER ModuleSigner FROM CERTIFICATE MS_SQLISSigningCertificate

	DECLARE @xmlDoc xml
	DECLARE @sql_string nvarchar(MAX)
	SET @sql_string = 'SELECT @xmlDoc = CONVERT(xml, BulkColumn, 2) FROM OPENROWSET(Bulk ''' +@signature_path +''', SINGLE_BLOB) [rowsetresults] '
	PRINT @sql_string
	EXEC sp_executesql @sql_string, N'@xmlDoc xml output', @xmlDoc = @xmlDoc output

	DECLARE @object_name nvarchar(128)
	DECLARE @object_signature nvarchar(MAX)

	
	SET CONCAT_NULL_YIELDS_NULL ON
	SET QUOTED_IDENTIFIER ON
	SET ANSI_NULLS ON
	SET ANSI_PADDING ON
	SET ANSI_WARNINGS ON

	PRINT 'OPEN CURSOR'
	DECLARE signature_cursor CURSOR FOR
	SELECT Meta.Data.value('@object_name','nvarchar(128)') AS [object_name],
		   Meta.Data.query('./SIGNATURE').value('.','nvarchar(MAX)') AS [object_signature]
		FROM @xmlDoc.nodes('/ROOT/meta') AS Meta(Data)

	OPEN signature_cursor

	FETCH NEXT FROM signature_cursor 
		INTO @object_name, @object_signature
	WHILE @@FETCH_STATUS = 0
	BEGIN
		set @object_signature = REPLACE(REPLACE(@object_signature,char(10),''), '''','''''')
		SELECT @sql_string = 'ADD SIGNATURE TO '+ @object_name +' BY CERTIFICATE [MS_SQLISSigningCertificate] WITH SIGNATURE  = ' + @object_signature
		EXEC (@sql_string)

		PRINT @sql_string

		FETCH NEXT FROM signature_cursor 
		INTO @object_name, @object_signature
	END

	CLOSE signature_cursor;
	DEALLOCATE signature_cursor;

GO

PRINT 'Grant permission to ModuleSigner'

GRANT UPDATE ON [internal].[operations] TO ModuleSigner
GO

GRANT SELECT ON [internal].[operations] TO ModuleSigner
GO

GRANT EXECUTE ON [internal].[append_operation_message] TO ModuleSigner
GO

GRANT SELECT ON [catalog].[catalog_properties] TO ModuleSigner
GO

GRANT INSERT ON [internal].[operation_os_sys_info] TO ModuleSigner
GO

GRANT EXECUTE ON [internal].[check_schema_version_internal] TO ModuleSigner
GO

GRANT VIEW DEFINITION TO ModuleSigner
GRANT VIEW DATABASE STATE TO ModuleSigner

PRINT '---------------------------------'
GO

IF EXISTS(SELECT * FROM sys.database_principals where name = '##MS_SSISLogDBWorkerAgentUser##')
	EXEC sp_addrolemember 'ssis_cluster_worker', '##MS_SSISLogDBWorkerAgentUser##'

/******************************************************
Permission control of the stored procedure
*******************************************************/
PRINT 'Permission control of the stored procedure'

GRANT EXECUTE ON [internal].[get_server_account] TO [PUBLIC]
GO

GRANT EXECUTE ON [internal].[cleanup_server_retention_window] TO ##MS_SSISServerCleanupJobUser##
GO

GRANT EXECUTE ON TYPE::[internal].[machine_property_list_type] TO [PUBLIC]
GO

GRANT EXECUTE ON TYPE::[internal].[machine_perf_counter_list_type] TO [PUBLIC]
GO

GRANT SELECT ON [internal].[operation_messages_scaleout] TO  ssis_cluster_worker
GO

GRANT INSERT ON [internal].[operation_messages_scaleout] TO  ssis_cluster_worker
GO

GRANT SELECT ON [internal].[event_messages_scaleout] TO  ssis_cluster_worker
GO

GRANT INSERT ON [internal].[event_messages_scaleout] TO  ssis_cluster_worker
GO

GRANT SELECT ON [internal].[event_message_context_scaleout] TO  ssis_cluster_worker
GO

GRANT INSERT ON [internal].[event_message_context_scaleout] TO  ssis_cluster_worker
GO


USE msdb
GO

IF EXISTS (SELECT name FROM sysjobs WHERE name = N'SSIS Server Maintenance Job')
BEGIN
	EXEC sp_update_jobstep
		@job_name = N'SSIS Server Maintenance Job',
		@step_id = 1,
		@step_name = N'SSIS Server Operation Records Maintenance',
		@subsystem = N'TSQL',
		@command = N'
		DECLARE @role int
		SET @role = (SELECT [role] FROM [sys].[dm_hadr_availability_replica_states] hars INNER JOIN [sys].[availability_databases_cluster] adc ON hars.[group_id] = adc.[group_id] WHERE hars.[is_local] = 1 AND adc.[database_name] =''SSISDB'')
		IF DB_ID(''SSISDB'') IS NOT NULL AND (@role IS NULL OR @role = 1)
			EXEC [SSISDB].[internal].[cleanup_server_retention_window]',
		@database_name = N'msdb',
		@on_success_action = 3,
		@retry_attempts = 3,
		@retry_interval = 3;
END
GO

IF EXISTS (SELECT name FROM sysjobs WHERE name = N'SSIS Server Maintenance Job')
BEGIN
	EXEC sp_update_jobstep
		@job_name = N'SSIS Server Maintenance Job',
		@step_id = 2,
		@step_name = N'SSIS Server Max Version Per Project Maintenance',
		@subsystem = N'TSQL',
		@command = N'
		DECLARE @role int
		SET @role = (SELECT [role] FROM [sys].[dm_hadr_availability_replica_states] hars INNER JOIN [sys].[availability_databases_cluster] adc ON hars.[group_id] = adc.[group_id] WHERE hars.[is_local] = 1 AND adc.[database_name] =''SSISDB'')
		IF DB_ID(''SSISDB'') IS NOT NULL AND (@role IS NULL OR @role = 1)
			EXEC [SSISDB].[internal].[cleanup_server_project_version]',
		@database_name = N'msdb',
		@retry_attempts = 3,
		@retry_interval = 3;
END
GO

IF EXISTS (SELECT name FROM sysjobs WHERE name = N'SSIS Failover Monitor Job')
BEGIN
    EXEC sp_delete_job
        @job_name = N'SSIS Failover Monitor Job' ;
END
GO

USE master
GO

GRANT VIEW SERVER STATE TO ##MS_SSISServerCleanupJobLogin##
GO

USE SSISDB

GO


IF NOT EXISTS (SELECT * FROM SSISDB.internal.catalog_properties WHERE property_name = 'SERVER_OPERATION_ENCRYPTION_LEVEL')
INSERT INTO SSISDB.internal.catalog_properties VALUES
('SERVER_OPERATION_ENCRYPTION_LEVEL', '1')
ELSE
BEGIN
       DECLARE @curr_level int
       SELECT @curr_level = property_value FROM SSISDB.internal.catalog_properties WHERE property_name = 'SERVER_OPERATION_ENCRYPTION_LEVEL'
       IF @curr_level <> 2
       BEGIN
              UPDATE [SSISDB].[internal].[catalog_properties] 
              SET 
              [property_value]  = '1'
              WHERE property_name = 'SERVER_OPERATION_ENCRYPTION_LEVEL'
       END
END 

GO

DECLARE @schema_build [nvarchar](256)
DECLARE @upgrade_build [nvarchar](256)
SELECT @upgrade_build = (SELECT property_value FROM [internal].[catalog_properties] WHERE property_name = N'SCHEMA_BUILD')
SELECT @schema_build = CONVERT(nvarchar, SERVERPROPERTY('ProductVersion'))

-- only update value of UPGRADE_FROM when old schema_build does not equal with ProductVersion
IF (@upgrade_build <> @schema_build)
BEGIN
	IF EXISTS (select * from [internal].[catalog_properties] where property_name = N'UPGRADE_FROM')
	BEGIN
		UPDATE [internal].[catalog_properties] SET property_value = @upgrade_build WHERE property_name = N'UPGRADE_FROM'
	END
	ELSE
	BEGIN
		INSERT INTO [internal].[catalog_properties] VALUES (N'UPGRADE_FROM',@upgrade_build)
	END

	UPDATE [internal].[catalog_properties] SET property_value = @schema_build WHERE property_name = 'SCHEMA_BUILD'
END

GO

-- All the work completed, update the schema verion
UPDATE [internal].[catalog_properties] SET property_value = '6' WHERE property_name = 'SCHEMA_VERSION'
GO
 
ALTER DATABASE [SSISDB] SET MULTI_USER WITH ROLLBACK IMMEDIATE  
GO
/*
**  SSIS_hotfix_install.SQL
**  Patch install script for the SSIS server catalog (SSISDB).
*/

PRINT '------------------------------------------------------'
PRINT 'Starting execution of SSIS_HOTFIX_INSTALL.SQL         '
PRINT '------------------------------------------------------'

DECLARE @run_script BIT
SET @run_script=1

DECLARE @ssis_database_name SYSNAME
SET @ssis_database_name = N'SSISDB'

-- Check whether SSISDB exists
IF(DB_ID(@ssis_database_name) IS NULL)
BEGIN
    SET @run_script=0
    PRINT 'Database SSISDB does not exist in current SQL Server instance'
END

-- Check whether SSISDB is online
IF @run_script <> 0
BEGIN
	DECLARE @state_online SYSNAME
	SET @state_online = 'ONLINE'
	SELECT @state_online = UPPER(@state_online COLLATE SQL_Latin1_General_CP1_CI_AS)

	IF NOT EXISTS (SELECT state_desc FROM master.sys.databases WHERE name = @ssis_database_name AND
										 UPPER(state_desc COLLATE SQL_Latin1_General_CP1_CI_AS) LIKE @state_online)
	BEGIN
		SET @run_script=0    
		PRINT 'WARNING! The database SSISDB is not ONLINE. SSIS_HOTFIX_INSTALL.SQL will not be applied. Please run the script manually after the upgrade.'
	END
END

-- Check SSISDB is not in always-on group
IF @run_script <> 0
BEGIN
	IF EXISTS (SELECT hadr.database_state FROM master.sys.dm_hadr_database_replica_states AS hadr 
			JOIN master.sys.databases AS dbs 
			ON hadr.database_id = dbs.database_id 
			WHERE dbs.name = @ssis_database_name)
	BEGIN
		SET @run_script=0
		PRINT 'WARNING! The database SSISDB is in alwayson group. So skipping execution of ISServer_upgrade.sql on it. Please run the script manually after the upgrade.'
	END
END

-- Check whether SSISDB is corrupted
IF @run_script <> 0
BEGIN
	IF OBJECT_ID (N'SSISDB.internal.catalog_properties', N'U') IS NULL
	BEGIN
		SET @run_script=0
		PRINT 'Database SSISDB is missing the catalog properties table. The database may be corrupted, or it is not an SSIS Catalog.'
	END
END

IF  @run_script <> 0
BEGIN
	PRINT 'Start applying SSIS_HOTFIX_INSTALL changes.'

	DECLARE @rawCmd NVARCHAR(MAX), @cmd NVARCHAR(MAX)

	DECLARE @targetVersion NVARCHAR(256)
	SELECT @targetVersion = CONVERT(NVARCHAR,SERVERPROPERTY(N'ProductVersion'))
	
    --1. drop the old SP
    
    --2. create the new SP
    
    --3. grant the permission on new one

	--4. Finally update the schema build number to server's build number
	SET @cmd = 'UPDATE [SSISDB].[internal].[catalog_properties] SET property_value = N'''+@targetVersion+''' WHERE property_name = N''SCHEMA_BUILD'''
	EXEC sp_executesql @cmd
	PRINT 'Schema build in SSISDB has been updated to ' + @targetVersion

END

PRINT '------------------------------------------------------'
PRINT 'Execution of SSIS_HOTFIX_INSTALL.SQL completed'
PRINT '------------------------------------------------------'
GO


/****************************************************************************
//		Copyright (c) Microsoft Corporation.
//
// @File: shutdown_xevents_add.sql
//
// Purpose:
//		Adds process_killed and sql_exit_invoked xevents to the default 
//		system_health xevent session on the server.  This addition adds 
//		diagnostics to allow for easy debugging so that CSS and other teams can
//		determine what caused an improper shutdown (and normal shutdown too).
//
// Notes:
//		This script is run using the sql script upgrade framework at server
//		startup.
//
// @EndHeader@
*****************************************************************************/

PRINT '------------------------------------------------------'
PRINT 'Starting execution of shutdown_xevents_add.sql.       '
PRINT '------------------------------------------------------'
GO

IF  EXISTS (SELECT * FROM sys.server_event_sessions WHERE name = 'system_health') AND
	NOT EXISTS (SELECT * FROM sys.server_event_sessions S LEFT JOIN 
	sys.server_event_session_events E ON S.event_session_id = E.event_session_id
	WHERE S.name = 'system_health' AND E.name = 'process_killed' AND E.package = 'sqlos')
BEGIN
	ALTER EVENT SESSION system_health ON SERVER
	ADD EVENT sqlos.process_killed
		(ACTION (sqlserver.client_app_name, sqlserver.client_hostname, sqlserver.client_pid,
				sqlserver.query_hash, sqlserver.session_id, sqlserver.session_nt_username, package0.callstack));
END
ELSE
BEGIN
	PRINT 'Warning: Could not add sqlos.process_killed to the system_health xevent session';
END
GO

IF  EXISTS (SELECT * FROM sys.server_event_sessions WHERE name = 'system_health') AND
	NOT EXISTS (SELECT * FROM sys.server_event_sessions S LEFT JOIN
	sys.server_event_session_events E ON S.event_session_id = E.event_session_id
	WHERE S.name = 'system_health' AND E.name = 'sql_exit_invoked' AND E.package = 'sqlserver')
BEGIN
	ALTER EVENT SESSION system_health ON SERVER
	ADD EVENT sqlserver.sql_exit_invoked
		(ACTION (sqlserver.client_app_name, sqlserver.client_hostname, sqlserver.client_pid,
				sqlserver.query_hash, sqlserver.session_id, sqlserver.session_nt_username, package0.callstack));
END
ELSE
BEGIN
	PRINT 'Warning: Could not add sqlserver.sql_exit_invoked to the system_health xevent session';
END
GO

PRINT '------------------------------------------------------'
PRINT 'Execution of shutdown_xevents_add.sql completed.      '
PRINT '------------------------------------------------------'
GO
/************************************************************************/
/* provision_ceipsvc_identity.sql                                       */
/*                                                                      */
/* This script is called to provision login for sql ceip service account*/
/* during sql upgrade                                                   */
/* Copyright (c) Microsoft Corporation                                  */
/* All Rights Reserved.                                                 */
/*                                                                      */
/************************************************************************/


PRINT '------------------------------------------------------'
PRINT 'Start provisioning of CEIPService Login       '
PRINT '------------------------------------------------------'
GO

DECLARE @advopt_old_value INT;
DECLARE @comp_old_value   INT;
SELECT @advopt_old_value=cast(value_in_use as int) from sys.configurations where name = 'show advanced options';
SELECT @comp_old_value=cast(value_in_use as int) from sys.configurations where name = 'Agent XPs';
EXEC sp_configure 'show advanced options', 1; 
RECONFIGURE WITH OVERRIDE;
EXEC sp_configure 'Agent XPs', 1;
RECONFIGURE WITH OVERRIDE; 

DECLARE @CEIPLoginSID nvarchar(256);
-- SID of service account or service principal representing CEIP service.this will be set in setup extension for ceip component
EXEC master.dbo.xp_instance_regread N'HKEY_LOCAL_MACHINE', N'SOFTWARE\Microsoft\MSSQLServer\Setup', N'SqlCEIPSvc', @CEIPLoginSID OUTPUT, no_output

-- restore option state
EXEC sp_configure 'Agent XPs', @comp_old_value;
RECONFIGURE WITH OVERRIDE; 
EXEC sp_configure 'show advanced options', @advopt_old_value; 
RECONFIGURE WITH OVERRIDE;

BEGIN
	IF (@CEIPLoginSID IS NOT NULL)
	BEGIN
		--we get the login name from SID. Ensures that we always get the login name with correct casing
		--with respect to sql server collation.
		DECLARE @CEIPLoginSIDBinary varbinary(256)
		DECLARE @CEIPLoginName nvarchar(256)
		
		SELECT @CEIPLoginSIDBinary = sid_binary(@CEIPLoginSID);
		SELECT @CEIPLoginName = suser_sname(@CEIPLoginSIDBinary);
		
		DECLARE @cmd1 nvarchar(max)
		
		IF (@CEIPLoginName IS NOT NULL)
		BEGIN
			IF NOT EXISTS (SELECT * FROM sys.server_principals WHERE name = @CEIPLoginName)
			BEGIN
				-- Create a new login for ceip . pre2k16 to 2k16 upgrade scenario
				SELECT @cmd1 = 'CREATE LOGIN ' + QUOTENAME(@CEIPLoginName) + ' FROM WINDOWS'
				print 'Running command to create sql ceip svc service login: ' + @cmd1
				EXEC (@cmd1)
			END
		
			-- In ctp3.0 2016 install this login had sysadmin privilege.drop that role and give grant state + connect any db .
			print 'executing sp_dropsrvrolemember sysadmin for ' + @CEIPLoginName
			EXEC sys.sp_dropsrvrolemember @CEIPLoginName, N'sysadmin'

			SELECT @cmd1 = 'GRANT VIEW SERVER STATE TO ' + QUOTENAME(@CEIPLoginName)
			print 'Running command to grant view server state: ' + @cmd1
			EXEC (@cmd1)
			SELECT @cmd1 = 'GRANT CONNECT ANY DATABASE TO ' + QUOTENAME(@CEIPLoginName)
			print 'Running command to connect any database: ' + @cmd1
			EXEC (@cmd1)
			SELECT @cmd1 = 'GRANT VIEW ANY DEFINITION TO ' + QUOTENAME(@CEIPLoginName)
			print 'Running command to view any definition: ' + @cmd1
			EXEC (@cmd1)
			SELECT @cmd1 = 'GRANT ALTER ANY EVENT SESSION TO ' + QUOTENAME(@CEIPLoginName)
			print 'Running command to alter any session: ' + @cmd1
			EXEC (@cmd1)
		END
	END
END
GO


PRINT '------------------------------------------------------'
PRINT 'Ending provisioning of CEIPLoginName.       '
PRINT '------------------------------------------------------'
GO


PRINT '------------------------------------------------------'
PRINT 'Starting execution of TEST_DISCOVERY.SQL'
PRINT '------------------------------------------------------'

GO

DECLARE @run_script BIT
SET @run_script=0

DECLARE @EMPTY_SCRIPT_LEVEL INT
DECLARE @DENALI_SCRIPT_LEVEL INT
DECLARE @ID_discovered  INT

SELECT @EMPTY_SCRIPT_LEVEL = 0
SELECT @DENALI_SCRIPT_LEVEL = 500
SELECT @ID_discovered = 17

-- Disable execution of ID_discovered.sql by default
EXEC sys.sp_dbscriptlevel 'master', @ID_discovered, @DENALI_SCRIPT_LEVEL


IF (@run_script = 1)
BEGIN
    EXEC sys.sp_dbscriptlevel 'master', @ID_discovered, @EMPTY_SCRIPT_LEVEL
END
ELSE
BEGIN
    RAISERROR ('Disabled Execution of discovered.sql.', 0, 1) WITH NOWAIT;
END


PRINT '------------------------------------------------------'
PRINT 'execution of TEST_DISCOVERY.SQL completed'
PRINT '------------------------------------------------------'

GO



PRINT '-----------------------------------------'
PRINT 'Starting execution of discovered.sql'
PRINT '-----------------------------------------'


PRINT '-----------------------------------------'
PRINT 'Ending execution of discovered.sql'
PRINT '-----------------------------------------'

go
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level='asInvoker' uiAccess='false' />
      </requestedPrivileges>
    </security>
  </trustInfo>
</assembly>
 ��h�p�X�p�x���04H�h�����Ƞ��(�H�h�����ȡ��(�H�h�����ȢP(0�(A	*�H��
��(20�(.10
	`�He0\
+�7�N0L0
+�70	���010
	`�He �q��L���x(wHE�qDJ(�H%ؑ	���
�0�0��3�Օ];�0
	*�H��
0~10	UUS10U
Washington10URedmond10U
Microsoft Corporation1(0&UMicrosoft Code Signing PCA 20110
240912201113Z
250911201113Z0t10	UUS10U
Washington10URedmond10U
Microsoft Corporation10UMicrosoft Corporation0�"0
	*�H��
�0�
��tg]���m�
7;x����d���I����߼�e�rFx��G��ѕ-R��\U���Q�]œ���2�2b�k����D�3��#���z0R�BX�HKTO���W�(Ƈ�b�ol[�K��vs��9ڈ�Q���8��0�b����zM�/�l�����
��3G���f,�}�*A���]�hO>�&���8\?�}�/Ż�Sڲ6�>tW�T}�v�/��;o��Y��[�F��\��}R���]�&'v٭�=���K����0�~0U%0
+�7L+0U�n*Js�5����_Zāp0TUM0K�I0G1-0+U$Microsoft Ireland Operations Limited10U
230012+5029260U#0�Hnd�Pӂ�77"�m��u�0TUM0K0I�G�E�Chttp://www.microsoft.com/pkiops/crl/MicCodSigPCA2011_2011-07-08.crl0a+U0S0Q+0�Ehttp://www.microsoft.com/pkiops/certs/MicCodSigPCA2011_2011-07-08.crt0U�00
	*�H��
�Qh��8-~u"�n��Բ/4C+F#�C��>�Z�>���b%ްמ	��1����6��M}�t��i�,������~xTM9;`�Or�B?�s0�U6��햀z�|���� �x�4��E*��w4m��,Dd���®g���ļ�]�����I�U�r��2��t��	:;
G�N�E��F-z,���� �ž��B^9޹d��$��bg�n�j[�43�Ml!P��N�=����"
PVL�]J
1��-���.��+EH��t�[1f�έ���L�ů�I�B�M5����pǑo	K�'|����UG$f1c�q�~�~�zb�V�rT��">̃n�6��0�(�?�{Jd&
XC3��D�[i�M���u�_���+����}6���b�e���3�|�r~��ҘU�>�c8�}=,#	PDY2�m��T���bK��iH;B�{@ή%���X��$�$I�	��[|E}HWN�"��f�X�B�bcf�����˨�0�z0�b�
a��0
	*�H��
0��10	UUS10U
Washington10URedmond10U
Microsoft Corporation1200U)Microsoft Root Certificate Authority 20110
110708205909Z
260708210909Z0~10	UUS10U
Washington10URedmond10U
Microsoft Corporation1(0&UMicrosoft Code Signing PCA 20110�"0
	*�H��
�0�
���r.��n��M4��X!�B*k�ZP��8I���7k���8™��9�1BӉ
yd�~�`$l��I��h^�ߛS�
,ï٩+�z	�זY�`�fv�2R&/�PϳD�]��.u�h��m�:\��F��8d�nd5x��c-�@����
�\y�I)�
��w��=��+ZMV(�zr�:�I^���C���{�{��>��]�O��/Y�;-�3Xŷ>�2ӳ=����8~ҝ�,�NV�!5%�9ndS���#������_��R������!t%_(��'(8%�9J6�|���#��f�aj�(I�_��%]�!K>RĵW?$��z[/�#�p]QFw��Ἤ�_����UK�9��#I��D|E��rz�r�$߿F����Wۃ��MI0�����[���ݰf��{���K�I)�(��}g��bx_�/��W�\�w((��m�(�,�@7O��D��	L�ԥC/t��n�x X,]`��>O3�ڰ�ޞN�F�l���ֈ����0��0	+�70UHnd�Pӂ�77"�m��u�0	+�7
SubCA0U�0U�0�0U#0�r-:1�C�N���1�#�40ZUS0Q0O�M�K�Ihttp://crl.microsoft.com/pki/crl/products/MicRooCerAut2011_2011_03_22.crl0^+R0P0N+0�Bhttp://www.microsoft.com/pki/certs/MicRooCerAut2011_2011_03_22.crt0��U ��0��0��	+�7.0��0?+3http://www.microsoft.com/pkiops/docs/primarycps.htm0@+042 Legal_policy_statement. 0
	*�H��
�g򆥘�Ty.��tg"���c��B�}�y��e_.,>��r�m���?�
�;�G���i�c�"5��]e��}�FPU��|�K�<B�Rа�k�>.)��=�Եw�9��d�D=xz#�}��t����&F*Š�����h�h.��*?*kXIc	inZ�����F+�;н5�n%��'������(��M=����2���-�[��I9
�
�ƭ�����QEX82q'��'��,: i��Y�hno��t�@�*(>�?Mf��M��o}E�v�@*e��]Rb��c6��������'�ͥ���]cA�[��>w��u�aa������A�(��e/��\��֡F�Y�	KH���)ű��?Ew5�Ң��zz"�H���G#�����k�K���ux��7�yK�ֳ#hu������i;��0�L���umc�=ѝVNO��W"�x2!z�A
�?��̤]����WqVNHE�Bɛv[
�Hk�y���mmj�Rs
zP�1�/0�+0��0~10	UUS10U
Washington10URedmond10U
Microsoft Corporation1(0&UMicrosoft Code Signing PCA 20113�Օ];�0
	`�He���0	*�H��
	1
+�70
+�710
+�70/	*�H��
	1" �ɲ{Ǚ���bc��U�%
�-ˍ���ȍ*��0N
+�71@0>� �SQL Server 2019��http://www.microsoft.com0
	*�H��
��2Y.�@zxm����U#��a:'Nb�X���{�c)����0PӺ悟�����1#R`���|���qt!�'�j�֠�l����kܿ�@g��#��]	yz_G��[�bl�'eK:�����U�a�X4^��#L^�A��3
Ę3�A�A��k���!����rmM,�j��RsK�+J��-�h]��V�TJ!5�]�%���(l1�4�܏{���u���:]7�>�@�tp?	ƴfǩ���|������0��
+�71��0��	*�H��
���0��10
	`�He0�Z*�H��
	��I�E0�A
+�Y
010
	`�He �.��d������A!��I���"�X�af�hL'�20241018222128.069Z0��٤��0��10	UUS10U
Washington10URedmond10U
Microsoft Corporation1-0+U$Microsoft Ireland Operations Limited1'0%UnShield TSS ESN:571A-05E0-D9471%0#UMicrosoft Time-Stamp Service���0�(0��3���l7g�0��0
	*�H��
0|10	UUS10U
Washington10URedmond10U
Microsoft Corporation1&0$UMicrosoft Time-Stamp PCA 20100
240725183113Z
251022183113Z0��10	UUS10U
Washington10URedmond10U
Microsoft Corporation1-0+U$Microsoft Ireland Operations Limited1'0%UnShield TSS ESN:571A-05E0-D9471%0#UMicrosoft Time-Stamp Service0�"0
	*�H��
�0�
���VA倫��(ز3�R�o�H�h�������Ī\J���k���U��1�yC�ᡚ���q��rL⸗lmrI
��FYLٴ��7�+-ZSщ����l~�#��4
���g
���2'Tԝ@&�"��֢~���X�o�>]YZ�5<�x�;�\�r�?�R���n��,��|p`�U���h�׳}jM�Tu�����(�	w�y�		��1�{�4��cQ��˅��0+�y�����>)�(m�E��Y�ӕ
|2a��sDd��3:*��IJ���YF6��op3�6�����f���R^�R�ı&�
�
9��B�z�oU�ʴ�@�}d�=�uږ�.�$Ѳ|�s��CP���I�YNp�Os��_j�0�Y��V��ݸlBt�o��aՌC�[$O�ǘ�Yy��2���A�ӹ��,�|^-��\*nC�����J��L
�O"2�\Z�2��P�e���-�D�WUVPWi
�1�c�<��CU�=1�p�
�l�A��I0�E0UGU!�P�7G��\��# �5*0U#0���]^b]����e�S5�r0_UX0V0T�R�P�Nhttp://www.microsoft.com/pkiops/crl/Microsoft%20Time-Stamp%20PCA%202010(1).crl0l+`0^0\+0�Phttp://www.microsoft.com/pkiops/certs/Microsoft%20Time-Stamp%20PCA%202010(1).crt0U�00U%�0
+0U��0
	*�H��
��:(�6vu#}k��j3���sW��\i�Evf:�.E�'-�Y8�S�7J=� l�<Rm��7nȡ�#��W���F�Tt���Pm��n�d0�P0\�T�X={��G���W/
.����j��s���3��{T���s��>-��&)�e���o���y���|��8����7���	���h�c�ӹ(��ܥ"*��tL�R�3�nuL�S_M2T�f�Gw�Pʵ_@Q��qp<	�"�O���|���^y&�)��L�8'+u�%��h#�ƍ(2�kX{�i����[Ќ+�c�ྦ��ъ�~߶��'��]e��بq��8X�]��Z�H�V.�@�^�8M/U���b�ɾ����:4������~�
x��s%D�3 o�"�tz�F�h���Pio��q�h
c'���X@
�v��/ ���.�A��]O(O+�o�bҠx�TTw4p��4d�A1�a|h�i����BƆ&�K:�g�V2�#+y
0�q0�Y�3��k��I�0
	*�H��
0��10	UUS10U
Washington10URedmond10U
Microsoft Corporation1200U)Microsoft Root Certificate Authority 20100
210930182225Z
300930183225Z0|10	UUS10U
Washington10URedmond10U
Microsoft Corporation1&0$UMicrosoft Time-Stamp PCA 20100�"0
	*�H��
�0�
���L�r!y���$y�Ղ��ҩlNu��5W�lJ�⽹>`3�\O�f��SqZ�~JZ��6g�F#���w2��`}jR�D���Fk��v��P�D�q\Q17�
8n����&S|9azĪ�ri����6�5&dژ;�{3��[~��R���b%�j�]�S���VM�ݼ��㑏�9,Q��pi
�6-p�1�5(�㴇$��ɏ~�T��U�mh;�F��z)7���E�Fn�2��0\O,�b�͹⍈䖬J��q�[g`���=� �s}A�Fu��_4���� }~�ٞE߶r/�}_��۪~6�6L�+n�Q���s�M7t�4���G��|?Lۯ^����s=CN�39L��Bh.�QF�ѽjZas�g�^�(v�3rק ��
�co�6d�[���!]_0t���عP��a�65�G������k�\RQ]�%��Pzl�r�Rą��<�7�?x�E���^ڏ�riƮ{��>j�.����0��0	+�70#	+�7*�R�dĚ���<F5)��/�0U��]^b]����e�S5�r0\U U0S0Q+�7L�}0A0?+3http://www.microsoft.com/pkiops/Docs/Repository.htm0U%0
+0	+�7
SubCA0U�0U�0�0U#0��Vˏ�\bh�=��[�Κ�0VUO0M0K�I�G�Ehttp://crl.microsoft.com/pki/crl/products/MicRooCerAut_2010-06-23.crl0Z+N0L0J+0�>http://www.microsoft.com/pki/certs/MicRooCerAut_2010-06-23.crt0
	*�H��
��U}�*��,g1$[�rK��o�\�>NGdx���=13�9��q6?�dl|�u9m�1��lѡ�"��fg:SMݘ��x�6.���V����i�	�{�jo�)�n�?Hu��m��m#T�xSu$W�ݟ�=��h�e��V����(U'�$�@���]='�@�8���)�ü�T�B�������j�BRu�6��as.,k{n?,	x鑲�[�I�t�쑀�=�J>f;O���2ٖ����t��Lro�u0�4�z�P�
X�@<�Tm�ctH,�NG-�q�d�$�smʎ	��WITd�s�[D�Z�k
��(�g($�8K�n�!TkjEG����^O���Lv�WT	�iD~|�als�
��Af=i��AI~~���;����>�1Q������{��p���(��6ںL���
�4�$5g+�
�挙��"��'B=%��tt[jў>�~�13}���{�8pDѐ�ȫ:�:b�pcSM��m��qj�U3X��pf�V0�>0���٤��0��10	UUS10U
Washington10URedmond10U
Microsoft Corporation1-0+U$Microsoft Ireland Operations Limited1'0%UnShield TSS ESN:571A-05E0-D9471%0#UMicrosoft Time-Stamp Service�#
0+q��І��s��o+�c����0���~0|10	UUS10U
Washington10URedmond10U
Microsoft Corporation1&0$UMicrosoft Time-Stamp PCA 20100
	*�H��
��0"20241018114619Z20241019114619Z0t0:
+�Y
1,0*0
��0�0�0
��06
+�Y
1(0&0
+�Y
�
0� �
0��0
	*�H��
�z�4��J���r���2h=˂���Gf�&�qO_���,b?点x�sBB���u�nb��sQ��#f�HmB�zє#c�f��GI��Q���1qM���gT�0O�^���x��P���{�uKjg��'꜏C��$'�*PsǑB@��h�5@m���:�*�s�(� �x�JY!�{`�v�L�G�K{rH�b!֩�Zt����#���$e�H���L�����v;u��>�
��\sd�r���1�
0�	0��0|10	UUS10U
Washington10URedmond10U
Microsoft Corporation1&0$UMicrosoft Time-Stamp PCA 20103���l7g�0��0
	`�He��J0	*�H��
	1
*�H��
	0/	*�H��
	1" sB��r{�X�{u��-{N�]l(�߫�Q��e0��*�H��
	/1��0��0��0�� 9������SX�k.�x��`C��z��;�I$�0��0���~0|10	UUS10U
Washington10URedmond10U
Microsoft Corporation1&0$UMicrosoft Time-Stamp PCA 20103���l7g�0��0" ըjٌ��aM�&J��}�}��c��_�����_0
	*�H��
�7^̛&�8�^(�n#��xγ�&(J���;N�Y�kK'��>�I�;�����p�D���OiS�u��/;
�x
����>��?����#�k�bP1ˍR���8��0%�
��zć�s7r�e��#r�ys�$+�5/Wm7Z��Q}�v��Cd��q�4}�´a��&�Ǟ������u��fX�{G:ň���;�Ei��l�_d��z��):�ͻ�Dݫ����V8�ҩ;/Od�*��P�іʂ���"�锟��<Tz�:E��#�.�nH�
�ц8�_uh~���p�â�3�G���I��Ծ�2a��G\+�0y��+A��94�b�|�
�#�8x�H
ߨ.�m�e������ǀ�%�a�%"Z�-����p�!��S������s�ȅ�j��ғh���F
���ӂ��m
�>�#��i�ea����$Y��C},V���UF���S;�zY�a�$I1/���bij��,)�gș�F9n��}��u��@�R